diff --git a/packages/grafana-ui/src/components/Badge/Badge.tsx b/packages/grafana-ui/src/components/Badge/Badge.tsx index e05b9056517..969c6ebfb10 100644 --- a/packages/grafana-ui/src/components/Badge/Badge.tsx +++ b/packages/grafana-ui/src/components/Badge/Badge.tsx @@ -7,6 +7,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '../../themes/ThemeContext'; import { IconName } from '../../types'; +import { SkeletonComponent, attachSkeleton } from '../../utils/skeleton'; import { Icon } from '../Icon/Icon'; import { Tooltip } from '../Tooltip/Tooltip'; @@ -38,19 +39,13 @@ const BadgeComponent = React.memo(({ icon, color, text, tooltip, cla }); BadgeComponent.displayName = 'Badge'; -const BadgeSkeleton = () => { +const BadgeSkeleton: SkeletonComponent = ({ rootProps }) => { const styles = useStyles2(getSkeletonStyles); - return ; + return ; }; -interface BadgeWithSkeleton extends React.NamedExoticComponent { - Skeleton: typeof BadgeSkeleton; -} - -export const Badge: BadgeWithSkeleton = Object.assign(BadgeComponent, { Skeleton: BadgeSkeleton }); - -Badge.Skeleton = BadgeSkeleton; +export const Badge = attachSkeleton(BadgeComponent, BadgeSkeleton); const getSkeletonStyles = () => ({ container: css({ diff --git a/packages/grafana-ui/src/components/Tags/Tag.tsx b/packages/grafana-ui/src/components/Tags/Tag.tsx index 84742cfd987..81a857ee4ff 100644 --- a/packages/grafana-ui/src/components/Tags/Tag.tsx +++ b/packages/grafana-ui/src/components/Tags/Tag.tsx @@ -7,6 +7,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2, useTheme2 } from '../../themes'; import { IconName } from '../../types/icon'; import { getTagColor, getTagColorsFromName } from '../../utils'; +import { SkeletonComponent, attachSkeleton } from '../../utils/skeleton'; import { Icon } from '../Icon/Icon'; /** @@ -50,18 +51,12 @@ const TagComponent = forwardRef(({ name, onClick, icon, clas }); TagComponent.displayName = 'Tag'; -const TagSkeleton = () => { +const TagSkeleton: SkeletonComponent = ({ rootProps }) => { const styles = useStyles2(getSkeletonStyles); - return ; + return ; }; -interface TagWithSkeleton extends React.ForwardRefExoticComponent> { - Skeleton: typeof TagSkeleton; -} - -export const Tag: TagWithSkeleton = Object.assign(TagComponent, { - Skeleton: TagSkeleton, -}); +export const Tag = attachSkeleton(TagComponent, TagSkeleton); const getSkeletonStyles = () => ({ container: css({ diff --git a/packages/grafana-ui/src/components/Tags/TagList.tsx b/packages/grafana-ui/src/components/Tags/TagList.tsx index 6749f2fc781..fefd90c4b4c 100644 --- a/packages/grafana-ui/src/components/Tags/TagList.tsx +++ b/packages/grafana-ui/src/components/Tags/TagList.tsx @@ -5,6 +5,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2, useTheme2 } from '../../themes'; import { IconName } from '../../types/icon'; +import { SkeletonComponent, attachSkeleton } from '../../utils/skeleton'; import { OnTagClick, Tag } from './Tag'; @@ -56,23 +57,17 @@ const TagListComponent = memo( ); TagListComponent.displayName = 'TagList'; -const TagListSkeleton = () => { +const TagListSkeleton: SkeletonComponent = ({ rootProps }) => { const styles = useStyles2(getSkeletonStyles); return ( -
+
); }; -interface TagListWithSkeleton extends React.ForwardRefExoticComponent> { - Skeleton: typeof TagListSkeleton; -} - -export const TagList: TagListWithSkeleton = Object.assign(TagListComponent, { - Skeleton: TagListSkeleton, -}); +export const TagList = attachSkeleton(TagListComponent, TagListSkeleton); const getSkeletonStyles = (theme: GrafanaTheme2) => ({ container: css({ diff --git a/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx b/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx index 996ab956201..5b2d5fef657 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx +++ b/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx @@ -10,6 +10,7 @@ import { getExtraStyles } from './extra'; import { getFormElementStyles } from './forms'; import { getMarkdownStyles } from './markdownStyles'; import { getPageStyles } from './page'; +import { getSkeletonStyles } from './skeletonStyles'; /** @internal */ export function GlobalStyles() { @@ -25,6 +26,7 @@ export function GlobalStyles() { getCardStyles(theme), getAgularPanelStyles(theme), getMarkdownStyles(theme), + getSkeletonStyles(theme), ]} /> ); diff --git a/packages/grafana-ui/src/themes/GlobalStyles/skeletonStyles.ts b/packages/grafana-ui/src/themes/GlobalStyles/skeletonStyles.ts new file mode 100644 index 00000000000..21ff50536a3 --- /dev/null +++ b/packages/grafana-ui/src/themes/GlobalStyles/skeletonStyles.ts @@ -0,0 +1,11 @@ +import { css } from '@emotion/react'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { skeletonAnimation } from '../../utils/skeleton'; + +export const getSkeletonStyles = (theme: GrafanaTheme2) => { + return css({ + '.react-loading-skeleton': skeletonAnimation, + }); +}; diff --git a/packages/grafana-ui/src/unstable.ts b/packages/grafana-ui/src/unstable.ts index 106197389da..02938ddf2d2 100644 --- a/packages/grafana-ui/src/unstable.ts +++ b/packages/grafana-ui/src/unstable.ts @@ -8,3 +8,5 @@ * Once mature, they will be moved to the main export, be available to plugins, and * be subject to the standard policies */ + +export * from './utils/skeleton'; diff --git a/packages/grafana-ui/src/utils/skeleton.tsx b/packages/grafana-ui/src/utils/skeleton.tsx new file mode 100644 index 00000000000..042e14e3ea4 --- /dev/null +++ b/packages/grafana-ui/src/utils/skeleton.tsx @@ -0,0 +1,51 @@ +import { keyframes } from '@emotion/css'; +import React from 'react'; + +const fadeIn = keyframes({ + '0%': { + opacity: 0, + }, + '100%': { + opacity: 1, + }, +}); + +export const skeletonAnimation = { + animationName: fadeIn, + animationDelay: '100ms', + animationTimingFunction: 'ease-in', + animationDuration: '100ms', + animationFillMode: 'backwards', +}; + +interface SkeletonProps { + /** + * Spread these props at the root of your skeleton to handle animation logic + */ + rootProps: { + style: React.CSSProperties; + }; +} + +export type SkeletonComponent

= React.ComponentType

; + +/** + * Use this to attach a skeleton as a static property on the component. + * e.g. if you render a component with ``, you can render the skeleton with ``. + * @param Component A functional or class component + * @param Skeleton A functional or class skeleton component + * @returns A wrapped component with a static skeleton property + */ +export const attachSkeleton = (Component: C, Skeleton: SkeletonComponent

) => { + const skeletonWrapper = (props: P) => { + return ( + + ); + }; + return Object.assign(Component, { Skeleton: skeletonWrapper }); +}; diff --git a/public/app/features/admin/AdminOrgsTable.tsx b/public/app/features/admin/AdminOrgsTable.tsx index 225800056f8..8f6cd87a571 100644 --- a/public/app/features/admin/AdminOrgsTable.tsx +++ b/public/app/features/admin/AdminOrgsTable.tsx @@ -4,6 +4,7 @@ import Skeleton from 'react-loading-skeleton'; import { GrafanaTheme2 } from '@grafana/data'; import { Button, ConfirmModal, useStyles2 } from '@grafana/ui'; +import { SkeletonComponent, attachSkeleton } from '@grafana/ui/src/unstable'; import { contextSrv } from 'app/core/core'; import { AccessControlAction, Organization } from 'app/types'; @@ -22,7 +23,7 @@ const getTableHeader = () => ( ); -export function AdminOrgsTable({ orgs, onDelete }: Props) { +function AdminOrgsTableComponent({ orgs, onDelete }: Props) { const canDeleteOrgs = contextSrv.hasPermission(AccessControlAction.OrgsDelete); const [deleteOrg, setDeleteOrg] = useState(); @@ -74,10 +75,10 @@ export function AdminOrgsTable({ orgs, onDelete }: Props) { ); } -const AdminOrgsTableSkeleton = () => { +const AdminOrgsTableSkeleton: SkeletonComponent = ({ rootProps }) => { const styles = useStyles2(getSkeletonStyles); return ( - +
{getTableHeader()} {new Array(3).fill(null).map((_, index) => ( @@ -98,7 +99,7 @@ const AdminOrgsTableSkeleton = () => { ); }; -AdminOrgsTable.Skeleton = AdminOrgsTableSkeleton; +export const AdminOrgsTable = attachSkeleton(AdminOrgsTableComponent, AdminOrgsTableSkeleton); const getSkeletonStyles = (theme: GrafanaTheme2) => ({ deleteButton: css({ diff --git a/public/app/features/admin/AdminSettingsTable.tsx b/public/app/features/admin/AdminSettingsTable.tsx index 9b8ad467486..a026998aa1b 100644 --- a/public/app/features/admin/AdminSettingsTable.tsx +++ b/public/app/features/admin/AdminSettingsTable.tsx @@ -1,13 +1,15 @@ import React from 'react'; import Skeleton from 'react-loading-skeleton'; +import { SkeletonComponent, attachSkeleton } from '@grafana/ui/src/unstable'; + import { Settings } from './AdminSettings'; interface Props { settings: Settings; } -export const AdminSettingsTable = ({ settings }: Props) => { +const AdminSettingsTableComponent = ({ settings }: Props) => { return (
@@ -33,9 +35,9 @@ export const AdminSettingsTable = ({ settings }: Props) => { // note: don't want to put this in render function else it will get regenerated const randomValues = new Array(50).fill(null).map(() => Math.random()); -const AdminSettingsTableSkeleton = () => { +const AdminSettingsTableSkeleton: SkeletonComponent = ({ rootProps }) => { return ( -
+
{randomValues.map((randomValue, index) => { const isSection = index === 0 || randomValue > 0.9; @@ -70,4 +72,4 @@ function getRandomInRange(min: number, max: number, randomSeed: number) { return randomSeed * (max - min) + min; } -AdminSettingsTable.Skeleton = AdminSettingsTableSkeleton; +export const AdminSettingsTable = attachSkeleton(AdminSettingsTableComponent, AdminSettingsTableSkeleton); diff --git a/public/app/features/library-panels/components/LibraryPanelCard/LibraryPanelCard.tsx b/public/app/features/library-panels/components/LibraryPanelCard/LibraryPanelCard.tsx index 3955d939f05..dea580a005e 100644 --- a/public/app/features/library-panels/components/LibraryPanelCard/LibraryPanelCard.tsx +++ b/public/app/features/library-panels/components/LibraryPanelCard/LibraryPanelCard.tsx @@ -5,6 +5,7 @@ import Skeleton from 'react-loading-skeleton'; import { GrafanaTheme2 } from '@grafana/data'; import { config } from '@grafana/runtime'; import { Icon, Link, useStyles2 } from '@grafana/ui'; +import { SkeletonComponent, attachSkeleton } from '@grafana/ui/src/unstable'; import { getPanelPluginNotFound } from 'app/features/panel/components/PanelPluginError'; import { PanelTypeCard } from 'app/features/panel/components/VizTypePicker/PanelTypeCard'; @@ -20,7 +21,7 @@ export interface LibraryPanelCardProps { type Props = LibraryPanelCardProps & { children?: JSX.Element | JSX.Element[] }; -export const LibraryPanelCard = ({ libraryPanel, onClick, onDelete, showSecondaryActions }: Props) => { +const LibraryPanelCardComponent = ({ libraryPanel, onClick, onDelete, showSecondaryActions }: Props) => { const [showDeletionModal, setShowDeletionModal] = useState(false); const onDeletePanel = () => { @@ -53,17 +54,20 @@ export const LibraryPanelCard = ({ libraryPanel, onClick, onDelete, showSecondar ); }; -const LibraryPanelCardSkeleton = ({ showSecondaryActions }: Pick) => { +const LibraryPanelCardSkeleton: SkeletonComponent> = ({ + showSecondaryActions, + rootProps, +}) => { const styles = useStyles2(getStyles); return ( - + ); }; -LibraryPanelCard.Skeleton = LibraryPanelCardSkeleton; +export const LibraryPanelCard = attachSkeleton(LibraryPanelCardComponent, LibraryPanelCardSkeleton); interface FolderLinkProps { libraryPanel: LibraryElementDTO; diff --git a/public/app/features/manage-dashboards/components/SnapshotListTableRow.tsx b/public/app/features/manage-dashboards/components/SnapshotListTableRow.tsx index b9a6b81d66a..9a9209ffc62 100644 --- a/public/app/features/manage-dashboards/components/SnapshotListTableRow.tsx +++ b/public/app/features/manage-dashboards/components/SnapshotListTableRow.tsx @@ -3,6 +3,7 @@ import React from 'react'; import Skeleton from 'react-loading-skeleton'; import { Button, LinkButton, useStyles2 } from '@grafana/ui'; +import { SkeletonComponent, attachSkeleton } from '@grafana/ui/src/unstable'; import { Trans } from 'app/core/internationalization'; import { Snapshot } from 'app/features/dashboard/services/SnapshotSrv'; @@ -11,7 +12,7 @@ export interface Props { onRemove: () => void; } -export const SnapshotListTableRow = ({ snapshot, onRemove }: Props) => { +const SnapshotListTableRowComponent = ({ snapshot, onRemove }: Props) => { const url = snapshot.externalUrl || snapshot.url; return ( @@ -40,10 +41,10 @@ export const SnapshotListTableRow = ({ snapshot, onRemove }: Props) => { ); }; -const SnapshotListTableRowSkeleton = () => { +const SnapshotListTableRowSkeleton: SkeletonComponent = ({ rootProps }) => { const styles = useStyles2(getSkeletonStyles); return ( - + @@ -61,7 +62,7 @@ const SnapshotListTableRowSkeleton = () => { ); }; -SnapshotListTableRow.Skeleton = SnapshotListTableRowSkeleton; +export const SnapshotListTableRow = attachSkeleton(SnapshotListTableRowComponent, SnapshotListTableRowSkeleton); const getSkeletonStyles = () => ({ blockSkeleton: css({ diff --git a/public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx b/public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx index 17f225b33c0..ba6bad69f35 100644 --- a/public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx +++ b/public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx @@ -5,6 +5,7 @@ import Skeleton from 'react-loading-skeleton'; import { GrafanaTheme2, isUnsignedPluginSignature, PanelPluginMeta, PluginState } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { IconButton, PluginSignatureBadge, useStyles2 } from '@grafana/ui'; +import { SkeletonComponent, attachSkeleton } from '@grafana/ui/src/unstable'; import { PluginStateInfo } from 'app/features/plugins/components/PluginStateInfo'; interface Props { @@ -20,7 +21,7 @@ interface Props { const IMAGE_SIZE = 38; -export const PanelTypeCard = ({ +const PanelTypeCardComponent = ({ isCurrent, title, plugin, @@ -76,17 +77,23 @@ export const PanelTypeCard = ({ ); }; +PanelTypeCardComponent.displayName = 'PanelTypeCard'; interface SkeletonProps { hasDescription?: boolean; hasDelete?: boolean; } -const PanelTypeCardSkeleton = ({ children, hasDescription, hasDelete }: React.PropsWithChildren) => { +const PanelTypeCardSkeleton: SkeletonComponent> = ({ + children, + hasDescription, + hasDelete, + rootProps, +}) => { const styles = useStyles2(getStyles); const skeletonStyles = useStyles2(getSkeletonStyles); return ( -
+
@@ -103,8 +110,7 @@ const PanelTypeCardSkeleton = ({ children, hasDescription, hasDelete }: React.Pr ); }; -PanelTypeCard.displayName = 'PanelTypeCard'; -PanelTypeCard.Skeleton = PanelTypeCardSkeleton; +export const PanelTypeCard = attachSkeleton(PanelTypeCardComponent, PanelTypeCardSkeleton); const getSkeletonStyles = () => { return { diff --git a/public/app/features/playlist/PlaylistCard.tsx b/public/app/features/playlist/PlaylistCard.tsx index e5d64bd19a9..1706bcb93b7 100644 --- a/public/app/features/playlist/PlaylistCard.tsx +++ b/public/app/features/playlist/PlaylistCard.tsx @@ -4,6 +4,7 @@ import Skeleton from 'react-loading-skeleton'; import { GrafanaTheme2 } from '@grafana/data'; import { Button, Card, LinkButton, ModalsController, Stack, useStyles2 } from '@grafana/ui'; +import { attachSkeleton, SkeletonComponent } from '@grafana/ui/src/unstable'; import { t, Trans } from 'app/core/internationalization'; import { contextSrv } from 'app/core/services/context_srv'; import { DashNavButton } from 'app/features/dashboard/components/DashNav/DashNavButton'; @@ -17,7 +18,7 @@ interface Props { playlist: Playlist; } -export const PlaylistCard = ({ playlist, setStartPlaylist, setPlaylistToDelete }: Props) => { +const PlaylistCardComponent = ({ playlist, setStartPlaylist, setPlaylistToDelete }: Props) => { return ( @@ -62,10 +63,10 @@ export const PlaylistCard = ({ playlist, setStartPlaylist, setPlaylistToDelete } ); }; -const PlaylistCardSkeleton = () => { +const PlaylistCardSkeleton: SkeletonComponent = ({ rootProps }) => { const skeletonStyles = useStyles2(getSkeletonStyles); return ( - + @@ -84,7 +85,7 @@ const PlaylistCardSkeleton = () => { ); }; -PlaylistCard.Skeleton = PlaylistCardSkeleton; +export const PlaylistCard = attachSkeleton(PlaylistCardComponent, PlaylistCardSkeleton); function getSkeletonStyles(theme: GrafanaTheme2) { return { diff --git a/public/app/features/playlist/PlaylistPageList.tsx b/public/app/features/playlist/PlaylistPageList.tsx index 74d39603b6f..0f3fecfe55e 100644 --- a/public/app/features/playlist/PlaylistPageList.tsx +++ b/public/app/features/playlist/PlaylistPageList.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; +import { SkeletonComponent, attachSkeleton } from '@grafana/ui/src/unstable'; import { PlaylistCard } from './PlaylistCard'; import { Playlist } from './types'; @@ -13,7 +14,7 @@ interface Props { playlists: Playlist[]; } -export const PlaylistPageList = ({ playlists, setStartPlaylist, setPlaylistToDelete }: Props) => { +const PlaylistPageListComponent = ({ playlists, setStartPlaylist, setPlaylistToDelete }: Props) => { const styles = useStyles2(getStyles); return (
    @@ -30,10 +31,10 @@ export const PlaylistPageList = ({ playlists, setStartPlaylist, setPlaylistToDel ); }; -const PlaylistPageListSkeleton = () => { +const PlaylistPageListSkeleton: SkeletonComponent = ({ rootProps }) => { const styles = useStyles2(getStyles); return ( -
    +
    @@ -41,7 +42,7 @@ const PlaylistPageListSkeleton = () => { ); }; -PlaylistPageList.Skeleton = PlaylistPageListSkeleton; +export const PlaylistPageList = attachSkeleton(PlaylistPageListComponent, PlaylistPageListSkeleton); function getStyles(theme: GrafanaTheme2) { return { diff --git a/public/app/features/plugins/admin/components/PluginListItem.tsx b/public/app/features/plugins/admin/components/PluginListItem.tsx index 57434ed32a0..8dbfdd21b6a 100644 --- a/public/app/features/plugins/admin/components/PluginListItem.tsx +++ b/public/app/features/plugins/admin/components/PluginListItem.tsx @@ -4,6 +4,7 @@ import Skeleton from 'react-loading-skeleton'; import { GrafanaTheme2 } from '@grafana/data'; import { Badge, Icon, Stack, useStyles2 } from '@grafana/ui'; +import { SkeletonComponent, attachSkeleton } from '@grafana/ui/src/unstable'; import { CatalogPlugin, PluginIconName, PluginListDisplayMode } from '../types'; @@ -18,7 +19,7 @@ type Props = { displayMode?: PluginListDisplayMode; }; -export function PluginListItem({ plugin, pathName, displayMode = PluginListDisplayMode.Grid }: Props) { +function PluginListItemComponent({ plugin, pathName, displayMode = PluginListDisplayMode.Grid }: Props) { const styles = useStyles2(getStyles); const isList = displayMode === PluginListDisplayMode.List; @@ -37,12 +38,15 @@ export function PluginListItem({ plugin, pathName, displayMode = PluginListDispl ); } -const PluginListItemSkeleton = ({ displayMode = PluginListDisplayMode.Grid }: Pick) => { +const PluginListItemSkeleton: SkeletonComponent> = ({ + displayMode = PluginListDisplayMode.Grid, + rootProps, +}) => { const styles = useStyles2(getStyles); const isList = displayMode === PluginListDisplayMode.List; return ( -
    +
    { diff --git a/public/app/features/serviceaccounts/components/ServiceAccountsListItem.tsx b/public/app/features/serviceaccounts/components/ServiceAccountsListItem.tsx index ed78f6da724..81166afd6f3 100644 --- a/public/app/features/serviceaccounts/components/ServiceAccountsListItem.tsx +++ b/public/app/features/serviceaccounts/components/ServiceAccountsListItem.tsx @@ -4,6 +4,7 @@ import Skeleton from 'react-loading-skeleton'; import { GrafanaTheme2, OrgRole } from '@grafana/data'; import { Button, Icon, IconButton, Stack, useStyles2 } from '@grafana/ui'; +import { SkeletonComponent, attachSkeleton } from '@grafana/ui/src/unstable'; import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker'; import { contextSrv } from 'app/core/core'; import { OrgRolePicker } from 'app/features/admin/OrgRolePicker'; @@ -162,11 +163,11 @@ const ServiceAccountListItemComponent = memo( ); ServiceAccountListItemComponent.displayName = 'ServiceAccountListItem'; -const ServiceAccountsListItemSkeleton = () => { +const ServiceAccountsListItemSkeleton: SkeletonComponent = ({ rootProps }) => { const styles = useStyles2(getSkeletonStyles); return ( -
+ @@ -193,12 +194,7 @@ const ServiceAccountsListItemSkeleton = () => { ); }; -interface ServiceAccountsListItemWithSkeleton extends React.NamedExoticComponent { - Skeleton: typeof ServiceAccountsListItemSkeleton; -} -const ServiceAccountListItem: ServiceAccountsListItemWithSkeleton = Object.assign(ServiceAccountListItemComponent, { - Skeleton: ServiceAccountsListItemSkeleton, -}); +const ServiceAccountListItem = attachSkeleton(ServiceAccountListItemComponent, ServiceAccountsListItemSkeleton); const getSkeletonStyles = (theme: GrafanaTheme2) => ({ blockSkeleton: css({ diff --git a/public/app/plugins/panel/news/component/News.tsx b/public/app/plugins/panel/news/component/News.tsx index 2dbebeebf73..39c95ff82d1 100644 --- a/public/app/plugins/panel/news/component/News.tsx +++ b/public/app/plugins/panel/news/component/News.tsx @@ -4,6 +4,7 @@ import Skeleton from 'react-loading-skeleton'; import { DataFrameView, GrafanaTheme2, textUtil, dateTimeFormat } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; +import { attachSkeleton, SkeletonComponent } from '@grafana/ui/src/unstable'; import { NewsItem } from '../types'; @@ -14,7 +15,7 @@ interface NewsItemProps { data: DataFrameView; } -export function News({ width, showImage, data, index }: NewsItemProps) { +function NewsComponent({ width, showImage, data, index }: NewsItemProps) { const styles = useStyles2(getStyles); const useWideLayout = width > 600; const newsItem = data.get(index); @@ -46,12 +47,16 @@ export function News({ width, showImage, data, index }: NewsItemProps) { ); } -const NewsSkeleton = ({ width, showImage }: Pick) => { +const NewsSkeleton: SkeletonComponent> = ({ + width, + showImage, + rootProps, +}) => { const styles = useStyles2(getStyles); const useWideLayout = width > 600; return ( -
+
{showImage && ( ({ container: css({