@fragments-sdk/ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +44 -0
- package/src/brand.ts +15 -0
- package/src/components/Alert/Alert.fragment.tsx +163 -0
- package/src/components/Alert/Alert.module.scss +116 -0
- package/src/components/Alert/index.tsx +95 -0
- package/src/components/Avatar/Avatar.fragment.tsx +147 -0
- package/src/components/Avatar/Avatar.module.scss +136 -0
- package/src/components/Avatar/index.tsx +177 -0
- package/src/components/Badge/Badge.fragment.tsx +151 -0
- package/src/components/Badge/Badge.module.scss +87 -0
- package/src/components/Badge/index.tsx +55 -0
- package/src/components/Button/Button.fragment.tsx +159 -0
- package/src/components/Button/Button.module.scss +97 -0
- package/src/components/Button/index.tsx +51 -0
- package/src/components/Card/Card.fragment.tsx +156 -0
- package/src/components/Card/Card.module.scss +86 -0
- package/src/components/Card/index.tsx +79 -0
- package/src/components/Checkbox/Checkbox.fragment.tsx +166 -0
- package/src/components/Checkbox/Checkbox.module.scss +144 -0
- package/src/components/Checkbox/index.tsx +166 -0
- package/src/components/Dialog/Dialog.fragment.tsx +179 -0
- package/src/components/Dialog/Dialog.module.scss +158 -0
- package/src/components/Dialog/index.tsx +230 -0
- package/src/components/EmptyState/EmptyState.fragment.tsx +222 -0
- package/src/components/EmptyState/EmptyState.module.scss +120 -0
- package/src/components/EmptyState/index.tsx +80 -0
- package/src/components/Input/Input.fragment.tsx +174 -0
- package/src/components/Input/Input.module.scss +64 -0
- package/src/components/Input/index.tsx +76 -0
- package/src/components/Menu/Menu.fragment.tsx +168 -0
- package/src/components/Menu/Menu.module.scss +190 -0
- package/src/components/Menu/index.tsx +318 -0
- package/src/components/Popover/Popover.fragment.tsx +178 -0
- package/src/components/Popover/Popover.module.scss +165 -0
- package/src/components/Popover/index.tsx +229 -0
- package/src/components/Progress/Progress.fragment.tsx +142 -0
- package/src/components/Progress/Progress.module.scss +185 -0
- package/src/components/Progress/index.tsx +196 -0
- package/src/components/RadioGroup/RadioGroup.fragment.tsx +188 -0
- package/src/components/RadioGroup/RadioGroup.module.scss +155 -0
- package/src/components/RadioGroup/index.tsx +166 -0
- package/src/components/Select/Select.fragment.tsx +173 -0
- package/src/components/Select/Select.module.scss +187 -0
- package/src/components/Select/index.tsx +233 -0
- package/src/components/Separator/Separator.fragment.tsx +148 -0
- package/src/components/Separator/Separator.module.scss +92 -0
- package/src/components/Separator/index.tsx +89 -0
- package/src/components/Skeleton/Skeleton.fragment.tsx +147 -0
- package/src/components/Skeleton/Skeleton.module.scss +166 -0
- package/src/components/Skeleton/index.tsx +185 -0
- package/src/components/Table/Table.fragment.tsx +193 -0
- package/src/components/Table/Table.module.scss +152 -0
- package/src/components/Table/index.tsx +266 -0
- package/src/components/Tabs/Tabs.fragment.tsx +155 -0
- package/src/components/Tabs/Tabs.module.scss +142 -0
- package/src/components/Tabs/index.tsx +142 -0
- package/src/components/Textarea/Textarea.fragment.tsx +171 -0
- package/src/components/Textarea/Textarea.module.scss +89 -0
- package/src/components/Textarea/index.tsx +128 -0
- package/src/components/Toast/Toast.fragment.tsx +210 -0
- package/src/components/Toast/Toast.module.scss +227 -0
- package/src/components/Toast/index.tsx +315 -0
- package/src/components/Toggle/Toggle.fragment.tsx +174 -0
- package/src/components/Toggle/Toggle.module.scss +103 -0
- package/src/components/Toggle/index.tsx +80 -0
- package/src/components/Tooltip/Tooltip.fragment.tsx +158 -0
- package/src/components/Tooltip/Tooltip.module.scss +82 -0
- package/src/components/Tooltip/index.tsx +135 -0
- package/src/index.ts +151 -0
- package/src/scss.d.ts +4 -0
- package/src/styles/globals.scss +17 -0
- package/src/tokens/_mixins.scss +93 -0
- package/src/tokens/_variables.scss +276 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { defineSegment } from '@fragments/core';
|
|
3
|
+
import { Separator } from './index.js';
|
|
4
|
+
|
|
5
|
+
export default defineSegment({
|
|
6
|
+
component: Separator,
|
|
7
|
+
|
|
8
|
+
meta: {
|
|
9
|
+
name: 'Separator',
|
|
10
|
+
description: 'Visual divider between content sections. Use to create clear visual boundaries and improve content organization.',
|
|
11
|
+
category: 'layout',
|
|
12
|
+
status: 'stable',
|
|
13
|
+
tags: ['separator', 'divider', 'hr', 'line', 'layout'],
|
|
14
|
+
since: '0.1.0',
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
usage: {
|
|
18
|
+
when: [
|
|
19
|
+
'Dividing content sections',
|
|
20
|
+
'Separating groups of related items',
|
|
21
|
+
'Creating visual breathing room',
|
|
22
|
+
'Labeled section breaks',
|
|
23
|
+
],
|
|
24
|
+
whenNot: [
|
|
25
|
+
'Creating grid layouts (use CSS Grid)',
|
|
26
|
+
'Decorative borders (use CSS)',
|
|
27
|
+
'Spacing alone is sufficient',
|
|
28
|
+
],
|
|
29
|
+
guidelines: [
|
|
30
|
+
'Use sparingly - too many separators create visual noise',
|
|
31
|
+
'Consider if spacing alone would work',
|
|
32
|
+
'Use soft variant for subtle separation',
|
|
33
|
+
'Labeled separators work well for major section breaks',
|
|
34
|
+
],
|
|
35
|
+
accessibility: [
|
|
36
|
+
'Uses role="separator" for semantic meaning',
|
|
37
|
+
'Decorative separators should be aria-hidden',
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
props: {
|
|
42
|
+
orientation: {
|
|
43
|
+
type: 'enum',
|
|
44
|
+
description: 'Direction of the separator',
|
|
45
|
+
values: ['horizontal', 'vertical'],
|
|
46
|
+
default: 'horizontal',
|
|
47
|
+
},
|
|
48
|
+
spacing: {
|
|
49
|
+
type: 'enum',
|
|
50
|
+
description: 'Margin around the separator',
|
|
51
|
+
values: ['none', 'sm', 'md', 'lg'],
|
|
52
|
+
default: 'none',
|
|
53
|
+
},
|
|
54
|
+
soft: {
|
|
55
|
+
type: 'boolean',
|
|
56
|
+
description: 'Softer, lighter appearance',
|
|
57
|
+
default: 'false',
|
|
58
|
+
},
|
|
59
|
+
label: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
description: 'Optional text label (horizontal only)',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
relations: [
|
|
66
|
+
{ component: 'Card', relationship: 'sibling', note: 'Cards provide stronger visual grouping' },
|
|
67
|
+
],
|
|
68
|
+
|
|
69
|
+
contract: {
|
|
70
|
+
propsSummary: [
|
|
71
|
+
'orientation: horizontal|vertical - direction',
|
|
72
|
+
'spacing: none|sm|md|lg - margin',
|
|
73
|
+
'soft: boolean - lighter appearance',
|
|
74
|
+
'label: string - centered label text',
|
|
75
|
+
],
|
|
76
|
+
scenarioTags: [
|
|
77
|
+
'layout.divider',
|
|
78
|
+
'layout.section',
|
|
79
|
+
],
|
|
80
|
+
a11yRules: ['A11Y_SEPARATOR_ROLE'],
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
variants: [
|
|
84
|
+
{
|
|
85
|
+
name: 'Default',
|
|
86
|
+
description: 'Basic horizontal separator',
|
|
87
|
+
render: () => (
|
|
88
|
+
<div style={{ width: '300px' }}>
|
|
89
|
+
<p>Content above</p>
|
|
90
|
+
<Separator spacing="md" />
|
|
91
|
+
<p>Content below</p>
|
|
92
|
+
</div>
|
|
93
|
+
),
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'With Label',
|
|
97
|
+
description: 'Labeled section divider',
|
|
98
|
+
render: () => (
|
|
99
|
+
<div style={{ width: '300px' }}>
|
|
100
|
+
<p>First section</p>
|
|
101
|
+
<Separator label="Or" spacing="md" />
|
|
102
|
+
<p>Second section</p>
|
|
103
|
+
</div>
|
|
104
|
+
),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'Soft',
|
|
108
|
+
description: 'Subtle separator',
|
|
109
|
+
render: () => (
|
|
110
|
+
<div style={{ width: '300px' }}>
|
|
111
|
+
<p>Content above</p>
|
|
112
|
+
<Separator soft spacing="md" />
|
|
113
|
+
<p>Content below</p>
|
|
114
|
+
</div>
|
|
115
|
+
),
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: 'Vertical',
|
|
119
|
+
description: 'Vertical separator between elements',
|
|
120
|
+
render: () => (
|
|
121
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '16px', height: '40px' }}>
|
|
122
|
+
<span>Item 1</span>
|
|
123
|
+
<Separator orientation="vertical" />
|
|
124
|
+
<span>Item 2</span>
|
|
125
|
+
<Separator orientation="vertical" />
|
|
126
|
+
<span>Item 3</span>
|
|
127
|
+
</div>
|
|
128
|
+
),
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: 'Spacing Options',
|
|
132
|
+
description: 'Different spacing sizes',
|
|
133
|
+
render: () => (
|
|
134
|
+
<div style={{ width: '300px' }}>
|
|
135
|
+
<p>No spacing</p>
|
|
136
|
+
<Separator spacing="none" />
|
|
137
|
+
<p>Small spacing</p>
|
|
138
|
+
<Separator spacing="sm" />
|
|
139
|
+
<p>Medium spacing</p>
|
|
140
|
+
<Separator spacing="md" />
|
|
141
|
+
<p>Large spacing</p>
|
|
142
|
+
<Separator spacing="lg" />
|
|
143
|
+
<p>End</p>
|
|
144
|
+
</div>
|
|
145
|
+
),
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
@use '../../tokens/variables' as *;
|
|
2
|
+
@use '../../tokens/mixins' as *;
|
|
3
|
+
|
|
4
|
+
.separator {
|
|
5
|
+
flex-shrink: 0;
|
|
6
|
+
background-color: var(--fui-border, $fui-border);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Horizontal (default)
|
|
10
|
+
.horizontal {
|
|
11
|
+
width: 100%;
|
|
12
|
+
height: 1px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Vertical
|
|
16
|
+
.vertical {
|
|
17
|
+
width: 1px;
|
|
18
|
+
height: auto;
|
|
19
|
+
align-self: stretch;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// With label
|
|
23
|
+
.withLabel {
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
gap: var(--fui-space-3, $fui-space-3);
|
|
27
|
+
background-color: transparent;
|
|
28
|
+
|
|
29
|
+
&::before,
|
|
30
|
+
&::after {
|
|
31
|
+
content: '';
|
|
32
|
+
flex: 1;
|
|
33
|
+
height: 1px;
|
|
34
|
+
background-color: var(--fui-border, $fui-border);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.label {
|
|
39
|
+
font-family: var(--fui-font-sans, $fui-font-sans);
|
|
40
|
+
font-size: var(--fui-font-size-xs, $fui-font-size-xs);
|
|
41
|
+
font-weight: var(--fui-font-weight-medium, $fui-font-weight-medium);
|
|
42
|
+
color: var(--fui-text-tertiary, $fui-text-tertiary);
|
|
43
|
+
text-transform: uppercase;
|
|
44
|
+
letter-spacing: 0.05em;
|
|
45
|
+
white-space: nowrap;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Spacing variants
|
|
49
|
+
.spacingNone {
|
|
50
|
+
margin: 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.spacingSm {
|
|
54
|
+
&.horizontal {
|
|
55
|
+
margin: var(--fui-space-2, $fui-space-2) 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
&.vertical {
|
|
59
|
+
margin: 0 var(--fui-space-2, $fui-space-2);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.spacingMd {
|
|
64
|
+
&.horizontal {
|
|
65
|
+
margin: var(--fui-space-4, $fui-space-4) 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
&.vertical {
|
|
69
|
+
margin: 0 var(--fui-space-4, $fui-space-4);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.spacingLg {
|
|
74
|
+
&.horizontal {
|
|
75
|
+
margin: var(--fui-space-6, $fui-space-6) 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
&.vertical {
|
|
79
|
+
margin: 0 var(--fui-space-6, $fui-space-6);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Soft variant (lighter color)
|
|
84
|
+
.soft {
|
|
85
|
+
background-color: var(--fui-border, $fui-border);
|
|
86
|
+
opacity: 0.5;
|
|
87
|
+
|
|
88
|
+
&.withLabel::before,
|
|
89
|
+
&.withLabel::after {
|
|
90
|
+
opacity: 0.5;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Separator as BaseSeparator } from '@base-ui/react/separator';
|
|
3
|
+
import styles from './Separator.module.scss';
|
|
4
|
+
// Import globals to ensure CSS variables are defined
|
|
5
|
+
import '../../styles/globals.scss';
|
|
6
|
+
|
|
7
|
+
// ============================================
|
|
8
|
+
// Types
|
|
9
|
+
// ============================================
|
|
10
|
+
|
|
11
|
+
export interface SeparatorProps {
|
|
12
|
+
/** Orientation of the separator */
|
|
13
|
+
orientation?: 'horizontal' | 'vertical';
|
|
14
|
+
/** Spacing around the separator */
|
|
15
|
+
spacing?: 'none' | 'sm' | 'md' | 'lg';
|
|
16
|
+
/** Softer visual appearance */
|
|
17
|
+
soft?: boolean;
|
|
18
|
+
/** Optional label text (creates a labeled divider) */
|
|
19
|
+
label?: string;
|
|
20
|
+
/** Additional class name */
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ============================================
|
|
25
|
+
// Spacing class map
|
|
26
|
+
// ============================================
|
|
27
|
+
|
|
28
|
+
const spacingClasses = {
|
|
29
|
+
none: styles.spacingNone,
|
|
30
|
+
sm: styles.spacingSm,
|
|
31
|
+
md: styles.spacingMd,
|
|
32
|
+
lg: styles.spacingLg,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ============================================
|
|
36
|
+
// Component
|
|
37
|
+
// ============================================
|
|
38
|
+
|
|
39
|
+
export const Separator = React.forwardRef<HTMLDivElement, SeparatorProps>(
|
|
40
|
+
function Separator(
|
|
41
|
+
{
|
|
42
|
+
orientation = 'horizontal',
|
|
43
|
+
spacing = 'none',
|
|
44
|
+
soft = false,
|
|
45
|
+
label,
|
|
46
|
+
className,
|
|
47
|
+
},
|
|
48
|
+
ref
|
|
49
|
+
) {
|
|
50
|
+
// Labeled separator (horizontal only)
|
|
51
|
+
if (label && orientation === 'horizontal') {
|
|
52
|
+
const classes = [
|
|
53
|
+
styles.separator,
|
|
54
|
+
styles.withLabel,
|
|
55
|
+
spacingClasses[spacing],
|
|
56
|
+
soft && styles.soft,
|
|
57
|
+
className,
|
|
58
|
+
].filter(Boolean).join(' ');
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div
|
|
62
|
+
ref={ref}
|
|
63
|
+
role="separator"
|
|
64
|
+
aria-orientation="horizontal"
|
|
65
|
+
className={classes}
|
|
66
|
+
>
|
|
67
|
+
<span className={styles.label}>{label}</span>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Standard separator
|
|
73
|
+
const classes = [
|
|
74
|
+
styles.separator,
|
|
75
|
+
orientation === 'horizontal' ? styles.horizontal : styles.vertical,
|
|
76
|
+
spacingClasses[spacing],
|
|
77
|
+
soft && styles.soft,
|
|
78
|
+
className,
|
|
79
|
+
].filter(Boolean).join(' ');
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<BaseSeparator
|
|
83
|
+
ref={ref}
|
|
84
|
+
orientation={orientation}
|
|
85
|
+
className={classes}
|
|
86
|
+
/>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
);
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { defineSegment } from '@fragments/core';
|
|
3
|
+
import { Skeleton } from './index.js';
|
|
4
|
+
|
|
5
|
+
export default defineSegment({
|
|
6
|
+
component: Skeleton,
|
|
7
|
+
|
|
8
|
+
meta: {
|
|
9
|
+
name: 'Skeleton',
|
|
10
|
+
description: 'Placeholder loading state for content',
|
|
11
|
+
category: 'feedback',
|
|
12
|
+
status: 'stable',
|
|
13
|
+
tags: ['loading', 'placeholder', 'skeleton', 'shimmer'],
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
usage: {
|
|
17
|
+
when: [
|
|
18
|
+
'Content is loading asynchronously',
|
|
19
|
+
'Preventing layout shift during data fetching',
|
|
20
|
+
'Providing visual feedback that content is coming',
|
|
21
|
+
'Improving perceived performance',
|
|
22
|
+
],
|
|
23
|
+
whenNot: [
|
|
24
|
+
'Short loading times (< 300ms)',
|
|
25
|
+
'When spinner is more appropriate',
|
|
26
|
+
'Background operations without visible impact',
|
|
27
|
+
],
|
|
28
|
+
guidelines: [
|
|
29
|
+
'Match skeleton shape to expected content',
|
|
30
|
+
'Use semantic variants (text, heading, avatar) for consistency',
|
|
31
|
+
'Maintain similar dimensions to loaded content',
|
|
32
|
+
'Avoid too many skeleton elements - simplify complex layouts',
|
|
33
|
+
],
|
|
34
|
+
accessibility: [
|
|
35
|
+
'Skeletons are decorative - use aria-hidden',
|
|
36
|
+
'Announce loading state separately if needed',
|
|
37
|
+
'Ensure sufficient contrast for the animation',
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
props: {
|
|
42
|
+
variant: {
|
|
43
|
+
type: 'enum',
|
|
44
|
+
values: ['text', 'heading', 'avatar', 'button', 'input', 'rect'],
|
|
45
|
+
default: 'rect',
|
|
46
|
+
description: 'Semantic variant that auto-sizes',
|
|
47
|
+
},
|
|
48
|
+
size: {
|
|
49
|
+
type: 'enum',
|
|
50
|
+
values: ['sm', 'md', 'lg'],
|
|
51
|
+
default: 'md',
|
|
52
|
+
description: 'Size for avatar/button variants',
|
|
53
|
+
},
|
|
54
|
+
width: {
|
|
55
|
+
type: 'union',
|
|
56
|
+
description: 'Custom width (string or number)',
|
|
57
|
+
},
|
|
58
|
+
height: {
|
|
59
|
+
type: 'union',
|
|
60
|
+
description: 'Custom height (string or number)',
|
|
61
|
+
},
|
|
62
|
+
fill: {
|
|
63
|
+
type: 'boolean',
|
|
64
|
+
default: false,
|
|
65
|
+
description: 'Fill parent container',
|
|
66
|
+
},
|
|
67
|
+
radius: {
|
|
68
|
+
type: 'enum',
|
|
69
|
+
values: ['none', 'sm', 'md', 'lg', 'full'],
|
|
70
|
+
description: 'Border radius override',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
relations: [
|
|
75
|
+
{
|
|
76
|
+
component: 'Progress',
|
|
77
|
+
relationship: 'alternative',
|
|
78
|
+
note: 'Use Progress for determinate loading',
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
|
|
82
|
+
contract: {
|
|
83
|
+
propsSummary: [
|
|
84
|
+
'variant: text|heading|avatar|button|input|rect',
|
|
85
|
+
'size: sm|md|lg (for avatar/button)',
|
|
86
|
+
'width/height: custom dimensions',
|
|
87
|
+
'fill: boolean - fill parent',
|
|
88
|
+
],
|
|
89
|
+
scenarioTags: [
|
|
90
|
+
'loading.content',
|
|
91
|
+
'loading.list',
|
|
92
|
+
'loading.card',
|
|
93
|
+
],
|
|
94
|
+
a11yRules: [],
|
|
95
|
+
bans: [],
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
variants: [
|
|
99
|
+
{
|
|
100
|
+
name: 'Default',
|
|
101
|
+
description: 'Basic rectangle skeleton',
|
|
102
|
+
render: () => <Skeleton width={200} height={20} />,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'Text Lines',
|
|
106
|
+
description: 'Multi-line text placeholder',
|
|
107
|
+
render: () => <Skeleton.Text lines={3} />,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'Semantic Variants',
|
|
111
|
+
description: 'Pre-configured shapes for common elements',
|
|
112
|
+
render: () => (
|
|
113
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
114
|
+
<Skeleton variant="heading" width={200} />
|
|
115
|
+
<Skeleton variant="text" width="100%" />
|
|
116
|
+
<Skeleton variant="text" width="80%" />
|
|
117
|
+
</div>
|
|
118
|
+
),
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: 'Avatar Skeleton',
|
|
122
|
+
description: 'Circular placeholder for avatars',
|
|
123
|
+
render: () => (
|
|
124
|
+
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
|
125
|
+
<Skeleton.Circle size="sm" />
|
|
126
|
+
<Skeleton.Circle size="md" />
|
|
127
|
+
<Skeleton.Circle size="lg" />
|
|
128
|
+
</div>
|
|
129
|
+
),
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'Card Skeleton',
|
|
133
|
+
description: 'Composed skeleton for a card layout',
|
|
134
|
+
render: () => (
|
|
135
|
+
<div style={{ width: 300, padding: 16, border: '1px solid #e5e7eb', borderRadius: 8 }}>
|
|
136
|
+
<Skeleton variant="rect" height={120} radius="md" />
|
|
137
|
+
<div style={{ marginTop: 12 }}>
|
|
138
|
+
<Skeleton variant="heading" width="60%" />
|
|
139
|
+
</div>
|
|
140
|
+
<div style={{ marginTop: 8 }}>
|
|
141
|
+
<Skeleton.Text lines={2} />
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
),
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
@use '../../tokens/variables' as *;
|
|
2
|
+
|
|
3
|
+
// ============================================
|
|
4
|
+
// Skeleton Component Styles
|
|
5
|
+
// ============================================
|
|
6
|
+
|
|
7
|
+
// Animation
|
|
8
|
+
@keyframes skeleton-pulse {
|
|
9
|
+
0%, 100% {
|
|
10
|
+
opacity: 1;
|
|
11
|
+
}
|
|
12
|
+
50% {
|
|
13
|
+
opacity: 0.5;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Base skeleton
|
|
18
|
+
.skeleton {
|
|
19
|
+
display: block;
|
|
20
|
+
background-color: var(--fui-bg-tertiary, #{$fui-bg-tertiary});
|
|
21
|
+
animation: skeleton-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
22
|
+
border-radius: var(--fui-radius-md, #{$fui-radius-md});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Static (no animation)
|
|
26
|
+
.static {
|
|
27
|
+
animation: none;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Fill parent
|
|
31
|
+
.fill {
|
|
32
|
+
width: 100%;
|
|
33
|
+
height: 100%;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ============================================
|
|
37
|
+
// Semantic Variants
|
|
38
|
+
// ============================================
|
|
39
|
+
|
|
40
|
+
// Text - single line of body text
|
|
41
|
+
.text {
|
|
42
|
+
height: 1em;
|
|
43
|
+
width: 100%;
|
|
44
|
+
border-radius: var(--fui-radius-sm, #{$fui-radius-sm});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Heading - taller for headings
|
|
48
|
+
.heading {
|
|
49
|
+
height: 1.5em;
|
|
50
|
+
width: 100%;
|
|
51
|
+
border-radius: var(--fui-radius-sm, #{$fui-radius-sm});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Avatar - circular profile placeholder
|
|
55
|
+
.avatar {
|
|
56
|
+
border-radius: var(--fui-radius-full, #{$fui-radius-full});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Avatar sizes
|
|
60
|
+
.avatar-sm {
|
|
61
|
+
width: 2rem; // 32px
|
|
62
|
+
height: 2rem;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.avatar-md {
|
|
66
|
+
width: 2.5rem; // 40px
|
|
67
|
+
height: 2.5rem;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.avatar-lg {
|
|
71
|
+
width: 3rem; // 48px
|
|
72
|
+
height: 3rem;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Button - matches button heights
|
|
76
|
+
.button {
|
|
77
|
+
border-radius: var(--fui-radius-md, #{$fui-radius-md});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.button-sm {
|
|
81
|
+
height: var(--fui-button-height-sm, #{$fui-button-height-sm});
|
|
82
|
+
width: 5rem; // 80px default width
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.button-md {
|
|
86
|
+
height: var(--fui-button-height-md, #{$fui-button-height-md});
|
|
87
|
+
width: 6rem; // 96px default width
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.button-lg {
|
|
91
|
+
height: var(--fui-button-height-lg, #{$fui-button-height-lg});
|
|
92
|
+
width: 7rem; // 112px default width
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Input - matches input field height
|
|
96
|
+
.input {
|
|
97
|
+
height: var(--fui-input-height, #{$fui-input-height});
|
|
98
|
+
width: 100%;
|
|
99
|
+
border-radius: var(--fui-radius-md, #{$fui-radius-md});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Rect - generic rectangle (no preset dimensions)
|
|
103
|
+
.rect {
|
|
104
|
+
// Uses explicit width/height from props or fill
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ============================================
|
|
108
|
+
// Border Radius Overrides
|
|
109
|
+
// ============================================
|
|
110
|
+
|
|
111
|
+
.radius-none {
|
|
112
|
+
border-radius: 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.radius-sm {
|
|
116
|
+
border-radius: var(--fui-radius-sm, #{$fui-radius-sm});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.radius-md {
|
|
120
|
+
border-radius: var(--fui-radius-md, #{$fui-radius-md});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.radius-lg {
|
|
124
|
+
border-radius: var(--fui-radius-lg, #{$fui-radius-lg});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.radius-full {
|
|
128
|
+
border-radius: var(--fui-radius-full, #{$fui-radius-full});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ============================================
|
|
132
|
+
// Skeleton.Text - Multi-line text container
|
|
133
|
+
// ============================================
|
|
134
|
+
|
|
135
|
+
.textContainer {
|
|
136
|
+
display: flex;
|
|
137
|
+
flex-direction: column;
|
|
138
|
+
width: 100%;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.gap-sm {
|
|
142
|
+
gap: var(--fui-space-2, #{$fui-space-2}); // 8px
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.gap-md {
|
|
146
|
+
gap: var(--fui-space-3, #{$fui-space-3}); // 12px
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.textLine {
|
|
150
|
+
height: 1em;
|
|
151
|
+
width: 100%;
|
|
152
|
+
background-color: var(--fui-bg-tertiary, #{$fui-bg-tertiary});
|
|
153
|
+
animation: skeleton-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
154
|
+
border-radius: var(--fui-radius-sm, #{$fui-radius-sm});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ============================================
|
|
158
|
+
// Dark mode adjustments
|
|
159
|
+
// ============================================
|
|
160
|
+
|
|
161
|
+
@media (prefers-color-scheme: dark) {
|
|
162
|
+
.skeleton,
|
|
163
|
+
.textLine {
|
|
164
|
+
background-color: var(--fui-bg-tertiary, #{$fui-dark-bg-tertiary});
|
|
165
|
+
}
|
|
166
|
+
}
|