sonance-brand-mcp 1.1.4 → 1.2.2

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/dist/assets/BRAND_GUIDELINES.md +0 -8
  2. package/dist/assets/components/accordion.stories.tsx +310 -0
  3. package/dist/assets/components/accordion.tsx +56 -30
  4. package/dist/assets/components/alert.stories.tsx +199 -0
  5. package/dist/assets/components/autocomplete.stories.tsx +307 -0
  6. package/dist/assets/components/autocomplete.tsx +28 -4
  7. package/dist/assets/components/avatar.stories.tsx +175 -0
  8. package/dist/assets/components/badge.stories.tsx +258 -0
  9. package/dist/assets/components/breadcrumbs.stories.tsx +175 -0
  10. package/dist/assets/components/button.stories.tsx +362 -0
  11. package/dist/assets/components/button.tsx +48 -3
  12. package/dist/assets/components/calendar.stories.tsx +247 -0
  13. package/dist/assets/components/card.stories.tsx +275 -0
  14. package/dist/assets/components/card.tsx +26 -1
  15. package/dist/assets/components/checkbox-group.stories.tsx +281 -0
  16. package/dist/assets/components/checkbox.stories.tsx +160 -0
  17. package/dist/assets/components/checkbox.tsx +32 -4
  18. package/dist/assets/components/code.stories.tsx +265 -0
  19. package/dist/assets/components/date-input.stories.tsx +278 -0
  20. package/dist/assets/components/date-input.tsx +24 -2
  21. package/dist/assets/components/date-picker.stories.tsx +337 -0
  22. package/dist/assets/components/date-picker.tsx +28 -4
  23. package/dist/assets/components/date-range-picker.stories.tsx +340 -0
  24. package/dist/assets/components/dialog.stories.tsx +285 -0
  25. package/dist/assets/components/divider.stories.tsx +176 -0
  26. package/dist/assets/components/drawer.stories.tsx +216 -0
  27. package/dist/assets/components/dropdown.stories.tsx +342 -0
  28. package/dist/assets/components/dropdown.tsx +55 -10
  29. package/dist/assets/components/form.stories.tsx +372 -0
  30. package/dist/assets/components/image.stories.tsx +348 -0
  31. package/dist/assets/components/input-otp.stories.tsx +336 -0
  32. package/dist/assets/components/input-otp.tsx +24 -2
  33. package/dist/assets/components/input.stories.tsx +223 -0
  34. package/dist/assets/components/input.tsx +27 -2
  35. package/dist/assets/components/kbd.stories.tsx +272 -0
  36. package/dist/assets/components/link.stories.tsx +199 -0
  37. package/dist/assets/components/link.tsx +50 -1
  38. package/dist/assets/components/listbox.stories.tsx +287 -0
  39. package/dist/assets/components/listbox.tsx +30 -7
  40. package/dist/assets/components/navbar.stories.tsx +218 -0
  41. package/dist/assets/components/number-input.stories.tsx +295 -0
  42. package/dist/assets/components/number-input.tsx +30 -8
  43. package/dist/assets/components/pagination.stories.tsx +280 -0
  44. package/dist/assets/components/pagination.tsx +45 -21
  45. package/dist/assets/components/popover.stories.tsx +219 -0
  46. package/dist/assets/components/progress.stories.tsx +153 -0
  47. package/dist/assets/components/radio-group.stories.tsx +187 -0
  48. package/dist/assets/components/radio-group.tsx +30 -6
  49. package/dist/assets/components/range-calendar.stories.tsx +334 -0
  50. package/dist/assets/components/scroll-shadow.stories.tsx +335 -0
  51. package/dist/assets/components/select.stories.tsx +192 -0
  52. package/dist/assets/components/select.tsx +54 -7
  53. package/dist/assets/components/skeleton.stories.tsx +166 -0
  54. package/dist/assets/components/slider.stories.tsx +145 -0
  55. package/dist/assets/components/slider.tsx +43 -8
  56. package/dist/assets/components/spacer.stories.tsx +216 -0
  57. package/dist/assets/components/spinner.stories.tsx +149 -0
  58. package/dist/assets/components/switch.stories.tsx +170 -0
  59. package/dist/assets/components/switch.tsx +29 -4
  60. package/dist/assets/components/table.stories.tsx +322 -0
  61. package/dist/assets/components/tabs.stories.tsx +306 -0
  62. package/dist/assets/components/tabs.tsx +25 -4
  63. package/dist/assets/components/textarea.stories.tsx +103 -0
  64. package/dist/assets/components/textarea.tsx +27 -3
  65. package/dist/assets/components/theme-toggle.stories.tsx +248 -0
  66. package/dist/assets/components/time-input.stories.tsx +365 -0
  67. package/dist/assets/components/time-input.tsx +25 -3
  68. package/dist/assets/components/toast.stories.tsx +195 -0
  69. package/dist/assets/components/tooltip.stories.tsx +226 -0
  70. package/dist/assets/components/user.stories.tsx +274 -0
  71. package/dist/assets/logo-manifest.json +0 -18
  72. package/dist/index.js +2142 -85
  73. package/package.json +1 -1
