@fragments-sdk/ui 0.4.0 → 0.5.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 (94) hide show
  1. package/README.md +98 -2
  2. package/fragments.json +1 -1
  3. package/package.json +3 -2
  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/ConversationList/ConversationList.fragment.tsx +202 -0
  28. package/src/components/ConversationList/ConversationList.module.scss +160 -0
  29. package/src/components/ConversationList/index.tsx +254 -0
  30. package/src/components/Dialog/Dialog.fragment.tsx +3 -3
  31. package/src/components/EmptyState/EmptyState.fragment.tsx +2 -2
  32. package/src/components/Field/Field.fragment.tsx +3 -3
  33. package/src/components/Fieldset/Fieldset.fragment.tsx +7 -7
  34. package/src/components/Form/Form.fragment.tsx +11 -11
  35. package/src/components/Grid/Grid.fragment.tsx +1 -1
  36. package/src/components/Header/Header.fragment.tsx +4 -4
  37. package/src/components/Header/Header.module.scss +9 -10
  38. package/src/components/Icon/Icon.fragment.tsx +2 -2
  39. package/src/components/Image/Image.fragment.tsx +2 -2
  40. package/src/components/Input/Input.fragment.tsx +1 -1
  41. package/src/components/Input/Input.module.scss +2 -2
  42. package/src/components/Link/Link.fragment.tsx +1 -1
  43. package/src/components/List/List.fragment.tsx +2 -2
  44. package/src/components/Listbox/Listbox.fragment.tsx +1 -1
  45. package/src/components/Loading/Loading.fragment.tsx +153 -0
  46. package/src/components/Loading/Loading.module.scss +256 -0
  47. package/src/components/Loading/index.tsx +236 -0
  48. package/src/components/Menu/Menu.fragment.tsx +3 -3
  49. package/src/components/Message/Message.fragment.tsx +200 -0
  50. package/src/components/Message/Message.module.scss +224 -0
  51. package/src/components/Message/index.tsx +278 -0
  52. package/src/components/Popover/Popover.fragment.tsx +4 -4
  53. package/src/components/Progress/Progress.fragment.tsx +1 -1
  54. package/src/components/Prompt/Prompt.fragment.tsx +2 -2
  55. package/src/components/RadioGroup/RadioGroup.fragment.tsx +1 -1
  56. package/src/components/RadioGroup/RadioGroup.module.scss +7 -4
  57. package/src/components/Select/Select.fragment.tsx +1 -1
  58. package/src/components/Select/Select.module.scss +8 -0
  59. package/src/components/Select/index.tsx +85 -5
  60. package/src/components/Separator/Separator.fragment.tsx +1 -1
  61. package/src/components/Sidebar/Sidebar.fragment.tsx +2 -2
  62. package/src/components/Sidebar/Sidebar.module.scss +19 -0
  63. package/src/components/Sidebar/index.tsx +52 -11
  64. package/src/components/Skeleton/Skeleton.fragment.tsx +1 -1
  65. package/src/components/Slider/Slider.fragment.tsx +201 -0
  66. package/src/components/Stack/Stack.fragment.tsx +194 -0
  67. package/src/components/Table/Table.fragment.tsx +3 -3
  68. package/src/components/Tabs/Tabs.fragment.tsx +1 -1
  69. package/src/components/Tabs/Tabs.module.scss +2 -2
  70. package/src/components/Text/Text.fragment.tsx +188 -0
  71. package/src/components/Textarea/Textarea.fragment.tsx +1 -1
  72. package/src/components/Theme/Theme.fragment.tsx +2 -2
  73. package/src/components/Theme/ThemeToggle.module.scss +13 -13
  74. package/src/components/ThinkingIndicator/ThinkingIndicator.fragment.tsx +182 -0
  75. package/src/components/ThinkingIndicator/ThinkingIndicator.module.scss +226 -0
  76. package/src/components/ThinkingIndicator/index.tsx +258 -0
  77. package/src/components/Toast/Toast.fragment.tsx +1 -1
  78. package/src/components/Toggle/Toggle.fragment.tsx +1 -1
  79. package/src/components/ToggleGroup/ToggleGroup.fragment.tsx +207 -0
  80. package/src/components/Tooltip/Tooltip.fragment.tsx +3 -3
  81. package/src/components/VisuallyHidden/VisuallyHidden.fragment.tsx +2 -2
  82. package/src/index.ts +86 -3
  83. package/src/recipes/AIChat.recipe.ts +266 -0
  84. package/src/tokens/_computed.scss +212 -0
  85. package/src/tokens/_density.scss +171 -0
  86. package/src/tokens/_derive.scss +287 -0
  87. package/src/tokens/_index.scss +39 -1
  88. package/src/tokens/_mixins.scss +41 -0
  89. package/src/tokens/_palettes.scss +185 -0
  90. package/src/tokens/_radius.scss +107 -0
  91. package/src/tokens/_seeds.scss +59 -0
  92. package/src/tokens/_variables.scss +171 -130
  93. package/src/components/ColorChip/ColorChip.module.scss +0 -165
  94. package/src/components/ColorChip/index.tsx +0 -157
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { Progress, CircularProgress } from './index.js';
3
+ import { Progress, CircularProgress } from '.';
4
4
 
