@fragments-sdk/ui 0.4.0 → 0.6.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 (97) hide show
  1. package/README.md +98 -2
  2. package/fragments.json +1 -1
  3. package/package.json +4 -3
  4. package/src/components/Accordion/Accordion.fragment.tsx +1 -1
  5. package/src/components/Alert/Alert.fragment.tsx +1 -1
  6. package/src/components/AppShell/AppShell.fragment.tsx +4 -4
  7. package/src/components/Avatar/Avatar.fragment.tsx +2 -2
  8. package/src/components/Badge/Badge.fragment.tsx +2 -2
  9. package/src/components/Badge/Badge.module.scss +1 -1
  10. package/src/components/Box/Box.fragment.tsx +1 -1
  11. package/src/components/Button/Button.fragment.tsx +2 -2
  12. package/src/components/ButtonGroup/ButtonGroup.fragment.tsx +153 -0
  13. package/src/components/Card/Card.fragment.tsx +1 -1
  14. package/src/components/Chart/Chart.fragment.tsx +213 -0
  15. package/src/components/Chart/Chart.module.scss +123 -0
  16. package/src/components/Chart/index.tsx +267 -0
  17. package/src/components/Checkbox/Checkbox.fragment.tsx +1 -1
  18. package/src/components/CodeBlock/CodeBlock.fragment.tsx +265 -6
  19. package/src/components/CodeBlock/CodeBlock.module.scss +141 -3
  20. package/src/components/CodeBlock/index.tsx +250 -36
  21. package/src/components/Collapsible/Collapsible.fragment.tsx +199 -0
  22. package/src/components/Collapsible/Collapsible.module.scss +117 -0
  23. package/src/components/Collapsible/index.tsx +219 -0
  24. package/src/components/ColorPicker/ColorPicker.fragment.tsx +196 -0
  25. package/src/components/ColorPicker/ColorPicker.module.scss +33 -23
  26. package/src/components/ColorPicker/index.tsx +34 -12
  27. package/src/components/Combobox/Combobox.fragment.tsx +220 -0
  28. package/src/components/Combobox/Combobox.module.scss +268 -0
  29. package/src/components/Combobox/index.tsx +398 -0
  30. package/src/components/ConversationList/ConversationList.fragment.tsx +202 -0
  31. package/src/components/ConversationList/ConversationList.module.scss +160 -0
  32. package/src/components/ConversationList/index.tsx +254 -0
  33. package/src/components/Dialog/Dialog.fragment.tsx +3 -3
  34. package/src/components/EmptyState/EmptyState.fragment.tsx +2 -2
  35. package/src/components/Field/Field.fragment.tsx +3 -3
  36. package/src/components/Fieldset/Fieldset.fragment.tsx +7 -7
  37. package/src/components/Form/Form.fragment.tsx +11 -11
  38. package/src/components/Grid/Grid.fragment.tsx +1 -1
  39. package/src/components/Header/Header.fragment.tsx +4 -4
  40. package/src/components/Header/Header.module.scss +9 -10
  41. package/src/components/Icon/Icon.fragment.tsx +2 -2
  42. package/src/components/Image/Image.fragment.tsx +2 -2
  43. package/src/components/Input/Input.fragment.tsx +1 -1
  44. package/src/components/Input/Input.module.scss +2 -2
  45. package/src/components/Link/Link.fragment.tsx +1 -1
  46. package/src/components/List/List.fragment.tsx +2 -2
  47. package/src/components/Listbox/Listbox.fragment.tsx +1 -1
  48. package/src/components/Loading/Loading.fragment.tsx +153 -0
  49. package/src/components/Loading/Loading.module.scss +256 -0
  50. package/src/components/Loading/index.tsx +236 -0
  51. package/src/components/Menu/Menu.fragment.tsx +3 -3
  52. package/src/components/Message/Message.fragment.tsx +200 -0
  53. package/src/components/Message/Message.module.scss +224 -0
  54. package/src/components/Message/index.tsx +278 -0
  55. package/src/components/Popover/Popover.fragment.tsx +4 -4
  56. package/src/components/Progress/Progress.fragment.tsx +1 -1
  57. package/src/components/Prompt/Prompt.fragment.tsx +2 -2
  58. package/src/components/RadioGroup/RadioGroup.fragment.tsx +1 -1
  59. package/src/components/RadioGroup/RadioGroup.module.scss +7 -4
  60. package/src/components/Select/Select.fragment.tsx +1 -1
  61. package/src/components/Select/Select.module.scss +8 -0
  62. package/src/components/Select/index.tsx +85 -5
  63. package/src/components/Separator/Separator.fragment.tsx +1 -1
  64. package/src/components/Sidebar/Sidebar.fragment.tsx +2 -2
  65. package/src/components/Sidebar/Sidebar.module.scss +19 -0
  66. package/src/components/Sidebar/index.tsx +52 -11
  67. package/src/components/Skeleton/Skeleton.fragment.tsx +1 -1
  68. package/src/components/Slider/Slider.fragment.tsx +201 -0
  69. package/src/components/Stack/Stack.fragment.tsx +194 -0
  70. package/src/components/Table/Table.fragment.tsx +3 -3
  71. package/src/components/Tabs/Tabs.fragment.tsx +1 -1
  72. package/src/components/Tabs/Tabs.module.scss +2 -2
  73. package/src/components/Text/Text.fragment.tsx +188 -0
  74. package/src/components/Textarea/Textarea.fragment.tsx +1 -1
  75. package/src/components/Theme/Theme.fragment.tsx +2 -2
  76. package/src/components/Theme/ThemeToggle.module.scss +13 -13
  77. package/src/components/ThinkingIndicator/ThinkingIndicator.fragment.tsx +182 -0
  78. package/src/components/ThinkingIndicator/ThinkingIndicator.module.scss +226 -0
  79. package/src/components/ThinkingIndicator/index.tsx +258 -0
  80. package/src/components/Toast/Toast.fragment.tsx +1 -1
  81. package/src/components/Toggle/Toggle.fragment.tsx +1 -1
  82. package/src/components/ToggleGroup/ToggleGroup.fragment.tsx +207 -0
  83. package/src/components/Tooltip/Tooltip.fragment.tsx +3 -3
  84. package/src/components/VisuallyHidden/VisuallyHidden.fragment.tsx +2 -2
  85. package/src/index.ts +99 -3
  86. package/src/recipes/AIChat.recipe.ts +266 -0
  87. package/src/tokens/_computed.scss +212 -0
  88. package/src/tokens/_density.scss +171 -0
  89. package/src/tokens/_derive.scss +287 -0
  90. package/src/tokens/_index.scss +39 -1
  91. package/src/tokens/_mixins.scss +41 -0
  92. package/src/tokens/_palettes.scss +185 -0
  93. package/src/tokens/_radius.scss +107 -0
  94. package/src/tokens/_seeds.scss +59 -0
  95. package/src/tokens/_variables.scss +171 -130
  96. package/src/components/ColorChip/ColorChip.module.scss +0 -165
  97. package/src/components/ColorChip/index.tsx +0 -157
