@djangocfg/ui-core 2.1.382 → 2.1.384
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/package.json +5 -12
- package/src/components/boundary/boundary.story.tsx +0 -191
- package/src/components/data/avatar/avatar.story.tsx +0 -115
- package/src/components/data/badge/badge.story.tsx +0 -56
- package/src/components/data/calendar/calendar.story.tsx +0 -127
- package/src/components/data/carousel/carousel.story.tsx +0 -122
- package/src/components/data/progress/progress.story.tsx +0 -97
- package/src/components/data/table/table.story.tsx +0 -148
- package/src/components/data/toggle/toggle.story.tsx +0 -104
- package/src/components/data/toggle-group/toggle-group.story.tsx +0 -118
- package/src/components/feedback/alert/alert.story.tsx +0 -77
- package/src/components/feedback/empty/empty.story.tsx +0 -115
- package/src/components/feedback/preloader/preloader.story.tsx +0 -86
- package/src/components/feedback/spinner/spinner.story.tsx +0 -66
- package/src/components/forms/button/button.story.tsx +0 -116
- package/src/components/forms/button-download/button-download.story.tsx +0 -112
- package/src/components/forms/button-group/button-group.story.tsx +0 -79
- package/src/components/forms/checkbox/checkbox.story.tsx +0 -89
- package/src/components/forms/input/input.story.tsx +0 -77
- package/src/components/forms/input-group/input-group.story.tsx +0 -119
- package/src/components/forms/input-otp/input-otp.story.tsx +0 -105
- package/src/components/forms/label/label.story.tsx +0 -52
- package/src/components/forms/radio-group/radio-group.story.tsx +0 -113
- package/src/components/forms/slider/slider.story.tsx +0 -134
- package/src/components/forms/switch/switch.story.tsx +0 -98
- package/src/components/forms/textarea/textarea.story.tsx +0 -94
- package/src/components/layout/aspect-ratio/aspect-ratio.story.tsx +0 -94
- package/src/components/layout/card/card.story.tsx +0 -105
- package/src/components/layout/resizable/resizable.story.tsx +0 -119
- package/src/components/layout/scroll-area/scroll-area.story.tsx +0 -172
- package/src/components/layout/separator/separator.story.tsx +0 -69
- package/src/components/layout/skeleton/skeleton.story.tsx +0 -101
- package/src/components/navigation/accordion/accordion.story.tsx +0 -110
- package/src/components/navigation/collapsible/collapsible.story.tsx +0 -133
- package/src/components/navigation/command/command.story.tsx +0 -121
- package/src/components/navigation/context-menu/context-menu.story.tsx +0 -125
- package/src/components/navigation/dropdown-menu/dropdown-menu.story.tsx +0 -208
- package/src/components/navigation/menubar/menubar.story.tsx +0 -152
- package/src/components/navigation/navigation-menu/navigation-menu.story.tsx +0 -154
- package/src/components/navigation/tabs/tabs.story.tsx +0 -98
- package/src/components/overlay/alert-dialog/alert-dialog.story.tsx +0 -104
- package/src/components/overlay/dialog/dialog.story.tsx +0 -212
- package/src/components/overlay/drawer/drawer.story.tsx +0 -359
- package/src/components/overlay/hover-card/hover-card.story.tsx +0 -102
- package/src/components/overlay/popover/popover.story.tsx +0 -127
- package/src/components/overlay/responsive-sheet/responsive-sheet.story.tsx +0 -117
- package/src/components/overlay/sheet/sheet.story.tsx +0 -148
- package/src/components/overlay/tooltip/tooltip.story.tsx +0 -139
- package/src/components/select/combobox-async.story.tsx +0 -215
- package/src/components/select/combobox.story.tsx +0 -226
- package/src/components/select/country-select.story.tsx +0 -261
- package/src/components/select/language-select.story.tsx +0 -264
- package/src/components/select/multi-select.story.tsx +0 -122
- package/src/components/select/select.story.tsx +0 -112
- package/src/components/specialized/copy/copy.story.tsx +0 -77
- package/src/components/specialized/flag/flag.story.tsx +0 -82
- package/src/components/specialized/image-with-fallback/image-with-fallback.story.tsx +0 -105
- package/src/components/specialized/kbd/kbd.story.tsx +0 -113
- package/src/lib/dialog-service/dialog-service.story.tsx +0 -263
- package/src/stories/index.ts +0 -28
- package/src/styles/theme/theme-tokens.story.tsx +0 -157
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
import { defineStory, useBoolean } from '@djangocfg/playground';
|
|
4
|
-
|
|
5
|
-
import { ComboboxAsync, type ComboboxAsyncOption } from '.';
|
|
6
|
-
import { Label } from '../forms/label';
|
|
7
|
-
|
|
8
|
-
export default defineStory({
|
|
9
|
-
title: 'Core/ComboboxAsync',
|
|
10
|
-
component: ComboboxAsync,
|
|
11
|
-
description:
|
|
12
|
-
'Single-select combobox where the parent owns search + loading. Pairs with `MultiSelectProAsync` for the "pick one" case against a server-side typeahead endpoint.',
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
// ─── Fake "server" ──────────────────────────────────────────────────
|
|
16
|
-
//
|
|
17
|
-
// In real usage, ``options`` would come from an SWR / RQ hook keyed off
|
|
18
|
-
// the debounced search value. For the playground we simulate latency
|
|
19
|
-
// with a setTimeout so the loading affordance and seedOptions story
|
|
20
|
-
// can be exercised without a real backend.
|
|
21
|
-
|
|
22
|
-
interface Person {
|
|
23
|
-
id: string;
|
|
24
|
-
name: string;
|
|
25
|
-
email: string;
|
|
26
|
-
team: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const PEOPLE: Person[] = [
|
|
30
|
-
{ id: 'p-1', name: 'Alice Lee', email: 'alice@x.co', team: 'Platform' },
|
|
31
|
-
{ id: 'p-2', name: 'Bob Schmidt', email: 'bob@x.co', team: 'Platform' },
|
|
32
|
-
{ id: 'p-3', name: 'Carla Romero', email: 'carla@x.co', team: 'Growth' },
|
|
33
|
-
{ id: 'p-4', name: 'Dmitri Volkov', email: 'dmitri@x.co', team: 'Growth' },
|
|
34
|
-
{ id: 'p-5', name: 'Evelyn Park', email: 'evelyn@x.co', team: 'Design' },
|
|
35
|
-
{ id: 'p-6', name: 'Farah Patel', email: 'farah@x.co', team: 'Design' },
|
|
36
|
-
{ id: 'p-7', name: 'Greta Hahn', email: 'greta@x.co', team: 'Engineering' },
|
|
37
|
-
{ id: 'p-8', name: 'Hiro Tanaka', email: 'hiro@x.co', team: 'Engineering' },
|
|
38
|
-
];
|
|
39
|
-
|
|
40
|
-
function fakeSearch(query: string): Promise<Person[]> {
|
|
41
|
-
return new Promise((resolve) => {
|
|
42
|
-
const q = query.trim().toLowerCase();
|
|
43
|
-
const matches = q
|
|
44
|
-
? PEOPLE.filter(
|
|
45
|
-
(p) =>
|
|
46
|
-
p.name.toLowerCase().includes(q) ||
|
|
47
|
-
p.email.toLowerCase().includes(q) ||
|
|
48
|
-
p.team.toLowerCase().includes(q),
|
|
49
|
-
)
|
|
50
|
-
: PEOPLE.slice(0, 6);
|
|
51
|
-
setTimeout(() => resolve(matches), 350);
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function toOption(p: Person): ComboboxAsyncOption {
|
|
56
|
-
return {
|
|
57
|
-
value: p.id,
|
|
58
|
-
label: p.name,
|
|
59
|
-
description: `${p.email} · ${p.team}`,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ─── Stories ────────────────────────────────────────────────────────
|
|
64
|
-
|
|
65
|
-
export const Interactive = () => {
|
|
66
|
-
const [value, setValue] = useState<string | null>(null);
|
|
67
|
-
const [search, setSearch] = useState('');
|
|
68
|
-
const [options, setOptions] = useState<ComboboxAsyncOption[]>([]);
|
|
69
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
70
|
-
const [disabled] = useBoolean('disabled', {
|
|
71
|
-
defaultValue: false,
|
|
72
|
-
label: 'Disabled',
|
|
73
|
-
description: 'Disable picker',
|
|
74
|
-
});
|
|
75
|
-
const [clearable] = useBoolean('clearable', {
|
|
76
|
-
defaultValue: true,
|
|
77
|
-
label: 'Clearable',
|
|
78
|
-
description: 'Show inline × to reset selection',
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
let cancelled = false;
|
|
83
|
-
setIsLoading(true);
|
|
84
|
-
void fakeSearch(search).then((rows) => {
|
|
85
|
-
if (cancelled) return;
|
|
86
|
-
setOptions(rows.map(toOption));
|
|
87
|
-
setIsLoading(false);
|
|
88
|
-
});
|
|
89
|
-
return () => {
|
|
90
|
-
cancelled = true;
|
|
91
|
-
};
|
|
92
|
-
}, [search]);
|
|
93
|
-
|
|
94
|
-
return (
|
|
95
|
-
<div className="max-w-sm space-y-2">
|
|
96
|
-
<Label>Assignee</Label>
|
|
97
|
-
<ComboboxAsync
|
|
98
|
-
options={options}
|
|
99
|
-
value={value}
|
|
100
|
-
onValueChange={setValue}
|
|
101
|
-
searchValue={search}
|
|
102
|
-
onSearchChange={setSearch}
|
|
103
|
-
isLoading={isLoading}
|
|
104
|
-
placeholder="Pick a teammate…"
|
|
105
|
-
searchPlaceholder="Search by name, email, team…"
|
|
106
|
-
emptyText="No teammates found"
|
|
107
|
-
loadingText="Searching…"
|
|
108
|
-
disabled={disabled}
|
|
109
|
-
clearable={clearable}
|
|
110
|
-
/>
|
|
111
|
-
<p className="text-xs text-muted-foreground">
|
|
112
|
-
Selected: {value ?? '∅'}
|
|
113
|
-
</p>
|
|
114
|
-
</div>
|
|
115
|
-
);
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
export const Default = Interactive;
|
|
119
|
-
|
|
120
|
-
export const Loading = () => {
|
|
121
|
-
// Force ``isLoading`` true and an empty options list to show the
|
|
122
|
-
// initial spinner state — the same row that renders before the
|
|
123
|
-
// first server response lands.
|
|
124
|
-
const [value, setValue] = useState<string | null>(null);
|
|
125
|
-
const [search, setSearch] = useState('');
|
|
126
|
-
|
|
127
|
-
return (
|
|
128
|
-
<div className="max-w-sm space-y-2">
|
|
129
|
-
<Label>Assignee</Label>
|
|
130
|
-
<ComboboxAsync
|
|
131
|
-
options={[]}
|
|
132
|
-
value={value}
|
|
133
|
-
onValueChange={setValue}
|
|
134
|
-
searchValue={search}
|
|
135
|
-
onSearchChange={setSearch}
|
|
136
|
-
isLoading
|
|
137
|
-
placeholder="Pick a teammate…"
|
|
138
|
-
/>
|
|
139
|
-
</div>
|
|
140
|
-
);
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
export const Empty = () => {
|
|
144
|
-
// Loaded but no matches — distinct visual from the loading row above.
|
|
145
|
-
const [value, setValue] = useState<string | null>(null);
|
|
146
|
-
|
|
147
|
-
return (
|
|
148
|
-
<div className="max-w-sm space-y-2">
|
|
149
|
-
<Label>Assignee</Label>
|
|
150
|
-
<ComboboxAsync
|
|
151
|
-
options={[]}
|
|
152
|
-
value={value}
|
|
153
|
-
onValueChange={setValue}
|
|
154
|
-
searchValue="zzznoresult"
|
|
155
|
-
onSearchChange={() => {}}
|
|
156
|
-
isLoading={false}
|
|
157
|
-
emptyText="No teammates found"
|
|
158
|
-
/>
|
|
159
|
-
</div>
|
|
160
|
-
);
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
export const SeedOption = () => {
|
|
164
|
-
// The dialog opens with a pre-existing value whose row isn't part of
|
|
165
|
-
// the live results. ``seedOptions`` keeps the trigger from rendering
|
|
166
|
-
// a raw id while the user starts typing.
|
|
167
|
-
const [value, setValue] = useState<string | null>('p-stale');
|
|
168
|
-
const [search, setSearch] = useState('');
|
|
169
|
-
const [options, setOptions] = useState<ComboboxAsyncOption[]>([]);
|
|
170
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
171
|
-
|
|
172
|
-
useEffect(() => {
|
|
173
|
-
let cancelled = false;
|
|
174
|
-
setIsLoading(true);
|
|
175
|
-
void fakeSearch(search).then((rows) => {
|
|
176
|
-
if (cancelled) return;
|
|
177
|
-
setOptions(rows.map(toOption));
|
|
178
|
-
setIsLoading(false);
|
|
179
|
-
});
|
|
180
|
-
return () => {
|
|
181
|
-
cancelled = true;
|
|
182
|
-
};
|
|
183
|
-
}, [search]);
|
|
184
|
-
|
|
185
|
-
const seedOptions = useMemo<ComboboxAsyncOption[]>(
|
|
186
|
-
() => [
|
|
187
|
-
{
|
|
188
|
-
value: 'p-stale',
|
|
189
|
-
label: 'Pre-selected (offline)',
|
|
190
|
-
description: 'Fetched once, kept around as a seed',
|
|
191
|
-
},
|
|
192
|
-
],
|
|
193
|
-
[],
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
return (
|
|
197
|
-
<div className="max-w-sm space-y-2">
|
|
198
|
-
<Label>Assignee</Label>
|
|
199
|
-
<ComboboxAsync
|
|
200
|
-
options={options}
|
|
201
|
-
value={value}
|
|
202
|
-
onValueChange={setValue}
|
|
203
|
-
searchValue={search}
|
|
204
|
-
onSearchChange={setSearch}
|
|
205
|
-
isLoading={isLoading}
|
|
206
|
-
seedOptions={seedOptions}
|
|
207
|
-
placeholder="Pick a teammate…"
|
|
208
|
-
/>
|
|
209
|
-
<p className="text-xs text-muted-foreground">
|
|
210
|
-
Selected id: <code>{value ?? '∅'}</code> — note the trigger shows the
|
|
211
|
-
seed label even though <code>p-stale</code> isn't in the search results.
|
|
212
|
-
</p>
|
|
213
|
-
</div>
|
|
214
|
-
);
|
|
215
|
-
};
|
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { defineStory, useBoolean } from '@djangocfg/playground';
|
|
3
|
-
import { Combobox, type ComboboxOption } from '.';
|
|
4
|
-
import { Label } from '../forms/label';
|
|
5
|
-
|
|
6
|
-
export default defineStory({
|
|
7
|
-
title: 'Core/Combobox',
|
|
8
|
-
component: Combobox,
|
|
9
|
-
description: 'Searchable select with autocomplete.',
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
const frameworks: ComboboxOption[] = [
|
|
13
|
-
{ value: 'next.js', label: 'Next.js' },
|
|
14
|
-
{ value: 'sveltekit', label: 'SvelteKit' },
|
|
15
|
-
{ value: 'nuxt.js', label: 'Nuxt.js' },
|
|
16
|
-
{ value: 'remix', label: 'Remix' },
|
|
17
|
-
{ value: 'astro', label: 'Astro' },
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
const countries: ComboboxOption[] = [
|
|
21
|
-
{ value: 'us', label: 'United States' },
|
|
22
|
-
{ value: 'uk', label: 'United Kingdom' },
|
|
23
|
-
{ value: 'de', label: 'Germany' },
|
|
24
|
-
{ value: 'fr', label: 'France' },
|
|
25
|
-
{ value: 'jp', label: 'Japan' },
|
|
26
|
-
{ value: 'kr', label: 'South Korea' },
|
|
27
|
-
{ value: 'cn', label: 'China' },
|
|
28
|
-
{ value: 'au', label: 'Australia' },
|
|
29
|
-
{ value: 'ca', label: 'Canada' },
|
|
30
|
-
{ value: 'br', label: 'Brazil' },
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
export const Interactive = () => {
|
|
34
|
-
const [value, setValue] = useState('');
|
|
35
|
-
const [disabled] = useBoolean('disabled', {
|
|
36
|
-
defaultValue: false,
|
|
37
|
-
label: 'Disabled',
|
|
38
|
-
description: 'Disable combobox',
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<div className="max-w-xs space-y-2">
|
|
43
|
-
<Label>Framework</Label>
|
|
44
|
-
<Combobox
|
|
45
|
-
options={frameworks}
|
|
46
|
-
value={value}
|
|
47
|
-
onValueChange={setValue}
|
|
48
|
-
placeholder="Select framework..."
|
|
49
|
-
searchPlaceholder="Search framework..."
|
|
50
|
-
emptyText="No framework found."
|
|
51
|
-
disabled={disabled}
|
|
52
|
-
/>
|
|
53
|
-
</div>
|
|
54
|
-
);
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export const Default = () => {
|
|
58
|
-
const [value, setValue] = useState('');
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
<div className="max-w-xs">
|
|
62
|
-
<Combobox
|
|
63
|
-
options={frameworks}
|
|
64
|
-
value={value}
|
|
65
|
-
onValueChange={setValue}
|
|
66
|
-
placeholder="Select framework..."
|
|
67
|
-
searchPlaceholder="Search framework..."
|
|
68
|
-
emptyText="No framework found."
|
|
69
|
-
/>
|
|
70
|
-
</div>
|
|
71
|
-
);
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
export const WithDefaultValue = () => {
|
|
75
|
-
const [value, setValue] = useState('next.js');
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<div className="max-w-xs">
|
|
79
|
-
<Combobox
|
|
80
|
-
options={frameworks}
|
|
81
|
-
value={value}
|
|
82
|
-
onValueChange={setValue}
|
|
83
|
-
placeholder="Select framework..."
|
|
84
|
-
/>
|
|
85
|
-
</div>
|
|
86
|
-
);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
export const Countries = () => {
|
|
90
|
-
const [value, setValue] = useState('');
|
|
91
|
-
|
|
92
|
-
return (
|
|
93
|
-
<div className="max-w-xs space-y-2">
|
|
94
|
-
<Label>Country</Label>
|
|
95
|
-
<Combobox
|
|
96
|
-
options={countries}
|
|
97
|
-
value={value}
|
|
98
|
-
onValueChange={setValue}
|
|
99
|
-
placeholder="Select country..."
|
|
100
|
-
searchPlaceholder="Search countries..."
|
|
101
|
-
emptyText="No country found."
|
|
102
|
-
/>
|
|
103
|
-
</div>
|
|
104
|
-
);
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
export const Disabled = () => (
|
|
108
|
-
<div className="max-w-xs">
|
|
109
|
-
<Combobox
|
|
110
|
-
options={frameworks}
|
|
111
|
-
value=""
|
|
112
|
-
onValueChange={() => {}}
|
|
113
|
-
placeholder="Select framework..."
|
|
114
|
-
disabled
|
|
115
|
-
/>
|
|
116
|
-
</div>
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
export const Form = () => {
|
|
120
|
-
const [framework, setFramework] = useState('');
|
|
121
|
-
const [country, setCountry] = useState('');
|
|
122
|
-
|
|
123
|
-
return (
|
|
124
|
-
<div className="max-w-xs space-y-4">
|
|
125
|
-
<div className="space-y-2">
|
|
126
|
-
<Label>Framework</Label>
|
|
127
|
-
<Combobox
|
|
128
|
-
options={frameworks}
|
|
129
|
-
value={framework}
|
|
130
|
-
onValueChange={setFramework}
|
|
131
|
-
placeholder="Select framework..."
|
|
132
|
-
/>
|
|
133
|
-
</div>
|
|
134
|
-
<div className="space-y-2">
|
|
135
|
-
<Label>Country</Label>
|
|
136
|
-
<Combobox
|
|
137
|
-
options={countries}
|
|
138
|
-
value={country}
|
|
139
|
-
onValueChange={setCountry}
|
|
140
|
-
placeholder="Select country..."
|
|
141
|
-
/>
|
|
142
|
-
</div>
|
|
143
|
-
</div>
|
|
144
|
-
);
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
// Trading symbols for custom filter demo
|
|
148
|
-
const symbols: ComboboxOption[] = [
|
|
149
|
-
{ value: 'BTCUSDT', label: 'BTCUSDT', description: 'BTC/USDT' },
|
|
150
|
-
{ value: 'ETHUSDT', label: 'ETHUSDT', description: 'ETH/USDT' },
|
|
151
|
-
{ value: 'ETHBTC', label: 'ETHBTC', description: 'ETH/BTC' },
|
|
152
|
-
{ value: 'BNBUSDT', label: 'BNBUSDT', description: 'BNB/USDT' },
|
|
153
|
-
{ value: 'BNBBTC', label: 'BNBBTC', description: 'BNB/BTC' },
|
|
154
|
-
{ value: 'SOLUSDT', label: 'SOLUSDT', description: 'SOL/USDT' },
|
|
155
|
-
{ value: 'SOLBTC', label: 'SOLBTC', description: 'SOL/BTC' },
|
|
156
|
-
{ value: 'ADAUSDT', label: 'ADAUSDT', description: 'ADA/USDT' },
|
|
157
|
-
{ value: 'DOGEUSDT', label: 'DOGEUSDT', description: 'DOGE/USDT' },
|
|
158
|
-
{ value: 'XRPUSDT', label: 'XRPUSDT', description: 'XRP/USDT' },
|
|
159
|
-
];
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Multi-word filter: all search terms must match.
|
|
163
|
-
* "btc eth" matches ETHBTC (contains both "btc" and "eth")
|
|
164
|
-
*/
|
|
165
|
-
function multiWordFilter(option: ComboboxOption, search: string): boolean {
|
|
166
|
-
const terms = search.toLowerCase().trim().split(/\s+/).filter(Boolean);
|
|
167
|
-
if (terms.length === 0) return true;
|
|
168
|
-
|
|
169
|
-
const text = [option.label, option.value, option.description || '']
|
|
170
|
-
.join(' ')
|
|
171
|
-
.toLowerCase();
|
|
172
|
-
|
|
173
|
-
return terms.every((term) => text.includes(term));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export const CustomFilter = () => {
|
|
177
|
-
const [value, setValue] = useState('');
|
|
178
|
-
|
|
179
|
-
return (
|
|
180
|
-
<div className="max-w-xs space-y-2">
|
|
181
|
-
<Label>Symbol (try "btc eth" or "usdt sol")</Label>
|
|
182
|
-
<Combobox
|
|
183
|
-
options={symbols}
|
|
184
|
-
value={value}
|
|
185
|
-
onValueChange={setValue}
|
|
186
|
-
placeholder="Select symbol..."
|
|
187
|
-
searchPlaceholder="Search... (e.g. btc usdt)"
|
|
188
|
-
emptyText="No symbols found."
|
|
189
|
-
filterFunction={multiWordFilter}
|
|
190
|
-
renderOption={(option) => (
|
|
191
|
-
<div className="flex items-center justify-between w-full">
|
|
192
|
-
<span className="font-medium">{option.label}</span>
|
|
193
|
-
<span className="text-xs text-muted-foreground">{option.description}</span>
|
|
194
|
-
</div>
|
|
195
|
-
)}
|
|
196
|
-
/>
|
|
197
|
-
<p className="text-xs text-muted-foreground">
|
|
198
|
-
Custom filter: space-separated terms (all must match)
|
|
199
|
-
</p>
|
|
200
|
-
</div>
|
|
201
|
-
);
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
export const WithDescriptions = () => {
|
|
205
|
-
const [value, setValue] = useState('');
|
|
206
|
-
|
|
207
|
-
return (
|
|
208
|
-
<div className="max-w-xs space-y-2">
|
|
209
|
-
<Label>Trading Pair</Label>
|
|
210
|
-
<Combobox
|
|
211
|
-
options={symbols}
|
|
212
|
-
value={value}
|
|
213
|
-
onValueChange={setValue}
|
|
214
|
-
placeholder="Select trading pair..."
|
|
215
|
-
searchPlaceholder="Search pairs..."
|
|
216
|
-
emptyText="No pairs found."
|
|
217
|
-
renderOption={(option) => (
|
|
218
|
-
<div className="flex items-center justify-between w-full">
|
|
219
|
-
<span className="font-medium">{option.label}</span>
|
|
220
|
-
<span className="text-xs text-muted-foreground">{option.description}</span>
|
|
221
|
-
</div>
|
|
222
|
-
)}
|
|
223
|
-
/>
|
|
224
|
-
</div>
|
|
225
|
-
);
|
|
226
|
-
};
|
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { defineStory } from '@djangocfg/playground';
|
|
3
|
-
import { CountrySelect, type TCountryCode } from './country-select';
|
|
4
|
-
import { Label } from '../forms/label';
|
|
5
|
-
|
|
6
|
-
export default defineStory({
|
|
7
|
-
title: 'Core/CountrySelect',
|
|
8
|
-
component: CountrySelect,
|
|
9
|
-
description: 'Country selector with emoji flags. Supports dropdown and inline variants, single and multiple selection.',
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
// ============================================================================
|
|
13
|
-
// Dropdown Variants
|
|
14
|
-
// ============================================================================
|
|
15
|
-
|
|
16
|
-
export const SingleDropdown = () => {
|
|
17
|
-
const [value, setValue] = useState<string[]>([]);
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<div className="max-w-sm space-y-2">
|
|
21
|
-
<Label>Select country</Label>
|
|
22
|
-
<CountrySelect
|
|
23
|
-
value={value}
|
|
24
|
-
onChange={setValue}
|
|
25
|
-
placeholder="Choose a country..."
|
|
26
|
-
/>
|
|
27
|
-
{value.length > 0 && (
|
|
28
|
-
<p className="text-sm text-muted-foreground">Selected: {value[0]}</p>
|
|
29
|
-
)}
|
|
30
|
-
</div>
|
|
31
|
-
);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export const MultipleDropdown = () => {
|
|
35
|
-
const [value, setValue] = useState<string[]>([]);
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<div className="max-w-sm space-y-2">
|
|
39
|
-
<Label>Select countries</Label>
|
|
40
|
-
<CountrySelect
|
|
41
|
-
multiple
|
|
42
|
-
value={value}
|
|
43
|
-
onChange={setValue}
|
|
44
|
-
placeholder="Choose countries..."
|
|
45
|
-
/>
|
|
46
|
-
{value.length > 0 && (
|
|
47
|
-
<p className="text-sm text-muted-foreground">Selected: {value.join(', ')}</p>
|
|
48
|
-
)}
|
|
49
|
-
</div>
|
|
50
|
-
);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export const WithDefaultValue = () => {
|
|
54
|
-
const [value, setValue] = useState<string[]>(['US', 'GB', 'DE']);
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<div className="max-w-sm space-y-2">
|
|
58
|
-
<Label>Preselected countries</Label>
|
|
59
|
-
<CountrySelect
|
|
60
|
-
multiple
|
|
61
|
-
value={value}
|
|
62
|
-
onChange={setValue}
|
|
63
|
-
/>
|
|
64
|
-
</div>
|
|
65
|
-
);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export const WithNativeNames = () => {
|
|
69
|
-
const [value, setValue] = useState<string[]>([]);
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<div className="max-w-sm space-y-2">
|
|
73
|
-
<Label>With native names</Label>
|
|
74
|
-
<CountrySelect
|
|
75
|
-
multiple
|
|
76
|
-
value={value}
|
|
77
|
-
onChange={setValue}
|
|
78
|
-
showNativeName
|
|
79
|
-
/>
|
|
80
|
-
</div>
|
|
81
|
-
);
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
// ============================================================================
|
|
85
|
-
// Inline Variants
|
|
86
|
-
// ============================================================================
|
|
87
|
-
|
|
88
|
-
export const InlineSingle = () => {
|
|
89
|
-
const [value, setValue] = useState<string[]>([]);
|
|
90
|
-
|
|
91
|
-
return (
|
|
92
|
-
<div className="max-w-sm space-y-2">
|
|
93
|
-
<Label>Select your country</Label>
|
|
94
|
-
<CountrySelect
|
|
95
|
-
variant="inline"
|
|
96
|
-
value={value}
|
|
97
|
-
onChange={setValue}
|
|
98
|
-
maxHeight={250}
|
|
99
|
-
/>
|
|
100
|
-
{value.length > 0 && (
|
|
101
|
-
<p className="text-sm text-muted-foreground">Selected: {value[0]}</p>
|
|
102
|
-
)}
|
|
103
|
-
</div>
|
|
104
|
-
);
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
export const InlineMultiple = () => {
|
|
108
|
-
const [value, setValue] = useState<string[]>([]);
|
|
109
|
-
|
|
110
|
-
return (
|
|
111
|
-
<div className="max-w-sm space-y-2">
|
|
112
|
-
<Label>Select target markets</Label>
|
|
113
|
-
<CountrySelect
|
|
114
|
-
variant="inline"
|
|
115
|
-
multiple
|
|
116
|
-
value={value}
|
|
117
|
-
onChange={setValue}
|
|
118
|
-
maxHeight={300}
|
|
119
|
-
/>
|
|
120
|
-
</div>
|
|
121
|
-
);
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
export const InlineWithNativeNames = () => {
|
|
125
|
-
const [value, setValue] = useState<string[]>(['KR', 'JP']);
|
|
126
|
-
|
|
127
|
-
return (
|
|
128
|
-
<div className="max-w-sm space-y-2">
|
|
129
|
-
<Label>Countries with native names</Label>
|
|
130
|
-
<CountrySelect
|
|
131
|
-
variant="inline"
|
|
132
|
-
multiple
|
|
133
|
-
value={value}
|
|
134
|
-
onChange={setValue}
|
|
135
|
-
showNativeName
|
|
136
|
-
maxHeight={300}
|
|
137
|
-
/>
|
|
138
|
-
</div>
|
|
139
|
-
);
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
export const InlineNoSearch = () => {
|
|
143
|
-
const [value, setValue] = useState<string[]>([]);
|
|
144
|
-
|
|
145
|
-
return (
|
|
146
|
-
<div className="max-w-sm space-y-2">
|
|
147
|
-
<Label>Without search</Label>
|
|
148
|
-
<CountrySelect
|
|
149
|
-
variant="inline"
|
|
150
|
-
multiple
|
|
151
|
-
value={value}
|
|
152
|
-
onChange={setValue}
|
|
153
|
-
showSearch={false}
|
|
154
|
-
maxHeight={200}
|
|
155
|
-
/>
|
|
156
|
-
</div>
|
|
157
|
-
);
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
// ============================================================================
|
|
161
|
-
// Filtered Countries
|
|
162
|
-
// ============================================================================
|
|
163
|
-
|
|
164
|
-
const CIS_COUNTRIES: TCountryCode[] = ['RU', 'KZ', 'UZ', 'KG', 'TJ', 'TM', 'AZ', 'AM', 'GE', 'BY', 'MD', 'UA'];
|
|
165
|
-
const ASIAN_AUTO_MARKETS: TCountryCode[] = ['KR', 'JP', 'CN', 'TH', 'MY', 'ID', 'VN', 'PH'];
|
|
166
|
-
|
|
167
|
-
export const FilteredCISCountries = () => {
|
|
168
|
-
const [value, setValue] = useState<string[]>([]);
|
|
169
|
-
|
|
170
|
-
return (
|
|
171
|
-
<div className="max-w-sm space-y-2">
|
|
172
|
-
<Label>CIS Countries only</Label>
|
|
173
|
-
<CountrySelect
|
|
174
|
-
variant="inline"
|
|
175
|
-
multiple
|
|
176
|
-
value={value}
|
|
177
|
-
onChange={setValue}
|
|
178
|
-
allowedCountries={CIS_COUNTRIES}
|
|
179
|
-
maxHeight={300}
|
|
180
|
-
/>
|
|
181
|
-
</div>
|
|
182
|
-
);
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
export const FilteredAsianMarkets = () => {
|
|
186
|
-
const [value, setValue] = useState<string[]>([]);
|
|
187
|
-
|
|
188
|
-
return (
|
|
189
|
-
<div className="max-w-sm space-y-2">
|
|
190
|
-
<Label>Asian Auto Markets</Label>
|
|
191
|
-
<CountrySelect
|
|
192
|
-
multiple
|
|
193
|
-
value={value}
|
|
194
|
-
onChange={setValue}
|
|
195
|
-
allowedCountries={ASIAN_AUTO_MARKETS}
|
|
196
|
-
showNativeName
|
|
197
|
-
/>
|
|
198
|
-
</div>
|
|
199
|
-
);
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
export const ExcludeSanctioned = () => {
|
|
203
|
-
const [value, setValue] = useState<string[]>([]);
|
|
204
|
-
const excluded: TCountryCode[] = ['KP', 'IR', 'SY', 'CU'];
|
|
205
|
-
|
|
206
|
-
return (
|
|
207
|
-
<div className="max-w-sm space-y-2">
|
|
208
|
-
<Label>All except sanctioned</Label>
|
|
209
|
-
<CountrySelect
|
|
210
|
-
multiple
|
|
211
|
-
value={value}
|
|
212
|
-
onChange={setValue}
|
|
213
|
-
excludedCountries={excluded}
|
|
214
|
-
/>
|
|
215
|
-
</div>
|
|
216
|
-
);
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
// ============================================================================
|
|
220
|
-
// States
|
|
221
|
-
// ============================================================================
|
|
222
|
-
|
|
223
|
-
export const Disabled = () => (
|
|
224
|
-
<div className="max-w-sm space-y-4">
|
|
225
|
-
<div className="space-y-2">
|
|
226
|
-
<Label>Disabled dropdown</Label>
|
|
227
|
-
<CountrySelect
|
|
228
|
-
value={['US']}
|
|
229
|
-
onChange={() => {}}
|
|
230
|
-
disabled
|
|
231
|
-
/>
|
|
232
|
-
</div>
|
|
233
|
-
<div className="space-y-2">
|
|
234
|
-
<Label>Disabled inline</Label>
|
|
235
|
-
<CountrySelect
|
|
236
|
-
variant="inline"
|
|
237
|
-
multiple
|
|
238
|
-
value={['US', 'GB']}
|
|
239
|
-
onChange={() => {}}
|
|
240
|
-
disabled
|
|
241
|
-
maxHeight={150}
|
|
242
|
-
/>
|
|
243
|
-
</div>
|
|
244
|
-
</div>
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
export const MaxDisplayBadges = () => {
|
|
248
|
-
const [value, setValue] = useState<string[]>(['US', 'GB', 'DE', 'FR', 'IT', 'ES']);
|
|
249
|
-
|
|
250
|
-
return (
|
|
251
|
-
<div className="max-w-sm space-y-2">
|
|
252
|
-
<Label>Max 2 badges displayed</Label>
|
|
253
|
-
<CountrySelect
|
|
254
|
-
multiple
|
|
255
|
-
value={value}
|
|
256
|
-
onChange={setValue}
|
|
257
|
-
maxDisplay={2}
|
|
258
|
-
/>
|
|
259
|
-
</div>
|
|
260
|
-
);
|
|
261
|
-
};
|