@@ -3,6 +3,22 @@
3
3
  import { useState, useRef, KeyboardEvent, ClipboardEvent, useEffect } from "react";
4
4
  import { cn } from "@/lib/utils";
5
5
 
6
+ export type InputOTPState = "default" | "hover" | "focus" | "error" | "disabled";
7
+
8
+ // State styles for Storybook/Figma visualization
9
+ const getStateStyles = (state?: InputOTPState) => {
10
+ if (!state || state === "default") return "";
11
+
12
+ const stateMap: Record<string, string> = {
13
+ hover: "border-border-hover",
14
+ focus: "border-input-focus",
15
+ error: "border-error",
16
+ disabled: "opacity-50 cursor-not-allowed",
17
+ };
18
+
19
+ return stateMap[state] || "";
20
+ };
21
+
6
22
  interface InputOTPProps {
7
23
  length?: number;
8
24
  value?: string;
@@ -14,6 +30,8 @@ interface InputOTPProps {
14
30
  disabled?: boolean;
15
31
  autoFocus?: boolean;
16
32
  className?: string;
33
+ /** Visual state for Storybook/Figma documentation */
34
+ state?: InputOTPState;
17
35
  }
18
36
 
19
37
  export function InputOTP({
@@ -27,7 +45,10 @@ export function InputOTP({
27
45
  disabled = false,
28
46
  autoFocus = false,
29
47
  className,
48
+ state,
30
49
  }: InputOTPProps) {
50
+ const isDisabled = disabled || state === "disabled";
51
+ const hasError = error || state === "error";
31
52
  const [internalValue, setInternalValue] = useState(defaultValue);
32
53
  const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
33
54
 
@@ -127,14 +148,15 @@ export function InputOTP({
127
148
  onKeyDown={(e) => handleKeyDown(index, e)}
128
149
  onPaste={handlePaste}
129
150
  onFocus={() => handleFocus(index)}
130
- disabled={disabled}
151
+ disabled={isDisabled}
131
152
  className={cn(
132
153
  "h-12 w-10 border border-input-border bg-input",
133
154
  "text-center text-lg font-medium text-foreground",
134
155
  "transition-colors duration-200",
135
156
  "focus:border-input-focus focus:outline-none",
136
157
  "disabled:cursor-not-allowed disabled:opacity-50",
137
- error && "border-error"
158
+ hasError && "border-error",
159
+ getStateStyles(state)
138
160
  )}
139
161
  aria-label={`Digit ${index + 1}`}
140
162
  />
@@ -0,0 +1,223 @@
1
+ import type { Meta, StoryObj } from '@storybook/nextjs-vite';
2
+ import { Input, type InputState } from './input';
3
+
4
+ const meta: Meta<typeof Input> = {
5
+ title: 'Components/Forms/Input',
6
+ component: Input,
7
+ tags: ['autodocs'],
8
+ parameters: {
9
+ docs: {
10
+ description: {
11
+ component: 'A text input field with optional label and error state. Clean, minimal design following Sonance brand guidelines.',
12
+ },
13
+ },
14
+ },
15
+ argTypes: {
16
+ label: {
17
+ control: 'text',
18
+ description: 'Label text displayed above the input',
19
+ },
20
+ error: {
21
+ control: 'text',
22
+ description: 'Error message displayed below the input',
23
+ },
24
+ placeholder: {
25
+ control: 'text',
26
+ description: 'Placeholder text',
27
+ },
28
+ state: {
29
+ control: 'select',
30
+ options: ['default', 'hover', 'focus', 'error', 'disabled'],
31
+ description: 'Visual state for documentation',
32
+ table: {
33
+ defaultValue: { summary: 'default' },
34
+ },
35
+ },
36
+ disabled: {
37
+ control: 'boolean',
38
+ description: 'Disable the input',
39
+ },
40
+ type: {
41
+ control: 'select',
42
+ options: ['text', 'email', 'password', 'number', 'tel', 'url'],
43
+ description: 'Input type',
44
+ },
45
+ },
46
+ };
47
+
48
+ export default meta;
49
+ type Story = StoryObj<typeof meta>;
50
+
51
+ // Default story
52
+ export const Default: Story = {
53
+ args: {
54
+ placeholder: 'Enter text...',
55
+ },
56
+ };
57
+
58
+ // With Label
59
+ export const WithLabel: Story = {
60
+ args: {
61
+ label: 'Email Address',
62
+ placeholder: 'name@example.com',
63
+ type: 'email',
64
+ },
65
+ };
66
+
67
+ // With Error
68
+ export const WithError: Story = {
69
+ args: {
70
+ label: 'Email Address',
71
+ placeholder: 'name@example.com',
72
+ error: 'Please enter a valid email address',
73
+ defaultValue: 'invalid-email',
74
+ },
75
+ };
76
+
77
+ // Disabled
78
+ export const Disabled: Story = {
79
+ args: {
80
+ label: 'Disabled Input',
81
+ placeholder: 'Cannot edit...',
82
+ disabled: true,
83
+ },
84
+ };
85
+
86
+ // Password
87
+ export const Password: Story = {
88
+ args: {
89
+ label: 'Password',
90
+ type: 'password',
91
+ placeholder: 'Enter password...',
92
+ },
93
+ };
94
+
95
+ // All States
96
+ export const AllStates: Story = {
97
+ render: () => (
98
+ <div className="space-y-6 w-80">
99
+ <Input placeholder="Default input" />
100
+ <Input label="With Label" placeholder="Enter text..." />
101
+ <Input
102
+ label="With Error"
103
+ placeholder="Enter email..."
104
+ error="This field is required"
105
+ defaultValue="invalid"
106
+ />
107
+ <Input
108
+ label="Disabled"
109
+ placeholder="Cannot edit..."
110
+ disabled
111
+ />
112
+ <Input
113
+ label="With Value"
114
+ defaultValue="Pre-filled value"
115
+ />
116
+ </div>
117
+ ),
118
+ };
119
+
120
+ // Form Example
121
+ export const FormExample: Story = {
122
+ render: () => (
123
+ <form className="space-y-4 w-80">
124
+ <Input label="First Name" placeholder="John" />
125
+ <Input label="Last Name" placeholder="Doe" />
126
+ <Input label="Email" type="email" placeholder="john@example.com" />
127
+ <Input label="Phone" type="tel" placeholder="+1 (555) 000-0000" />
128
+ </form>
129
+ ),
130
+ };
131
+
132
+ // Interactive States - for Figma documentation
133
+ export const StateMatrix: Story = {
134
+ render: () => {
135
+ const states: InputState[] = ['default', 'hover', 'focus', 'error', 'disabled'];
136
+
137
+ return (
138
+ <div className="space-y-6 w-96">
139
+ <h3 className="text-sm font-medium text-foreground-muted">Input States</h3>
140
+ <div className="grid grid-cols-1 gap-4">
141
+ {states.map((state) => (
142
+ <div key={state} className="flex items-center gap-4">
143
+ <span className="text-xs font-medium text-foreground-muted uppercase w-20">{state}</span>
144
+ <Input
145
+ state={state}
146
+ placeholder={`${state} state`}
147
+ defaultValue={state !== 'default' ? 'Input value' : ''}
148
+ />
149
+ </div>
150
+ ))}
151
+ </div>
152
+ </div>
153
+ );
154
+ },
155
+ };
156
+
157
+ // With Label States
158
+ export const LabeledStateMatrix: Story = {
159
+ render: () => {
160
+ const states: InputState[] = ['default', 'hover', 'focus', 'error', 'disabled'];
161
+
162
+ return (
163
+ <div className="space-y-6">
164
+ <h3 className="text-sm font-medium text-foreground-muted">Input with Label - All States</h3>
165
+ <div className="grid grid-cols-5 gap-4">
166
+ {states.map((state) => (
167
+ <div key={state} className="text-center">
168
+ <Input
169
+ label="Email"
170
+ state={state}
171
+ placeholder="name@example.com"
172
+ defaultValue={state !== 'default' ? 'user@email.com' : ''}
173
+ error={state === 'error' ? 'Invalid email' : undefined}
174
+ />
175
+ <p className="text-xs text-foreground-muted mt-2 uppercase">{state}</p>
176
+ </div>
177
+ ))}
178
+ </div>
179
+ </div>
180
+ );
181
+ },
182
+ };
183
+
184
+ // Responsive Matrix - Mobile, Tablet, Desktop
185
+ export const ResponsiveMatrix: Story = {
186
+ render: () => (
187
+ <div className="space-y-8">
188
+ {/* Mobile */}
189
+ <div>
190
+ <h4 className="text-xs uppercase text-foreground-muted mb-2">Mobile (375px)</h4>
191
+ <div className="w-[375px] border border-dashed border-border p-4 space-y-4">
192
+ <Input label="Email" placeholder="name@example.com" />
193
+ <Input label="Password" type="password" placeholder="Enter password" />
194
+ <Input label="Error State" error="This field is required" defaultValue="Invalid" />
195
+ </div>
196
+ </div>
197
+ {/* Tablet */}
198
+ <div>
199
+ <h4 className="text-xs uppercase text-foreground-muted mb-2">Tablet (768px)</h4>
200
+ <div className="w-[768px] border border-dashed border-border p-4">
201
+ <div className="grid grid-cols-2 gap-4">
202
+ <Input label="First Name" placeholder="John" />
203
+ <Input label="Last Name" placeholder="Doe" />
204
+ <Input label="Email" type="email" placeholder="john@example.com" />
205
+ <Input label="Phone" type="tel" placeholder="+1 (555) 000-0000" />
206
+ </div>
207
+ </div>
208
+ </div>
209
+ {/* Desktop */}
210
+ <div>
211
+ <h4 className="text-xs uppercase text-foreground-muted mb-2">Desktop (1280px)</h4>
212
+ <div className="w-[1280px] border border-dashed border-border p-4">
213
+ <div className="grid grid-cols-4 gap-4">
214
+ <Input label="First Name" placeholder="John" />
215
+ <Input label="Last Name" placeholder="Doe" />
216
+ <Input label="Email" type="email" placeholder="john@example.com" />
217
+ <Input label="Phone" type="tel" placeholder="+1 (555) 000-0000" />
218
+ </div>
219
+ </div>
220
+ </div>
221
+ </div>
222
+ ),
223
+ };
@@ -1,13 +1,34 @@
1
1
  import { cn } from "@/lib/utils";
2
2
  import { forwardRef } from "react";
3
3
 
4
+ export type InputState = "default" | "hover" | "focus" | "error" | "disabled";
5
+
4
6
  interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
5
7
  label?: string;
6
8
  error?: string;
9
+ /** Visual state for Storybook/Figma documentation */
10
+ state?: InputState;
7
11
  }
8
12
 
13
+ // State styles for Storybook/Figma visualization
14
+ const getStateStyles = (state?: InputState) => {
15
+ if (!state || state === "default") return "";
16
+
17
+ const stateMap: Record<string, string> = {
18
+ hover: "border-border-hover bg-input",
19
+ focus: "border-input-focus",
20
+ error: "border-error",
21
+ disabled: "opacity-50 cursor-not-allowed bg-secondary",
22
+ };
23
+
24
+ return stateMap[state] || "";
25
+ };
26
+
9
27
  export const Input = forwardRef<HTMLInputElement, InputProps>(
10
- ({ className, label, error, ...props }, ref) => {
28
+ ({ className, label, error, state, disabled, ...props }, ref) => {
29
+ const isDisabled = disabled || state === "disabled";
30
+ const hasError = error || state === "error";
31
+
11
32
  return (
12
33
  <div className="w-full">
13
34
  {label && (
@@ -17,12 +38,16 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
17
38
  )}
18
39
  <input
19
40
  ref={ref}
41
+ disabled={isDisabled}
20
42
  className={cn(
21
43
  "w-full border border-input-border bg-input px-4 py-3",
22
44
  "text-foreground placeholder:text-input-placeholder",
23
45
  "transition-colors duration-200",
46
+ "hover:border-border-hover",
24
47
  "focus:border-input-focus focus:outline-none",
25
- error && "border-error",
48
+ "disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-secondary",
49
+ hasError && "border-error",
50
+ getStateStyles(state),
26
51
  className
27
52
  )}
28
53
  {...props}
@@ -0,0 +1,272 @@
1
+ import type { Meta, StoryObj } from '@storybook/nextjs-vite';
2
+ import { Kbd, KeyboardShortcut, shortcuts } from './kbd';
3
+
4
+ const meta: Meta<typeof Kbd> = {
5
+ title: 'Components/Utilities/Kbd',
6
+ component: Kbd,
7
+ tags: ['autodocs'],
8
+ parameters: {
9
+ docs: {
10
+ description: {
11
+ component: 'Keyboard key display component for showing keyboard shortcuts and key combinations.',
12
+ },
13
+ },
14
+ },
15
+ argTypes: {
16
+ variant: {
17
+ control: 'select',
18
+ options: ['default', 'outline', 'flat'],
19
+ },
20
+ size: {
21
+ control: 'select',
22
+ options: ['sm', 'md', 'lg'],
23
+ },
24
+ },
25
+ };
26
+
27
+ export default meta;
28
+ type Story = StoryObj<typeof meta>;
29
+
30
+ export const Default: Story = {
31
+ render: () => <Kbd>K</Kbd>,
32
+ };
33
+
34
+ export const SingleKey: Story = {
35
+ render: () => (
36
+ <div className="flex items-center gap-4">
37
+ <Kbd>A</Kbd>
38
+ <Kbd>Enter</Kbd>
39
+ <Kbd>Esc</Kbd>
40
+ <Kbd>Tab</Kbd>
41
+ </div>
42
+ ),
43
+ };
44
+
45
+ export const KeyCombination: Story = {
46
+ render: () => <Kbd keys={['cmd', 'k']} />,
47
+ };
48
+
49
+ export const CommonShortcuts: Story = {
50
+ render: () => (
51
+ <div className="space-y-3">
52
+ <div className="flex items-center justify-between">
53
+ <span className="text-foreground-secondary">Copy</span>
54
+ <Kbd keys={shortcuts.copy} />
55
+ </div>
56
+ <div className="flex items-center justify-between">
57
+ <span className="text-foreground-secondary">Paste</span>
58
+ <Kbd keys={shortcuts.paste} />
59
+ </div>
60
+ <div className="flex items-center justify-between">
61
+ <span className="text-foreground-secondary">Undo</span>
62
+ <Kbd keys={shortcuts.undo} />
63
+ </div>
64
+ <div className="flex items-center justify-between">
65
+ <span className="text-foreground-secondary">Redo</span>
66
+ <Kbd keys={shortcuts.redo} />
67
+ </div>
68
+ <div className="flex items-center justify-between">
69
+ <span className="text-foreground-secondary">Save</span>
70
+ <Kbd keys={shortcuts.save} />
71
+ </div>
72
+ <div className="flex items-center justify-between">
73
+ <span className="text-foreground-secondary">Find</span>
74
+ <Kbd keys={shortcuts.find} />
75
+ </div>
76
+ </div>
77
+ ),
78
+ };
79
+
80
+ export const Variants: Story = {
81
+ render: () => (
82
+ <div className="space-y-4">
83
+ <div className="flex items-center gap-4">
84
+ <span className="text-xs text-foreground-muted w-20">Default</span>
85
+ <Kbd variant="default" keys={['cmd', 'shift', 'p']} />
86
+ </div>
87
+ <div className="flex items-center gap-4">
88
+ <span className="text-xs text-foreground-muted w-20">Outline</span>
89
+ <Kbd variant="outline" keys={['cmd', 'shift', 'p']} />
90
+ </div>
91
+ <div className="flex items-center gap-4">
92
+ <span className="text-xs text-foreground-muted w-20">Flat</span>
93
+ <Kbd variant="flat" keys={['cmd', 'shift', 'p']} />
94
+ </div>
95
+ </div>
96
+ ),
97
+ };
98
+
99
+ export const Sizes: Story = {
100
+ render: () => (
101
+ <div className="space-y-4">
102
+ <div className="flex items-center gap-4">
103
+ <span className="text-xs text-foreground-muted w-20">Small</span>
104
+ <Kbd size="sm" keys={['cmd', 'k']} />
105
+ </div>
106
+ <div className="flex items-center gap-4">
107
+ <span className="text-xs text-foreground-muted w-20">Medium</span>
108
+ <Kbd size="md" keys={['cmd', 'k']} />
109
+ </div>
110
+ <div className="flex items-center gap-4">
111
+ <span className="text-xs text-foreground-muted w-20">Large</span>
112
+ <Kbd size="lg" keys={['cmd', 'k']} />
113
+ </div>
114
+ </div>
115
+ ),
116
+ };
117
+
118
+ export const KeyMapping: Story = {
119
+ render: () => (
120
+ <div className="space-y-3">
121
+ <p className="text-xs text-foreground-muted mb-4">
122
+ Common key names are automatically mapped to symbols:
123
+ </p>
124
+ <div className="grid grid-cols-2 gap-4">
125
+ <div className="flex items-center gap-2">
126
+ <span className="text-foreground-secondary text-sm">cmd</span>
127
+ <span>→</span>
128
+ <Kbd>cmd</Kbd>
129
+ </div>
130
+ <div className="flex items-center gap-2">
131
+ <span className="text-foreground-secondary text-sm">ctrl</span>
132
+ <span>→</span>
133
+ <Kbd>ctrl</Kbd>
134
+ </div>
135
+ <div className="flex items-center gap-2">
136
+ <span className="text-foreground-secondary text-sm">alt</span>
137
+ <span>→</span>
138
+ <Kbd>alt</Kbd>
139
+ </div>
140
+ <div className="flex items-center gap-2">
141
+ <span className="text-foreground-secondary text-sm">shift</span>
142
+ <span>→</span>
143
+ <Kbd>shift</Kbd>
144
+ </div>
145
+ <div className="flex items-center gap-2">
146
+ <span className="text-foreground-secondary text-sm">enter</span>
147
+ <span>→</span>
148
+ <Kbd>enter</Kbd>
149
+ </div>
150
+ <div className="flex items-center gap-2">
151
+ <span className="text-foreground-secondary text-sm">escape</span>
152
+ <span>→</span>
153
+ <Kbd>escape</Kbd>
154
+ </div>
155
+ <div className="flex items-center gap-2">
156
+ <span className="text-foreground-secondary text-sm">backspace</span>
157
+ <span>→</span>
158
+ <Kbd>backspace</Kbd>
159
+ </div>
160
+ <div className="flex items-center gap-2">
161
+ <span className="text-foreground-secondary text-sm">up</span>
162
+ <span>→</span>
163
+ <Kbd>up</Kbd>
164
+ </div>
165
+ </div>
166
+ </div>
167
+ ),
168
+ };
169
+
170
+ export const WithLabel: Story = {
171
+ render: () => (
172
+ <div className="space-y-3">
173
+ <KeyboardShortcut label="Search" keys={['cmd', 'k']} />
174
+ <KeyboardShortcut label="Command Palette" keys={['cmd', 'shift', 'p']} />
175
+ <KeyboardShortcut label="Quick Open" keys={['cmd', 'p']} />
176
+ </div>
177
+ ),
178
+ };
179
+
180
+ export const InContext: Story = {
181
+ render: () => (
182
+ <div className="w-64 border border-border">
183
+ <div className="px-3 py-2 text-xs font-medium uppercase tracking-widest text-foreground-muted border-b border-border">
184
+ Actions
185
+ </div>
186
+ <div className="py-1">
187
+ {[
188
+ { label: 'New File', keys: ['cmd', 'n'] },
189
+ { label: 'Open', keys: ['cmd', 'o'] },
190
+ { label: 'Save', keys: ['cmd', 's'] },
191
+ { label: 'Close', keys: ['cmd', 'w'] },
192
+ ].map((item) => (
193
+ <button
194
+ key={item.label}
195
+ className="w-full flex items-center justify-between px-3 py-2 text-sm text-foreground hover:bg-secondary-hover"
196
+ >
197
+ <span>{item.label}</span>
198
+ <Kbd keys={item.keys} size="sm" variant="flat" />
199
+ </button>
200
+ ))}
201
+ </div>
202
+ </div>
203
+ ),
204
+ };
205
+
206
+ export const SearchBar: Story = {
207
+ render: () => (
208
+ <div className="w-96 relative">
209
+ <input
210
+ type="text"
211
+ placeholder="Search..."
212
+ className="w-full px-4 py-2 pr-16 border border-border bg-background text-foreground"
213
+ />
214
+ <div className="absolute right-2 top-1/2 -translate-y-1/2">
215
+ <Kbd keys={['cmd', 'k']} size="sm" variant="outline" />
216
+ </div>
217
+ </div>
218
+ ),
219
+ };
220
+
221
+ // Responsive Matrix - Mobile, Tablet, Desktop
222
+ export const ResponsiveMatrix: Story = {
223
+ render: () => (
224
+ <div className="space-y-8">
225
+ {/* Mobile */}
226
+ <div>
227
+ <h4 className="text-xs uppercase text-foreground-muted mb-2">Mobile (375px)</h4>
228
+ <div className="w-[375px] border border-dashed border-border p-4">
229
+ <div className="flex items-center justify-between">
230
+ <span className="text-foreground-secondary text-sm">Save</span>
231
+ <Kbd keys={['cmd', 's']} size="sm" />
232
+ </div>
233
+ </div>
234
+ </div>
235
+ {/* Tablet */}
236
+ <div>
237
+ <h4 className="text-xs uppercase text-foreground-muted mb-2">Tablet (768px)</h4>
238
+ <div className="w-[768px] border border-dashed border-border p-4">
239
+ <div className="grid grid-cols-2 gap-4">
240
+ <KeyboardShortcut label="Copy" keys={['cmd', 'c']} />
241
+ <KeyboardShortcut label="Paste" keys={['cmd', 'v']} />
242
+ <KeyboardShortcut label="Undo" keys={['cmd', 'z']} />
243
+ <KeyboardShortcut label="Redo" keys={['cmd', 'shift', 'z']} />
244
+ </div>
245
+ </div>
246
+ </div>
247
+ {/* Desktop */}
248
+ <div>
249
+ <h4 className="text-xs uppercase text-foreground-muted mb-2">Desktop (1280px)</h4>
250
+ <div className="w-[1280px] border border-dashed border-border p-4">
251
+ <div className="flex items-center justify-between">
252
+ <div className="flex items-center gap-8">
253
+ <KeyboardShortcut label="Search" keys={['cmd', 'k']} />
254
+ <KeyboardShortcut label="Command Palette" keys={['cmd', 'shift', 'p']} />
255
+ <KeyboardShortcut label="New File" keys={['cmd', 'n']} />
256
+ </div>
257
+ <div className="relative w-64">
258
+ <input
259
+ type="text"
260
+ placeholder="Search..."
261
+ className="w-full px-4 py-2 pr-16 border border-border bg-background text-foreground text-sm"
262
+ />
263
+ <div className="absolute right-2 top-1/2 -translate-y-1/2">
264
+ <Kbd keys={['cmd', 'k']} size="sm" variant="outline" />
265
+ </div>
266
+ </div>
267
+ </div>
268
+ </div>
269
+ </div>
270
+ </div>
271
+ ),
272
+ };