@@ -1,16 +1,16 @@
1
1
  import React from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { Form } from './index.js';
4
- import { Field } from '../Field/index.js';
5
- import { Fieldset } from '../Fieldset/index.js';
6
- import { Input } from '../Input/index.js';
7
- import { Textarea } from '../Textarea/index.js';
8
- import { Select } from '../Select/index.js';
9
- import { Checkbox } from '../Checkbox/index.js';
10
- import { RadioGroup } from '../RadioGroup/index.js';
11
- import { Toggle } from '../Toggle/index.js';
12
- import { Button } from '../Button/index.js';
13
- import { Grid } from '../Grid/index.js';
3
+ import { Form } from '.';
4
+ import { Field } from '../Field';
5
+ import { Fieldset } from '../Fieldset';
6
+ import { Input } from '../Input';
7
+ import { Textarea } from '../Textarea';
8
+ import { Select } from '../Select';
9
+ import { Checkbox } from '../Checkbox';
10
+ import { RadioGroup } from '../RadioGroup';
11
+ import { Toggle } from '../Toggle';
12
+ import { Button } from '../Button';
13
+ import { Grid } from '../Grid';
14
14
 
15
15
  export default defineSegment({
16
16
  component: Form,
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { Grid } from './index.js';
3
+ import { Grid } from '.';
4
4
 
5
5
  export default defineSegment({
6
6
  component: Grid,
@@ -1,9 +1,9 @@
1
1
  import React from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { Header } from './index.js';
4
- import { ThemeToggle, ThemeProvider } from '../Theme/index.js';
5
- import { Button } from '../Button/index.js';
6
- import { Input } from '../Input/index.js';
3
+ import { Header } from '.';
4
+ import { ThemeToggle, ThemeProvider } from '../Theme';
5
+ import { Button } from '../Button';
6
+ import { Input } from '../Input';
7
7
 
8
8
  function SearchIcon() {
9
9
  return (
@@ -8,15 +8,15 @@
8
8
  .header {
9
9
  display: flex;
10
10
  align-items: center;
11
- height: var(--header-height, 56px);
12
- min-height: var(--header-height, 56px);
11
+ height: var(--header-height, var(--fui-appshell-header-height, $fui-appshell-header-height));
12
+ min-height: var(--header-height, var(--fui-appshell-header-height, $fui-appshell-header-height));
13
13
  background-color: var(--fui-bg-primary, $fui-bg-primary);
14
14
  padding: 0 var(--fui-space-4, $fui-space-4);
15
- z-index: var(--fui-header-z-index, 40);
15
+ z-index: var(--fui-header-z-index, $fui-header-z-index);
16
16
  }
17
17
 
18
18
  .fixed {
19
- position: fixed;
19
+ position: sticky;
20
20
  top: 0;
21
21
  left: 0;
22
22
  right: 0;
@@ -55,7 +55,6 @@
55
55
 
56
56
  img,
57
57
  svg {
58
- height: 32px;
59
58
  width: auto;
60
59
  }
61
60
  }
@@ -94,7 +93,7 @@
94
93
  color: var(--fui-text-secondary, $fui-text-secondary);
95
94
  text-decoration: none;
96
95
  white-space: nowrap;
97
- min-height: 36px;
96
+ min-height: var(--fui-button-height-md, $fui-button-height-md);
98
97
 
99
98
  &:hover {
100
99
  background-color: var(--fui-bg-hover, $fui-bg-hover);
@@ -151,8 +150,8 @@
151
150
  display: flex;
152
151
  align-items: center;
153
152
  justify-content: center;
154
- width: 44px;
155
- height: 44px;
153
+ width: var(--fui-touch-lg, $fui-touch-lg);
154
+ height: var(--fui-touch-lg, $fui-touch-lg);
156
155
  border-radius: var(--fui-radius-md, $fui-radius-md);
157
156
  color: var(--fui-text-primary, $fui-text-primary);
158
157
  flex-shrink: 0;
@@ -162,8 +161,8 @@
162
161
  }
163
162
 
164
163
  svg {
165
- width: 24px;
166
- height: 24px;
164
+ width: var(--fui-icon-xl, $fui-icon-xl);
165
+ height: var(--fui-icon-xl, $fui-icon-xl);
167
166
  }
168
167
  }
169
168
 
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { Icon } from './index.js';
3
+ import { Icon } from '.';
4
4
  import { Heart, Star, Check, Warning, Info } from '@phosphor-icons/react';
5
5
 
6
6
  export default defineSegment({
@@ -9,7 +9,7 @@ export default defineSegment({
9
9
  meta: {
10
10
  name: 'Icon',
11
11
  description: 'Wrapper for Phosphor icons with consistent sizing and semantic colors. Provides standardized icon rendering across the design system.',
12
- category: 'data-display',
12
+ category: 'display',
13
13
  status: 'stable',
14
14
  tags: ['icon', 'phosphor', 'visual', 'symbol', 'graphic'],
15
15
  since: '0.1.0',
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { Image } from './index.js';
3
+ import { Image } from '.';
4
4
 
5
5
  export default defineSegment({
6
6
  component: Image,
@@ -8,7 +8,7 @@ export default defineSegment({
8
8
  meta: {
9
9
  name: 'Image',
10
10
  description: 'Responsive image component with aspect ratio control, loading states, and error fallbacks. Handles image display with consistent styling.',
11
- category: 'data-display',
11
+ category: 'display',
12
12
  status: 'stable',
13
13
  tags: ['image', 'media', 'photo', 'picture', 'visual'],
14
14
  since: '0.1.0',
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { Input } from './index.js';
3
+ import { Input } from '.';
4
4
 
5
5
  export default defineSegment({
6
6
  component: Input,
@@ -43,7 +43,7 @@
43
43
 
44
44
  // Size variants
45
45
  .sm {
46
- height: var(--fui-button-height-sm, $fui-button-height-sm);
46
+ height: var(--fui-input-height-sm, $fui-input-height-sm);
47
47
  padding: 0 var(--fui-space-2, $fui-space-2);
48
48
  font-size: var(--fui-font-size-xs, $fui-font-size-xs);
49
49
  }
@@ -55,7 +55,7 @@
55
55
  }
56
56
 
57
57
  .lg {
58
- height: var(--fui-button-height-lg, $fui-button-height-lg);
58
+ height: var(--fui-input-height-lg, $fui-input-height-lg);
59
59
  padding: 0 var(--fui-space-4, $fui-space-4);
60
60
  font-size: var(--fui-font-size-base, $fui-font-size-base);
61
61
  }
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { Link } from './index.js';
3
+ import { Link } from '.';
4
4
 
5
5
  export default defineSegment({
6
6
  component: Link,
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { List } from './index.js';
3
+ import { List } from '.';
4
4
  import { Check, Star, ArrowRight } from '@phosphor-icons/react';
5
5
 
6
6
  export default defineSegment({
@@ -9,7 +9,7 @@ export default defineSegment({
9
9
  meta: {
10
10
  name: 'List',
11
11
  description: 'Compound component for rendering ordered or unordered lists with consistent styling. Supports bullet, numbered, and icon-prefixed items.',
12
- category: 'layout',
12
+ category: 'display',
13
13
  status: 'stable',
14
14
  tags: ['list', 'items', 'bullet', 'ordered', 'unordered'],
15
15
  since: '0.1.0',
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { Listbox } from './index.js';
3
+ import { Listbox } from '.';
4
4
 
5
5
  export default defineSegment({
6
6
  component: Listbox,
@@ -0,0 +1,153 @@
1
+ import React from 'react';
2
+ import { defineSegment } from '@fragments/core';
3
+ import { Loading } from '.';
4
+
5
+ export default defineSegment({
6
+ component: Loading,
7
+
8
+ meta: {
9
+ name: 'Loading',
10
+ description: 'Versatile loading indicator with multiple variants for showing progress or waiting states',
11
+ category: 'feedback',
12
+ status: 'stable',
13
+ tags: ['loading', 'spinner', 'progress', 'feedback', 'indicator', 'async'],
14
+ },
15
+
16
+ usage: {
17
+ when: [
18
+ 'Indicating content is being fetched or processed',
19
+ 'Showing a pending state while waiting for an async operation',
20
+ 'Displaying loading state for buttons, forms, or page sections',
21
+ 'Full-screen loading during initial app/page load',
22
+ ],
23
+ whenNot: [
24
+ 'For showing determinate progress - use Progress component instead',
25
+ 'For showing skeleton placeholders - use Skeleton component instead',
26
+ 'For AI-specific thinking states - use ThinkingIndicator instead',
27
+ ],
28
+ guidelines: [
29
+ 'Use spinner variant for general loading states',
30
+ 'Use dots variant for chat/messaging contexts',
31
+ 'Use pulse variant for subtle, ambient loading',
32
+ 'Always provide a meaningful label for screen readers',
33
+ 'Consider using Loading.Screen for initial page loads',
34
+ 'Use Loading.Inline when loading indicator should flow with text',
35
+ ],
36
+ accessibility: [
37
+ 'Component uses role="status" and aria-live="polite"',
38
+ 'Always provide descriptive label prop for screen readers',
39
+ 'Animations respect prefers-reduced-motion preference',
40
+ ],
41
+ },
42
+
43
+ props: {
44
+ size: {
45
+ type: 'enum',
46
+ values: ['sm', 'md', 'lg', 'xl'],
47
+ default: 'md',
48
+ description: 'Size of the loading indicator',
49
+ },
50
+ variant: {
51
+ type: 'enum',
52
+ values: ['spinner', 'dots', 'pulse'],
53
+ default: 'spinner',
54
+ description: 'Visual style of the loading animation',
55
+ },
56
+ label: {
57
+ type: 'string',
58
+ default: 'Loading...',
59
+ description: 'Accessible label for screen readers',
60
+ },
61
+ centered: {
62
+ type: 'boolean',
63
+ default: false,
64
+ description: 'Whether to center the loading indicator in its container',
65
+ },
66
+ fill: {
67
+ type: 'boolean',
68
+ default: false,
69
+ description: 'Whether to fill the parent container',
70
+ },
71
+ overlay: {
72
+ type: 'boolean',
73
+ default: false,
74
+ description: 'Whether to show with a backdrop overlay',
75
+ },
76
+ color: {
77
+ type: 'enum',
78
+ values: ['accent', 'current', 'muted'],
79
+ default: 'accent',
80
+ description: 'Color variant - accent uses theme color, current inherits text color',
81
+ },
82
+ },
83
+
84
+ variants: [
85
+ {
86
+ name: 'Default',
87
+ description: 'Default spinner loading indicator',
88
+ render: () => <Loading />,
89
+ },
90
+ {
91
+ name: 'Sizes',
92
+ description: 'Loading indicators in different sizes',
93
+ render: () => (
94
+ <div style={{ display: 'flex', alignItems: 'center', gap: '24px' }}>
95
+ <Loading size="sm" />
96
+ <Loading size="md" />
97
+ <Loading size="lg" />
98
+ <Loading size="xl" />
99
+ </div>
100
+ ),
101
+ },
102
+ {
103
+ name: 'Dots',
104
+ description: 'Bouncing dots animation',
105
+ render: () => <Loading variant="dots" />,
106
+ },
107
+ {
108
+ name: 'Pulse',
109
+ description: 'Pulsing circle animation',
110
+ render: () => <Loading variant="pulse" />,
111
+ },
112
+ {
113
+ name: 'Colors',
114
+ description: 'Different color variants',
115
+ render: () => (
116
+ <div style={{ display: 'flex', alignItems: 'center', gap: '24px' }}>
117
+ <Loading color="accent" />
118
+ <Loading color="muted" />
119
+ <span style={{ color: '#3b82f6' }}>
120
+ <Loading color="current" />
121
+ </span>
122
+ </div>
123
+ ),
124
+ },
125
+ {
126
+ name: 'Inline',
127
+ description: 'Inline loading indicator that flows with text',
128
+ render: () => (
129
+ <p style={{ margin: 0 }}>
130
+ Processing your request <Loading.Inline /> please wait...
131
+ </p>
132
+ ),
133
+ },
134
+ {
135
+ name: 'Centered',
136
+ description: 'Centered in container',
137
+ render: () => (
138
+ <div style={{ width: '200px', height: '100px', border: '1px dashed #ccc', borderRadius: '8px' }}>
139
+ <Loading centered fill />
140
+ </div>
141
+ ),
142
+ },
143
+ {
144
+ name: 'Screen',
145
+ description: 'Full-screen loading state with optional label',
146
+ render: () => (
147
+ <div style={{ position: 'relative', width: '300px', height: '200px', border: '1px solid #ccc', borderRadius: '8px', overflow: 'hidden' }}>
148
+ <Loading.Screen size="lg" label="Loading application..." showLabel />
149
+ </div>
150
+ ),
151
+ },
152
+ ],
153
+ });
@@ -0,0 +1,256 @@
1
+ @use '../../tokens/variables' as *;
2
+ @use '../../tokens/mixins' as *;
3
+
4
+ // ============================================
5
+ // Loading Base
6
+ // ============================================
7
+
8
+ .loading {
9
+ display: inline-flex;
10
+ align-items: center;
11
+ justify-content: center;
12
+ flex-shrink: 0;
13
+ }
14
+
15
+ // ============================================
16
+ // Sizes
17
+ // ============================================
18
+
19
+ .sm {
20
+ --loading-size: #{$fui-icon-sm}; // 14px
21
+ }
22
+
23
+ .md {
24
+ --loading-size: #{$fui-icon-lg}; // 20px
25
+ }
26
+
27
+ .lg {
28
+ --loading-size: #{$fui-icon-xl}; // 24px
29
+ }
30
+
31
+ .xl {
32
+ --loading-size: 2.286rem; // 32px
33
+ }
34
+
35
+ // ============================================
36
+ // Colors
37
+ // ============================================
38
+
39
+ .color-accent {
40
+ color: var(--fui-color-accent, $fui-color-accent);
41
+ }
42
+
43
+ .color-current {
44
+ color: currentColor;
45
+ }
46
+
47
+ .color-muted {
48
+ color: var(--fui-text-tertiary, $fui-text-tertiary);
49
+ }
50
+
51
+ // ============================================
52
+ // Layout Modifiers
53
+ // ============================================
54
+
55
+ .centered {
56
+ display: flex;
57
+ align-items: center;
58
+ justify-content: center;
59
+ }
60
+
61
+ .fill {
62
+ width: 100%;
63
+ height: 100%;
64
+ display: flex;
65
+ align-items: center;
66
+ justify-content: center;
67
+ }
68
+
69
+ // ============================================
70
+ // Spinner Variant
71
+ // ============================================
72
+
73
+ .spinnerIcon {
74
+ width: var(--loading-size);
75
+ height: var(--loading-size);
76
+ animation: spin 0.8s linear infinite;
77
+
78
+ @media (prefers-reduced-motion: reduce) {
79
+ animation: none;
80
+ opacity: 0.7;
81
+ }
82
+ }
83
+
84
+ @keyframes spin {
85
+ from {
86
+ transform: rotate(0deg);
87
+ }
88
+ to {
89
+ transform: rotate(360deg);
90
+ }
91
+ }
92
+
93
+ // ============================================
94
+ // Dots Variant
95
+ // ============================================
96
+
97
+ .dots {
98
+ display: flex;
99
+ align-items: center;
100
+ gap: calc(var(--loading-size) * 0.3);
101
+ }
102
+
103
+ .dot {
104
+ width: calc(var(--loading-size) * 0.35);
105
+ height: calc(var(--loading-size) * 0.35);
106
+ border-radius: var(--fui-radius-full, $fui-radius-full);
107
+ background-color: currentColor;
108
+ animation: bounce 1.4s ease-in-out infinite;
109
+
110
+ &:nth-child(1) {
111
+ animation-delay: 0s;
112
+ }
113
+
114
+ &:nth-child(2) {
115
+ animation-delay: 0.2s;
116
+ }
117
+
118
+ &:nth-child(3) {
119
+ animation-delay: 0.4s;
120
+ }
121
+
122
+ @media (prefers-reduced-motion: reduce) {
123
+ animation: none;
124
+ opacity: 0.6;
125
+ }
126
+ }
127
+
128
+ @keyframes bounce {
129
+ 0%,
130
+ 60%,
131
+ 100% {
132
+ transform: translateY(0);
133
+ opacity: 0.4;
134
+ }
135
+ 30% {
136
+ transform: translateY(-30%);
137
+ opacity: 1;
138
+ }
139
+ }
140
+
141
+ // ============================================
142
+ // Pulse Variant
143
+ // ============================================
144
+
145
+ .pulse {
146
+ position: relative;
147
+ width: var(--loading-size);
148
+ height: var(--loading-size);
149
+ display: flex;
150
+ align-items: center;
151
+ justify-content: center;
152
+ }
153
+
154
+ .pulseRing {
155
+ position: absolute;
156
+ width: 100%;
157
+ height: 100%;
158
+ border-radius: var(--fui-radius-full, $fui-radius-full);
159
+ background-color: currentColor;
160
+ opacity: 0.3;
161
+ animation: pulseRing 1.5s ease-out infinite;
162
+
163
+ @media (prefers-reduced-motion: reduce) {
164
+ animation: none;
165
+ opacity: 0.2;
166
+ }
167
+ }
168
+
169
+ .pulseDot {
170
+ width: 50%;
171
+ height: 50%;
172
+ border-radius: var(--fui-radius-full, $fui-radius-full);
173
+ background-color: currentColor;
174
+ z-index: 1;
175
+ }
176
+
177
+ @keyframes pulseRing {
178
+ 0% {
179
+ transform: scale(0.5);
180
+ opacity: 0.5;
181
+ }
182
+ 100% {
183
+ transform: scale(1.5);
184
+ opacity: 0;
185
+ }
186
+ }
187
+
188
+ // ============================================
189
+ // Overlay
190
+ // ============================================
191
+
192
+ .overlayBackdrop {
193
+ position: absolute;
194
+ inset: 0;
195
+ display: flex;
196
+ align-items: center;
197
+ justify-content: center;
198
+ background-color: var(--fui-backdrop, $fui-backdrop);
199
+ z-index: 50;
200
+ }
201
+
202
+ .overlay {
203
+ // Additional styling if needed when in overlay mode
204
+ }
205
+
206
+ // ============================================
207
+ // Inline Variant
208
+ // ============================================
209
+
210
+ .inline {
211
+ display: inline-flex;
212
+ align-items: center;
213
+ vertical-align: middle;
214
+ }
215
+
216
+ .inline-sm {
217
+ --inline-size: 0.875em; // Relative to surrounding text
218
+ }
219
+
220
+ .inline-md {
221
+ --inline-size: 1.125em;
222
+ }
223
+
224
+ .inlineSpinner {
225
+ width: var(--inline-size);
226
+ height: var(--inline-size);
227
+ animation: spin 0.8s linear infinite;
228
+ color: currentColor;
229
+
230
+ @media (prefers-reduced-motion: reduce) {
231
+ animation: none;
232
+ opacity: 0.7;
233
+ }
234
+ }
235
+
236
+ // ============================================
237
+ // Screen (Full-screen loading)
238
+ // ============================================
239
+
240
+ .screen {
241
+ position: fixed;
242
+ inset: 0;
243
+ display: flex;
244
+ flex-direction: column;
245
+ align-items: center;
246
+ justify-content: center;
247
+ gap: var(--fui-space-3, $fui-space-3);
248
+ background-color: var(--fui-bg-primary, $fui-bg-primary);
249
+ z-index: 100;
250
+ }
251
+
252
+ .screenLabel {
253
+ @include text-base;
254
+ font-size: var(--fui-font-size-sm, $fui-font-size-sm);
255
+ color: var(--fui-text-secondary, $fui-text-secondary);
256
+ }