Merge pull request #30466 from backstage/canon-skeleton
Canon - Add new Skeleton component
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/canon': patch
|
||||
---
|
||||
|
||||
Add new Skeleton component in Canon
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,59 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { SkeletonSnippet } from '@/snippets/stories-snippets';
|
||||
import {
|
||||
skeletonPropDefs,
|
||||
skeletonUsageSnippet,
|
||||
skeletonDefaultSnippet,
|
||||
skeletonDemo1Snippet,
|
||||
skeletonDemo2Snippet,
|
||||
} from './skeleton.props';
|
||||
import { ComponentInfos } from '@/components/ComponentInfos';
|
||||
|
||||
# Skeleton
|
||||
|
||||
Use to show a placeholder while content is loading.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<SkeletonSnippet story="Demo2" />}
|
||||
code={skeletonDefaultSnippet}
|
||||
/>
|
||||
|
||||
<ComponentInfos
|
||||
component="skeleton"
|
||||
classNames={['canon-Skeleton']}
|
||||
usageCode={skeletonUsageSnippet}
|
||||
/>
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={skeletonPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Demo 1
|
||||
|
||||
You can use a mix of different sizes to create a more complex skeleton.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<SkeletonSnippet story="Demo1" />}
|
||||
code={skeletonDemo1Snippet}
|
||||
open
|
||||
/>
|
||||
|
||||
### Demo 2
|
||||
|
||||
You can use a mix of different sizes to create a more complex skeleton.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<SkeletonSnippet story="Demo2" />}
|
||||
code={skeletonDemo2Snippet}
|
||||
open
|
||||
/>
|
||||
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const skeletonPropDefs: Record<string, PropDef> = {
|
||||
width: {
|
||||
type: 'number',
|
||||
default: '80',
|
||||
responsive: false,
|
||||
},
|
||||
height: {
|
||||
type: 'number',
|
||||
default: '24',
|
||||
responsive: false,
|
||||
},
|
||||
rounded: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
responsive: false,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const skeletonUsageSnippet = `import { Flex, Skeleton } from '@backstage/canon';
|
||||
|
||||
<Flex direction="column" gap="4">
|
||||
<Skeleton width={400} height={160} />
|
||||
<Skeleton width={400} height={12} />
|
||||
<Skeleton width={240} height={12} />
|
||||
</Flex>`;
|
||||
|
||||
export const skeletonDefaultSnippet = `<Flex direction="column" gap="4">
|
||||
<Skeleton width={400} height={160} />
|
||||
<Skeleton width={400} height={12} />
|
||||
<Skeleton width={240} height={12} />
|
||||
</Flex>`;
|
||||
|
||||
export const skeletonDemo1Snippet = `<Flex gap="4">
|
||||
<Skeleton rounded width={48} height={48} />
|
||||
<Flex direction="column" gap="4">
|
||||
<Skeleton width={200} height={8} />
|
||||
<Skeleton width={200} height={8} />
|
||||
<Skeleton width={200} height={8} />
|
||||
<Flex gap="4">
|
||||
<Skeleton width="100%" height={8} />
|
||||
<Skeleton width="100%" height={8} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>`;
|
||||
|
||||
export const skeletonDemo2Snippet = `<Flex direction="column" gap="4">
|
||||
<Skeleton width={400} height={160} />
|
||||
<Skeleton width={400} height={12} />
|
||||
<Skeleton width={240} height={12} />
|
||||
</Flex>`;
|
||||
@@ -23,6 +23,7 @@ import * as TabsStories from '../../../packages/canon/src/components/Tabs/Tabs.s
|
||||
import * as SwitchStories from '../../../packages/canon/src/components/Switch/Switch.stories';
|
||||
import * as SearchFieldStories from '../../../packages/canon/src/components/SearchField/SearchField.stories';
|
||||
import * as TooltipStories from '../../../packages/canon/src/components/Tooltip/Tooltip.stories';
|
||||
import * as SkeletonStories from '../../../packages/canon/src/components/Skeleton/Skeleton.stories';
|
||||
|
||||
export const BoxSnippet = ({ story }: { story: keyof typeof BoxStories }) => {
|
||||
const stories = composeStories(BoxStories);
|
||||
@@ -233,3 +234,14 @@ export const TooltipSnippet = ({
|
||||
|
||||
return StoryComponent ? <StoryComponent /> : null;
|
||||
};
|
||||
|
||||
export const SkeletonSnippet = ({
|
||||
story,
|
||||
}: {
|
||||
story: keyof typeof SkeletonStories;
|
||||
}) => {
|
||||
const stories = composeStories(SkeletonStories);
|
||||
const StoryComponent = stories[story as keyof typeof stories];
|
||||
|
||||
return StoryComponent ? <StoryComponent /> : null;
|
||||
};
|
||||
|
||||
@@ -131,6 +131,11 @@ export const components: Page[] = [
|
||||
slug: 'select',
|
||||
status: 'alpha',
|
||||
},
|
||||
{
|
||||
title: 'Skeleton',
|
||||
slug: 'skeleton',
|
||||
status: 'alpha',
|
||||
},
|
||||
{
|
||||
title: 'Switch',
|
||||
slug: 'switch',
|
||||
|
||||
@@ -9088,6 +9088,12 @@
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
50% {
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--canon-font-regular: system-ui;
|
||||
--canon-font-monospace: ui-monospace, "Menlo", "Monaco", "Consolas", "Liberation Mono", "Courier New", monospace;
|
||||
@@ -9173,6 +9179,7 @@
|
||||
--canon-ring: #1f5493;
|
||||
--canon-scrollbar: #a0a0a03b;
|
||||
--canon-scrollbar-thumb: #a0a0a0;
|
||||
--canon-animate-pulse: pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
@@ -10460,6 +10467,16 @@
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.canon-Skeleton {
|
||||
animation: var(--canon-animate-pulse);
|
||||
background-color: var(--canon-bg-surface-2);
|
||||
border-radius: var(--canon-radius-2);
|
||||
}
|
||||
|
||||
.canon-Skeleton[data-rounded="true"] {
|
||||
border-radius: var(--canon-radius-full);
|
||||
}
|
||||
|
||||
.canon-Tooltip {
|
||||
background: var(--canon-bg-surface-2);
|
||||
forced-color-adjust: none;
|
||||
|
||||
@@ -448,6 +448,11 @@ export const componentDefinitions: {
|
||||
readonly size: readonly ['small', 'medium'];
|
||||
};
|
||||
};
|
||||
readonly Skeleton: {
|
||||
readonly classNames: {
|
||||
readonly root: 'canon-Skeleton';
|
||||
};
|
||||
};
|
||||
readonly Switch: {
|
||||
readonly classNames: {
|
||||
readonly root: 'canon-Switch';
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Skeleton } from './Skeleton';
|
||||
import { Flex } from '../Flex';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Skeleton',
|
||||
component: Skeleton,
|
||||
argTypes: {
|
||||
rounded: {
|
||||
control: 'boolean',
|
||||
},
|
||||
width: {
|
||||
control: 'number',
|
||||
},
|
||||
height: {
|
||||
control: 'number',
|
||||
},
|
||||
},
|
||||
args: {
|
||||
width: 80,
|
||||
height: 24,
|
||||
rounded: false,
|
||||
},
|
||||
} satisfies Meta<typeof Skeleton>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {},
|
||||
};
|
||||
|
||||
export const Rounded: Story = {
|
||||
args: {
|
||||
rounded: true,
|
||||
width: 48,
|
||||
height: 48,
|
||||
},
|
||||
};
|
||||
|
||||
export const Demo1: Story = {
|
||||
render: () => (
|
||||
<Flex gap="4">
|
||||
<Skeleton rounded width={48} height={48} />
|
||||
<Flex direction="column" gap="4">
|
||||
<Skeleton width={200} height={8} />
|
||||
<Skeleton width={200} height={8} />
|
||||
<Skeleton width={200} height={8} />
|
||||
<Flex gap="4">
|
||||
<Skeleton width="100%" height={8} />
|
||||
<Skeleton width="100%" height={8} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
),
|
||||
};
|
||||
|
||||
export const Demo2: Story = {
|
||||
render: () => (
|
||||
<Flex direction="column" gap="4">
|
||||
<Skeleton width={400} height={160} />
|
||||
<Skeleton width={400} height={12} />
|
||||
<Skeleton width={240} height={12} />
|
||||
</Flex>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.canon-Skeleton {
|
||||
animation: var(--canon-animate-pulse);
|
||||
background-color: var(--canon-bg-surface-2);
|
||||
border-radius: var(--canon-radius-2);
|
||||
}
|
||||
|
||||
.canon-Skeleton[data-rounded='true'] {
|
||||
border-radius: var(--canon-radius-full);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { SkeletonProps } from './types';
|
||||
|
||||
/** @public */
|
||||
export const Skeleton = (props: SkeletonProps) => {
|
||||
const { width = 80, height = 24, rounded = false, ...rest } = props;
|
||||
const { classNames } = useStyles('Skeleton');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames.root}
|
||||
data-rounded={rounded}
|
||||
style={{
|
||||
width,
|
||||
height,
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { Skeleton } from './Skeleton';
|
||||
export type { SkeletonProps } from './types';
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentProps } from 'react';
|
||||
|
||||
/** @public */
|
||||
export interface SkeletonProps extends ComponentProps<'div'> {
|
||||
width?: number | string;
|
||||
height?: number | string;
|
||||
rounded?: boolean;
|
||||
}
|
||||
@@ -43,6 +43,7 @@
|
||||
@import '../components/Text/styles.css';
|
||||
@import '../components/TextField/TextField.styles.css';
|
||||
@import '../components/SearchField/SearchField.styles.css';
|
||||
@import '../components/Skeleton/Skeleton.styles.css';
|
||||
@import '../components/Tooltip/Tooltip.styles.css';
|
||||
@import '../components/ScrollArea/ScrollArea.styles.css';
|
||||
@import '../components/Select/Select.styles.css';
|
||||
|
||||
@@ -24,6 +24,12 @@
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* Light theme tokens */
|
||||
:root {
|
||||
/* Font families */
|
||||
@@ -132,6 +138,8 @@
|
||||
--canon-ring: #1f5493;
|
||||
--canon-scrollbar: #a0a0a03b;
|
||||
--canon-scrollbar-thumb: #a0a0a0;
|
||||
|
||||
--canon-animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
/* Dark theme tokens */
|
||||
|
||||
@@ -178,6 +178,11 @@ export const componentDefinitions = {
|
||||
size: ['small', 'medium'] as const,
|
||||
},
|
||||
},
|
||||
Skeleton: {
|
||||
classNames: {
|
||||
root: 'canon-Skeleton',
|
||||
},
|
||||
},
|
||||
Switch: {
|
||||
classNames: {
|
||||
root: 'canon-Switch',
|
||||
|
||||
Reference in New Issue
Block a user