5
5
  export default defineSegment({
6
6
  component: Progress,
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { Prompt } from './index.js';
3
+ import { Prompt } from '.';
4
4
 
5
5
  export default defineSegment({
6
6
  component: Prompt,
@@ -8,7 +8,7 @@ export default defineSegment({
8
8
  meta: {
9
9
  name: 'Prompt',
10
10
  description: 'Multi-line input with toolbar for AI/chat interfaces',
11
- category: 'forms',
11
+ category: 'ai',
12
12
  status: 'stable',
13
13
  tags: ['prompt', 'chat', 'ai', 'input', 'textarea', 'form'],
14
14
  },
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { RadioGroup } from './index.js';
3
+ import { RadioGroup } from '.';
4
4
 
5
5
  export default defineSegment({
6
6
  component: RadioGroup,
@@ -89,23 +89,26 @@
89
89
  margin-top: 0;
90
90
  }
91
91
 
92
- // The indicator dot
92
+ // The indicator dot - use absolute positioning for perfect centering
93
93
  .indicator {
94
+ position: absolute;
95
+ top: 50%;
96
+ left: 50%;
94
97
  width: 0.5rem;
95
98
  height: 0.5rem;
96
99
  background-color: var(--fui-color-accent, $fui-color-accent);
97
100
  border-radius: var(--fui-radius-full, $fui-radius-full);
98
101
 
99
- // Animation
102
+ // Animation - use translate for centering combined with scale
100
103
  opacity: 0;
101
- transform: scale(0);
104
+ transform: translate(-50%, -50%) scale(0);
102
105
  transition:
103
106
  opacity var(--fui-transition-fast, $fui-transition-fast),
104
107
  transform var(--fui-transition-fast, $fui-transition-fast);
105
108
 
106
109
  [data-checked] > & {
107
110
  opacity: 1;
108
- transform: scale(1);
111
+ transform: translate(-50%, -50%) scale(1);
109
112
  }
110
113
  }
111
114
 
@@ -1,6 +1,6 @@
1
1
  import React, { useState } from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { Select } from './index.js';
3
+ import { Select } from '.';
4
4
 
5
5
  // Stateful wrapper for interactive demos
6
6
  function StatefulSelect(props: React.ComponentProps<typeof Select> & {
@@ -37,15 +37,23 @@
37
37
  // Value display
38
38
  .value {
39
39
  flex: 1;
40
+ min-width: 0;
40
41
  overflow: hidden;
41
42
  text-overflow: ellipsis;
42
43
  white-space: nowrap;
44
+ display: flex;
45
+ align-items: center;
43
46
 
44
47
  &[data-placeholder] {
45
48
  color: var(--fui-text-tertiary, $fui-text-tertiary);
46
49
  }
47
50
  }
48
51
 
52
+ // Placeholder text
53
+ .placeholder {
54
+ color: var(--fui-text-tertiary, $fui-text-tertiary);
55
+ }
56
+
49
57
  // Chevron icon
50
58
  .icon {
51
59
  display: flex;
@@ -101,10 +101,23 @@ function CheckIcon() {
101
101
  }
102
102
 
103
103
  // ============================================
104
- // Context for placeholder
104
+ // Context for Select state
105
105
  // ============================================
106
106
 
107
- const SelectContext = React.createContext<{ placeholder?: string }>({});
107
+ interface SelectContextValue {
108
+ placeholder?: string;
109
+ value?: SelectValue | null;
110
+ itemsRef: React.MutableRefObject<Map<SelectValue, React.ReactNode>>;
111
+ // Version counter to trigger re-renders when items register
112
+ itemsVersion: number;
113
+ incrementItemsVersion: () => void;
114
+ }
115
+
116
+ const SelectContext = React.createContext<SelectContextValue>({
117
+ itemsRef: { current: new Map() },
118
+ itemsVersion: 0,
119
+ incrementItemsVersion: () => {},
120
+ });
108
121
 
109
122
  // ============================================
110
123
  // Components
@@ -123,12 +136,55 @@ function SelectRoot({
123
136
  name,
124
137
  placeholder,
125
138
  }: SelectProps) {
139
+ // Track current value for controlled and uncontrolled modes
140
+ const [internalValue, setInternalValue] = React.useState<SelectValue | null | undefined>(
141
+ value ?? defaultValue ?? null
142
+ );
143
+
144
+ // Registry for item children - allows trigger to render selected item's content
145
+ const itemsRef = React.useRef<Map<SelectValue, React.ReactNode>>(new Map());
146
+
147
+ // Version counter to trigger trigger re-render when items register
148
+ const [itemsVersion, setItemsVersion] = React.useState(0);
149
+ const incrementItemsVersion = React.useCallback(() => {
150
+ setItemsVersion((v) => v + 1);
151
+ }, []);
152
+
153
+ // Sync internal value with controlled value
154
+ React.useEffect(() => {
155
+ if (value !== undefined) {
156
+ setInternalValue(value);
157
+ }
158
+ }, [value]);
159
+
160
+ const handleValueChange = React.useCallback(
161
+ (newValue: SelectValue | null) => {
162
+ if (value === undefined) {
163
+ // Uncontrolled mode
164
+ setInternalValue(newValue);
165
+ }
166
+ onValueChange?.(newValue);
167
+ },
168
+ [value, onValueChange]
169
+ );
170
+
171
+ const contextValue = React.useMemo(
172
+ () => ({
173
+ placeholder,
174
+ value: value !== undefined ? value : internalValue,
175
+ itemsRef,
176
+ itemsVersion,
177
+ incrementItemsVersion,
178
+ }),
179
+ [placeholder, value, internalValue, itemsVersion, incrementItemsVersion]
180
+ );
181
+
126
182
  return (
127
- <SelectContext.Provider value={{ placeholder }}>
183
+ <SelectContext.Provider value={contextValue}>
128
184
  <BaseSelect.Root
129
185
  value={value}
130
186
  defaultValue={defaultValue}
131
- onValueChange={onValueChange}
187
+ onValueChange={handleValueChange}
132
188
  open={open}
133
189
  defaultOpen={defaultOpen}
134
190
  onOpenChange={onOpenChange}
@@ -148,11 +204,24 @@ function SelectTrigger({ children, placeholder, className, ...htmlProps }: Selec
148
204
 
149
205
  const classes = [styles.trigger, className].filter(Boolean).join(' ');
150
206
 
207
+ // Get the selected item's children from the registry
208
+ // Note: itemsVersion in context ensures we re-render when items register
209
+ const selectedContent = context.value != null
210
+ ? context.itemsRef.current.get(context.value)
211
+ : null;
212
+
213
+ // Determine what to show in the value area
214
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
215
+ const _version = context.itemsVersion; // Force dependency on itemsVersion for re-render
216
+ const displayContent = selectedContent ?? (
217
+ placeholderText ? <span className={styles.placeholder}>{placeholderText}</span> : null
218
+ );
219
+
151
220
  return (
152
221
  <BaseSelect.Trigger {...htmlProps} className={classes}>
153
222
  {children ?? (
154
223
  <>
155
- <BaseSelect.Value placeholder={placeholderText} className={styles.value} />
224
+ <span className={styles.value}>{displayContent}</span>
156
225
  <BaseSelect.Icon className={styles.icon}>
157
226
  <ChevronDownIcon />
158
227
  </BaseSelect.Icon>
@@ -187,8 +256,19 @@ function SelectContent({
187
256
  }
188
257
 
189
258
  function SelectItem({ children, value, disabled, className }: SelectItemProps) {
259
+ const context = React.useContext(SelectContext);
190
260
  const classes = [styles.item, className].filter(Boolean).join(' ');
191
261
 
262
+ // Register this item's children in the registry so the trigger can display them
263
+ React.useEffect(() => {
264
+ context.itemsRef.current.set(value, children);
265
+ // Trigger re-render of trigger to show the registered content
266
+ context.incrementItemsVersion();
267
+ return () => {
268
+ context.itemsRef.current.delete(value);
269
+ };
270
+ }, [context, value, children]);
271
+
192
272
  return (
193
273
  <BaseSelect.Item value={value} disabled={disabled} className={classes}>
194
274
  <BaseSelect.ItemText>{children}</BaseSelect.ItemText>
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { Separator } from './index.js';
3
+ import { Separator } from '.';
4
4
 
5
5
  export default defineSegment({
6
6
  component: Separator,
@@ -1,7 +1,7 @@
1
1
  import React, { useState } from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { Sidebar, SidebarProvider, useSidebar } from './index.js';
4
- import { Button } from '../Button/index.js';
3
+ import { Sidebar, SidebarProvider, useSidebar } from '.';
4
+ import { Button } from '../Button';
5
5
 
6
6
  // Example icons (inline SVGs for demo)
7
7
  const HomeIcon = () => (
@@ -159,6 +159,25 @@
159
159
  padding: 0;
160
160
  }
161
161
 
162
+ // Collapsible section styles (using Collapsible component)
163
+ .sectionCollapsible {
164
+ // Override Collapsible trigger styles for sidebar context
165
+ :global(.collapsible-trigger),
166
+ button[aria-expanded] {
167
+ background: transparent;
168
+ justify-content: space-between;
169
+ padding: var(--fui-space-1, $fui-space-1) var(--fui-space-2, $fui-space-2);
170
+
171
+ &:hover {
172
+ background-color: var(--fui-bg-hover, $fui-bg-hover);
173
+ }
174
+ }
175
+ }
176
+
177
+ .sectionContent {
178
+ // Content container - no extra padding needed
179
+ }
180
+
162
181
  // ============================================
163
182
  // Item
164
183
  // ============================================
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
  import styles from './Sidebar.module.scss';
3
3
  import { Tooltip } from '../Tooltip';
4
4
  import { Skeleton } from '../Skeleton';
5
+ import { Collapsible } from '../Collapsible';
5
6
  // Import globals to ensure CSS variables are defined
6
7
  import '../../styles/globals.scss';
7
8
 
@@ -82,6 +83,10 @@ export interface SidebarSectionProps {
82
83
  label?: string;
83
84
  /** Action element to display in the section header (e.g., "Add" button) */
84
85
  action?: React.ReactNode;
86
+ /** Enable collapsible behavior */
87
+ collapsible?: boolean;
88
+ /** Default expanded state (only applies when collapsible is true) */
89
+ defaultOpen?: boolean;
85
90
  className?: string;
86
91
  }
87
92
 
@@ -618,23 +623,59 @@ function SidebarNav({ children, 'aria-label': ariaLabel = 'Main navigation', cla
618
623
  );
619
624
  }
620
625
 
621
- function SidebarSection({ children, label, action, className }: SidebarSectionProps) {
626
+ function SidebarSection({
627
+ children,
628
+ label,
629
+ action,
630
+ collapsible: isCollapsibleProp = false,
631
+ defaultOpen = true,
632
+ className
633
+ }: SidebarSectionProps) {
622
634
  const { collapsed, isMobile } = useSidebarContext();
623
- const classes = [styles.section, className].filter(Boolean).join(' ');
635
+
636
+ const classes = [
637
+ styles.section,
638
+ className
639
+ ].filter(Boolean).join(' ');
640
+
624
641
  const showLabel = label && (!collapsed || isMobile);
625
642
  const showAction = action && (!collapsed || isMobile);
643
+ const isCollapsible = isCollapsibleProp && showLabel;
644
+
645
+ // Non-collapsible section
646
+ if (!isCollapsible) {
647
+ return (
648
+ <div className={classes} role="group" aria-label={label}>
649
+ {(showLabel || showAction) && (
650
+ <div className={styles.sectionHeader}>
651
+ {showLabel && <div className={styles.sectionLabel}>{label}</div>}
652
+ {showAction && <div className={styles.sectionActionWrapper}>{action}</div>}
653
+ </div>
654
+ )}
655
+ <ul className={styles.sectionList}>
656
+ {children}
657
+ </ul>
658
+ </div>
659
+ );
660
+ }
626
661
 
662
+ // Collapsible section using Collapsible component
627
663
  return (
628
664
  <div className={classes} role="group" aria-label={label}>
629
- {(showLabel || showAction) && (
630
- <div className={styles.sectionHeader}>
631
- {showLabel && <div className={styles.sectionLabel}>{label}</div>}
632
- {showAction && <div className={styles.sectionActionWrapper}>{action}</div>}
633
- </div>
634
- )}
635
- <ul className={styles.sectionList}>
636
- {children}
637
- </ul>
665
+ <Collapsible defaultOpen={defaultOpen} className={styles.sectionCollapsible}>
666
+ <Collapsible.Trigger
667
+ className={styles.sectionHeader}
668
+ chevronPosition="end"
669
+ >
670
+ <span className={styles.sectionLabel}>{label}</span>
671
+ {showAction && <span className={styles.sectionActionWrapper} onClick={(e) => e.stopPropagation()}>{action}</span>}
672
+ </Collapsible.Trigger>
673
+ <Collapsible.Content className={styles.sectionContent}>
674
+ <ul className={styles.sectionList}>
675
+ {children}
676
+ </ul>
677
+ </Collapsible.Content>
678
+ </Collapsible>
638
679
  </div>
639
680
  );
640
681
  }
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { defineSegment } from '@fragments/core';
3
- import { Skeleton } from './index.js';
3
+ import { Skeleton } from '.';
4
4
 
5
5
  export default defineSegment({
6
6
  component: Skeleton,
@@ -0,0 +1,201 @@
1
+ import React from 'react';
2
+ import { defineSegment } from '@fragments/core';
3
+ import { Slider } from '.';
4
+
5
+ export default defineSegment({
6
+ component: Slider,
7
+
8
+ meta: {
9
+ name: 'Slider',
10
+ description: 'Range input control for selecting a numeric value within a defined range. Supports labels, value display, and custom step intervals.',
11
+ category: 'forms',
12
+ status: 'stable',
13
+ tags: ['slider', 'range', 'input', 'number', 'control'],
14
+ since: '0.2.0',
15
+ },
16
+
17
+ usage: {
18
+ when: [
19
+ 'Selecting a value from a continuous range',
20
+ 'Volume or brightness controls',
21
+ 'Price range filters',
22
+ 'Settings that benefit from visual feedback',
23
+ ],
24
+ whenNot: [
25
+ 'Precise numeric input (use Input type="number")',
26
+ 'Discrete options (use RadioGroup or Select)',
27
+ 'Yes/no choices (use Toggle)',
28
+ ],
29
+ guidelines: [
30
+ 'Always provide a label describing what the slider controls',
31
+ 'Show the current value when precision matters',
32
+ 'Use appropriate min/max values for the context',
33
+ 'Consider step size for usability',
34
+ ],
35
+ accessibility: [
36
+ 'Label is associated with the slider',
37
+ 'Keyboard accessible with arrow keys',
38
+ 'Current value is announced to screen readers',
39
+ 'Uses native slider semantics',
40
+ ],
41
+ },
42
+
43
+ props: {
44
+ value: {
45
+ type: 'number',
46
+ description: 'Controlled value',
47
+ },
48
+ defaultValue: {
49
+ type: 'number',
50
+ description: 'Default value for uncontrolled usage',
51
+ },
52
+ onChange: {
53
+ type: 'function',
54
+ description: 'Called with new value when changed',
55
+ },
56
+ min: {
57
+ type: 'number',
58
+ description: 'Minimum value',
59
+ default: '0',
60
+ },
61
+ max: {
62
+ type: 'number',
63
+ description: 'Maximum value',
64
+ default: '100',
65
+ },
66
+ step: {
67
+ type: 'number',
68
+ description: 'Step interval',
69
+ default: '1',
70
+ },
71
+ label: {
72
+ type: 'string',
73
+ description: 'Label text',
74
+ },
75
+ showValue: {
76
+ type: 'boolean',
77
+ description: 'Display current value',
78
+ default: 'false',
79
+ },
80
+ valueSuffix: {
81
+ type: 'string',
82
+ description: 'Suffix after value (e.g., "%", "px")',
83
+ },
84
+ disabled: {
85
+ type: 'boolean',
86
+ description: 'Disable the slider',
87
+ default: 'false',
88
+ },
89
+ },
90
+
91
+ relations: [
92
+ { component: 'Input', relationship: 'alternative', note: 'Use Input for precise numeric entry' },
93
+ { component: 'Progress', relationship: 'sibling', note: 'Similar visual, but Progress is read-only' },
94
+ ],
95
+
96
+ contract: {
97
+ propsSummary: [
98
+ 'value: number - controlled value',
99
+ 'defaultValue: number - initial value',
100
+ 'onChange: (value: number) => void - change handler',
101
+ 'min/max: number - range bounds',
102
+ 'step: number - increment size',
103
+ 'label: string - field label',
104
+ 'showValue: boolean - display value',
105
+ 'valueSuffix: string - unit suffix',
106
+ ],
107
+ scenarioTags: [
108
+ 'forms.range',
109
+ 'input.numeric',
110
+ 'control.slider',
111
+ ],
112
+ a11yRules: ['A11Y_LABEL_REQUIRED', 'A11Y_KEYBOARD_ACCESSIBLE'],
113
+ },
114
+
115
+ variants: [
116
+ {
117
+ name: 'Default',
118
+ description: 'Basic slider with label',
119
+ render: () => (
120
+ <div style={{ width: '300px' }}>
121
+ <Slider label="Volume" defaultValue={50} />
122
+ </div>
123
+ ),
124
+ },
125
+ {
126
+ name: 'With Value Display',
127
+ description: 'Shows current value alongside the slider',
128
+ render: () => (
129
+ <div style={{ width: '300px' }}>
130
+ <Slider
131
+ label="Brightness"
132
+ defaultValue={75}
133
+ showValue
134
+ valueSuffix="%"
135
+ />
136
+ </div>
137
+ ),
138
+ },
139
+ {
140
+ name: 'Custom Range',
141
+ description: 'Custom min, max, and step values',
142
+ render: () => (
143
+ <div style={{ width: '300px', display: 'flex', flexDirection: 'column', gap: '16px' }}>
144
+ <Slider
145
+ label="Temperature"
146
+ min={60}
147
+ max={80}
148
+ step={1}
149
+ defaultValue={72}
150
+ showValue
151
+ valueSuffix="°F"
152
+ />
153
+ <Slider
154
+ label="Font Size"
155
+ min={12}
156
+ max={32}
157
+ step={2}
158
+ defaultValue={16}
159
+ showValue
160
+ valueSuffix="px"
161
+ />
162
+ </div>
163
+ ),
164
+ },
165
+ {
166
+ name: 'Controlled',
167
+ description: 'Controlled slider with external state',
168
+ render: () => {
169
+ const [value, setValue] = React.useState(50);
170
+ return (
171
+ <div style={{ width: '300px', display: 'flex', flexDirection: 'column', gap: '12px' }}>
172
+ <Slider
173
+ label="Opacity"
174
+ value={value}
175
+ onChange={setValue}
176
+ showValue
177
+ valueSuffix="%"
178
+ />
179
+ <div style={{ fontSize: '14px', color: 'var(--fui-text-secondary)' }}>
180
+ Current value: {value}%
181
+ </div>
182
+ </div>
183
+ );
184
+ },
185
+ },
186
+ {
187
+ name: 'Disabled',
188
+ description: 'Disabled slider',
189
+ render: () => (
190
+ <div style={{ width: '300px' }}>
191
+ <Slider
192
+ label="Locked Setting"
193
+ defaultValue={30}
194
+ showValue
195
+ disabled
196
+ />
197
+ </div>
198
+ ),
199
+ },
200
+ ],
201
+ });