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.
- package/dist/assets/BRAND_GUIDELINES.md +0 -8
- package/dist/assets/components/accordion.stories.tsx +310 -0
- package/dist/assets/components/accordion.tsx +56 -30
- package/dist/assets/components/alert.stories.tsx +199 -0
- package/dist/assets/components/autocomplete.stories.tsx +307 -0
- package/dist/assets/components/autocomplete.tsx +28 -4
- package/dist/assets/components/avatar.stories.tsx +175 -0
- package/dist/assets/components/badge.stories.tsx +258 -0
- package/dist/assets/components/breadcrumbs.stories.tsx +175 -0
- package/dist/assets/components/button.stories.tsx +362 -0
- package/dist/assets/components/button.tsx +48 -3
- package/dist/assets/components/calendar.stories.tsx +247 -0
- package/dist/assets/components/card.stories.tsx +275 -0
- package/dist/assets/components/card.tsx +26 -1
- package/dist/assets/components/checkbox-group.stories.tsx +281 -0
- package/dist/assets/components/checkbox.stories.tsx +160 -0
- package/dist/assets/components/checkbox.tsx +32 -4
- package/dist/assets/components/code.stories.tsx +265 -0
- package/dist/assets/components/date-input.stories.tsx +278 -0
- package/dist/assets/components/date-input.tsx +24 -2
- package/dist/assets/components/date-picker.stories.tsx +337 -0
- package/dist/assets/components/date-picker.tsx +28 -4
- package/dist/assets/components/date-range-picker.stories.tsx +340 -0
- package/dist/assets/components/dialog.stories.tsx +285 -0
- package/dist/assets/components/divider.stories.tsx +176 -0
- package/dist/assets/components/drawer.stories.tsx +216 -0
- package/dist/assets/components/dropdown.stories.tsx +342 -0
- package/dist/assets/components/dropdown.tsx +55 -10
- package/dist/assets/components/form.stories.tsx +372 -0
- package/dist/assets/components/image.stories.tsx +348 -0
- package/dist/assets/components/input-otp.stories.tsx +336 -0
- package/dist/assets/components/input-otp.tsx +24 -2
- package/dist/assets/components/input.stories.tsx +223 -0
- package/dist/assets/components/input.tsx +27 -2
- package/dist/assets/components/kbd.stories.tsx +272 -0
- package/dist/assets/components/link.stories.tsx +199 -0
- package/dist/assets/components/link.tsx +50 -1
- package/dist/assets/components/listbox.stories.tsx +287 -0
- package/dist/assets/components/listbox.tsx +30 -7
- package/dist/assets/components/navbar.stories.tsx +218 -0
- package/dist/assets/components/number-input.stories.tsx +295 -0
- package/dist/assets/components/number-input.tsx +30 -8
- package/dist/assets/components/pagination.stories.tsx +280 -0
- package/dist/assets/components/pagination.tsx +45 -21
- package/dist/assets/components/popover.stories.tsx +219 -0
- package/dist/assets/components/progress.stories.tsx +153 -0
- package/dist/assets/components/radio-group.stories.tsx +187 -0
- package/dist/assets/components/radio-group.tsx +30 -6
- package/dist/assets/components/range-calendar.stories.tsx +334 -0
- package/dist/assets/components/scroll-shadow.stories.tsx +335 -0
- package/dist/assets/components/select.stories.tsx +192 -0
- package/dist/assets/components/select.tsx +54 -7
- package/dist/assets/components/skeleton.stories.tsx +166 -0
- package/dist/assets/components/slider.stories.tsx +145 -0
- package/dist/assets/components/slider.tsx +43 -8
- package/dist/assets/components/spacer.stories.tsx +216 -0
- package/dist/assets/components/spinner.stories.tsx +149 -0
- package/dist/assets/components/switch.stories.tsx +170 -0
- package/dist/assets/components/switch.tsx +29 -4
- package/dist/assets/components/table.stories.tsx +322 -0
- package/dist/assets/components/tabs.stories.tsx +306 -0
- package/dist/assets/components/tabs.tsx +25 -4
- package/dist/assets/components/textarea.stories.tsx +103 -0
- package/dist/assets/components/textarea.tsx +27 -3
- package/dist/assets/components/theme-toggle.stories.tsx +248 -0
- package/dist/assets/components/time-input.stories.tsx +365 -0
- package/dist/assets/components/time-input.tsx +25 -3
- package/dist/assets/components/toast.stories.tsx +195 -0
- package/dist/assets/components/tooltip.stories.tsx +226 -0
- package/dist/assets/components/user.stories.tsx +274 -0
- package/dist/assets/logo-manifest.json +0 -18
- package/dist/index.js +2142 -85
- 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={
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|