@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.
Files changed (73) hide show
  1. package/package.json +44 -0
  2. package/src/brand.ts +15 -0
  3. package/src/components/Alert/Alert.fragment.tsx +163 -0
  4. package/src/components/Alert/Alert.module.scss +116 -0
  5. package/src/components/Alert/index.tsx +95 -0
  6. package/src/components/Avatar/Avatar.fragment.tsx +147 -0
  7. package/src/components/Avatar/Avatar.module.scss +136 -0
  8. package/src/components/Avatar/index.tsx +177 -0
  9. package/src/components/Badge/Badge.fragment.tsx +151 -0
  10. package/src/components/Badge/Badge.module.scss +87 -0
  11. package/src/components/Badge/index.tsx +55 -0
  12. package/src/components/Button/Button.fragment.tsx +159 -0
  13. package/src/components/Button/Button.module.scss +97 -0
  14. package/src/components/Button/index.tsx +51 -0
  15. package/src/components/Card/Card.fragment.tsx +156 -0
  16. package/src/components/Card/Card.module.scss +86 -0
  17. package/src/components/Card/index.tsx +79 -0
  18. package/src/components/Checkbox/Checkbox.fragment.tsx +166 -0
  19. package/src/components/Checkbox/Checkbox.module.scss +144 -0
  20. package/src/components/Checkbox/index.tsx +166 -0
  21. package/src/components/Dialog/Dialog.fragment.tsx +179 -0
  22. package/src/components/Dialog/Dialog.module.scss +158 -0
  23. package/src/components/Dialog/index.tsx +230 -0
  24. package/src/components/EmptyState/EmptyState.fragment.tsx +222 -0
  25. package/src/components/EmptyState/EmptyState.module.scss +120 -0
  26. package/src/components/EmptyState/index.tsx +80 -0
  27. package/src/components/Input/Input.fragment.tsx +174 -0
  28. package/src/components/Input/Input.module.scss +64 -0
  29. package/src/components/Input/index.tsx +76 -0
  30. package/src/components/Menu/Menu.fragment.tsx +168 -0
  31. package/src/components/Menu/Menu.module.scss +190 -0
  32. package/src/components/Menu/index.tsx +318 -0
  33. package/src/components/Popover/Popover.fragment.tsx +178 -0
  34. package/src/components/Popover/Popover.module.scss +165 -0
  35. package/src/components/Popover/index.tsx +229 -0
  36. package/src/components/Progress/Progress.fragment.tsx +142 -0
  37. package/src/components/Progress/Progress.module.scss +185 -0
  38. package/src/components/Progress/index.tsx +196 -0
  39. package/src/components/RadioGroup/RadioGroup.fragment.tsx +188 -0
  40. package/src/components/RadioGroup/RadioGroup.module.scss +155 -0
  41. package/src/components/RadioGroup/index.tsx +166 -0
  42. package/src/components/Select/Select.fragment.tsx +173 -0
  43. package/src/components/Select/Select.module.scss +187 -0
  44. package/src/components/Select/index.tsx +233 -0
  45. package/src/components/Separator/Separator.fragment.tsx +148 -0
  46. package/src/components/Separator/Separator.module.scss +92 -0
  47. package/src/components/Separator/index.tsx +89 -0
  48. package/src/components/Skeleton/Skeleton.fragment.tsx +147 -0
  49. package/src/components/Skeleton/Skeleton.module.scss +166 -0
  50. package/src/components/Skeleton/index.tsx +185 -0
  51. package/src/components/Table/Table.fragment.tsx +193 -0
  52. package/src/components/Table/Table.module.scss +152 -0
  53. package/src/components/Table/index.tsx +266 -0
  54. package/src/components/Tabs/Tabs.fragment.tsx +155 -0
  55. package/src/components/Tabs/Tabs.module.scss +142 -0
  56. package/src/components/Tabs/index.tsx +142 -0
  57. package/src/components/Textarea/Textarea.fragment.tsx +171 -0
  58. package/src/components/Textarea/Textarea.module.scss +89 -0
  59. package/src/components/Textarea/index.tsx +128 -0
  60. package/src/components/Toast/Toast.fragment.tsx +210 -0
  61. package/src/components/Toast/Toast.module.scss +227 -0
  62. package/src/components/Toast/index.tsx +315 -0
  63. package/src/components/Toggle/Toggle.fragment.tsx +174 -0
  64. package/src/components/Toggle/Toggle.module.scss +103 -0
  65. package/src/components/Toggle/index.tsx +80 -0
  66. package/src/components/Tooltip/Tooltip.fragment.tsx +158 -0
  67. package/src/components/Tooltip/Tooltip.module.scss +82 -0
  68. package/src/components/Tooltip/index.tsx +135 -0
  69. package/src/index.ts +151 -0
  70. package/src/scss.d.ts +4 -0
  71. package/src/styles/globals.scss +17 -0
  72. package/src/tokens/_mixins.scss +93 -0
  73. 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
+ }