@noxlovette/material 0.8.2 → 0.9.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.
- package/claude-skill/material-design/SKILL.md +1 -1
- package/claude-skill/material-design/references/component-patterns.md +36 -1
- package/dist/animation/containerTransform.js +21 -3
- package/dist/components/containers/list/theme.js +1 -1
- package/dist/components/containers/pane/theme.js +1 -1
- package/dist/components/date/theme.js +1 -1
- package/dist/components/forms/command/Command.mdx +90 -0
- package/dist/components/forms/command/Command.stories.svelte +69 -10
- package/dist/components/forms/command/Command.stories.svelte.d.ts +2 -17
- package/dist/components/forms/command/CommandDialog.svelte +58 -0
- package/dist/components/forms/command/CommandDialog.svelte.d.ts +11 -0
- package/dist/components/forms/command/CommandItem.svelte +48 -5
- package/dist/components/forms/command/CommandItem.svelte.d.ts +4 -0
- package/dist/components/forms/command/index.d.ts +1 -0
- package/dist/components/forms/command/index.js +1 -0
- package/dist/components/forms/command/theme.d.ts +371 -6
- package/dist/components/forms/command/theme.js +32 -1
- package/dist/components/forms/command/types.d.ts +27 -1
- package/dist/components/forms/search/Search.mdx +9 -0
- package/dist/components/forms/search/Search.stories.svelte +14 -2
- package/dist/components/forms/search/Search.svelte +41 -9
- package/dist/components/forms/search/Search.svelte.d.ts +3 -0
- package/dist/components/forms/search/SearchView.svelte +7 -2
- package/dist/components/forms/search/SearchView.svelte.d.ts +2 -1
- package/dist/components/forms/search/theme.js +4 -2
- package/dist/components/forms/search/types.d.ts +14 -1
- package/dist/components/forms/slider/theme.js +1 -1
- package/dist/components/nav/appbar/AppBar.stories.svelte +30 -2
- package/dist/components/nav/appbar/AppBar.svelte +40 -8
- package/dist/components/nav/appbar/types.d.ts +14 -0
- package/dist/components/nav/rail/Rail.mdx +6 -0
- package/dist/components/nav/rail/Rail.svelte +55 -1
- package/dist/components/nav/rail/Rail.svelte.d.ts +3 -0
- package/dist/components/nav/rail/types.d.ts +7 -0
- package/dist/styles/motion.css +6 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/shortcut.d.ts +26 -0
- package/dist/utils/shortcut.js +84 -0
- package/package.json +1 -1
|
@@ -3,7 +3,6 @@ export type CommandVariants = VariantProps<typeof command>;
|
|
|
3
3
|
export declare const command: import("tailwind-variants").TVReturnType<{
|
|
4
4
|
[key: string]: {
|
|
5
5
|
[key: string]: import("tailwind-variants").ClassValue | {
|
|
6
|
-
item?: import("tailwind-variants").ClassValue;
|
|
7
6
|
base?: import("tailwind-variants").ClassValue;
|
|
8
7
|
input?: import("tailwind-variants").ClassValue;
|
|
9
8
|
group?: import("tailwind-variants").ClassValue;
|
|
@@ -18,7 +17,6 @@ export declare const command: import("tailwind-variants").TVReturnType<{
|
|
|
18
17
|
} | {
|
|
19
18
|
[x: string]: {
|
|
20
19
|
[x: string]: import("tailwind-variants").ClassValue | {
|
|
21
|
-
item?: import("tailwind-variants").ClassValue;
|
|
22
20
|
base?: import("tailwind-variants").ClassValue;
|
|
23
21
|
input?: import("tailwind-variants").ClassValue;
|
|
24
22
|
group?: import("tailwind-variants").ClassValue;
|
|
@@ -39,12 +37,10 @@ export declare const command: import("tailwind-variants").TVReturnType<{
|
|
|
39
37
|
empty: string;
|
|
40
38
|
group: string;
|
|
41
39
|
separator: string;
|
|
42
|
-
item: string;
|
|
43
40
|
itemIcon: string;
|
|
44
41
|
}, undefined, {
|
|
45
42
|
[key: string]: {
|
|
46
43
|
[key: string]: import("tailwind-variants").ClassValue | {
|
|
47
|
-
item?: import("tailwind-variants").ClassValue;
|
|
48
44
|
base?: import("tailwind-variants").ClassValue;
|
|
49
45
|
input?: import("tailwind-variants").ClassValue;
|
|
50
46
|
group?: import("tailwind-variants").ClassValue;
|
|
@@ -65,7 +61,6 @@ export declare const command: import("tailwind-variants").TVReturnType<{
|
|
|
65
61
|
empty: string;
|
|
66
62
|
group: string;
|
|
67
63
|
separator: string;
|
|
68
|
-
item: string;
|
|
69
64
|
itemIcon: string;
|
|
70
65
|
}, import("tailwind-variants").TVReturnTypeLike<unknown, {
|
|
71
66
|
base: string;
|
|
@@ -76,6 +71,376 @@ export declare const command: import("tailwind-variants").TVReturnType<{
|
|
|
76
71
|
empty: string;
|
|
77
72
|
group: string;
|
|
78
73
|
separator: string;
|
|
79
|
-
item: string;
|
|
80
74
|
itemIcon: string;
|
|
81
75
|
}>>;
|
|
76
|
+
export declare const commandItem: import("tailwind-variants").TVReturnType<{
|
|
77
|
+
variant: {
|
|
78
|
+
standard: {
|
|
79
|
+
base: string;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
}, {
|
|
83
|
+
base: string[];
|
|
84
|
+
}, undefined, {
|
|
85
|
+
lines: {
|
|
86
|
+
1: {
|
|
87
|
+
base: string;
|
|
88
|
+
};
|
|
89
|
+
2: {
|
|
90
|
+
base: string;
|
|
91
|
+
};
|
|
92
|
+
3: {
|
|
93
|
+
base: string;
|
|
94
|
+
leading: string;
|
|
95
|
+
trailing: string;
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
variant: {
|
|
99
|
+
standard: {
|
|
100
|
+
base: string;
|
|
101
|
+
nested: string;
|
|
102
|
+
};
|
|
103
|
+
segmented: {
|
|
104
|
+
base: string;
|
|
105
|
+
nested: string;
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
interactive: {
|
|
109
|
+
true: {
|
|
110
|
+
base: string[];
|
|
111
|
+
};
|
|
112
|
+
false: "";
|
|
113
|
+
};
|
|
114
|
+
selected: {
|
|
115
|
+
true: {
|
|
116
|
+
base: string;
|
|
117
|
+
leading: string;
|
|
118
|
+
overline: string;
|
|
119
|
+
headline: string;
|
|
120
|
+
supporting: string;
|
|
121
|
+
trailing: string;
|
|
122
|
+
trailingText: string;
|
|
123
|
+
};
|
|
124
|
+
false: "";
|
|
125
|
+
};
|
|
126
|
+
disabled: {
|
|
127
|
+
true: {
|
|
128
|
+
base: string;
|
|
129
|
+
leading: string;
|
|
130
|
+
overline: string;
|
|
131
|
+
headline: string;
|
|
132
|
+
supporting: string;
|
|
133
|
+
trailing: string;
|
|
134
|
+
trailingText: string;
|
|
135
|
+
};
|
|
136
|
+
false: "";
|
|
137
|
+
};
|
|
138
|
+
expanded: {
|
|
139
|
+
true: {
|
|
140
|
+
base: string;
|
|
141
|
+
expandIcon: string;
|
|
142
|
+
region: string;
|
|
143
|
+
nested: string;
|
|
144
|
+
};
|
|
145
|
+
false: {
|
|
146
|
+
expandIcon: string;
|
|
147
|
+
region: string;
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
}, {
|
|
151
|
+
base: string[];
|
|
152
|
+
selection: string;
|
|
153
|
+
leading: string;
|
|
154
|
+
body: string;
|
|
155
|
+
overline: string;
|
|
156
|
+
headline: string;
|
|
157
|
+
supporting: string;
|
|
158
|
+
trailing: string;
|
|
159
|
+
trailingText: string;
|
|
160
|
+
expandIcon: string;
|
|
161
|
+
group: string;
|
|
162
|
+
region: string;
|
|
163
|
+
regionInner: string;
|
|
164
|
+
nested: string;
|
|
165
|
+
}, import("tailwind-variants").TVReturnType<{
|
|
166
|
+
lines: {
|
|
167
|
+
1: {
|
|
168
|
+
base: string;
|
|
169
|
+
};
|
|
170
|
+
2: {
|
|
171
|
+
base: string;
|
|
172
|
+
};
|
|
173
|
+
3: {
|
|
174
|
+
base: string;
|
|
175
|
+
leading: string;
|
|
176
|
+
trailing: string;
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
variant: {
|
|
180
|
+
standard: {
|
|
181
|
+
base: string;
|
|
182
|
+
nested: string;
|
|
183
|
+
};
|
|
184
|
+
segmented: {
|
|
185
|
+
base: string;
|
|
186
|
+
nested: string;
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
interactive: {
|
|
190
|
+
true: {
|
|
191
|
+
base: string[];
|
|
192
|
+
};
|
|
193
|
+
false: "";
|
|
194
|
+
};
|
|
195
|
+
selected: {
|
|
196
|
+
true: {
|
|
197
|
+
base: string;
|
|
198
|
+
leading: string;
|
|
199
|
+
overline: string;
|
|
200
|
+
headline: string;
|
|
201
|
+
supporting: string;
|
|
202
|
+
trailing: string;
|
|
203
|
+
trailingText: string;
|
|
204
|
+
};
|
|
205
|
+
false: "";
|
|
206
|
+
};
|
|
207
|
+
disabled: {
|
|
208
|
+
true: {
|
|
209
|
+
base: string;
|
|
210
|
+
leading: string;
|
|
211
|
+
overline: string;
|
|
212
|
+
headline: string;
|
|
213
|
+
supporting: string;
|
|
214
|
+
trailing: string;
|
|
215
|
+
trailingText: string;
|
|
216
|
+
};
|
|
217
|
+
false: "";
|
|
218
|
+
};
|
|
219
|
+
expanded: {
|
|
220
|
+
true: {
|
|
221
|
+
base: string;
|
|
222
|
+
expandIcon: string;
|
|
223
|
+
region: string;
|
|
224
|
+
nested: string;
|
|
225
|
+
};
|
|
226
|
+
false: {
|
|
227
|
+
expandIcon: string;
|
|
228
|
+
region: string;
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
}, {
|
|
232
|
+
base: string[];
|
|
233
|
+
selection: string;
|
|
234
|
+
leading: string;
|
|
235
|
+
body: string;
|
|
236
|
+
overline: string;
|
|
237
|
+
headline: string;
|
|
238
|
+
supporting: string;
|
|
239
|
+
trailing: string;
|
|
240
|
+
trailingText: string;
|
|
241
|
+
expandIcon: string;
|
|
242
|
+
group: string;
|
|
243
|
+
region: string;
|
|
244
|
+
regionInner: string;
|
|
245
|
+
nested: string;
|
|
246
|
+
}, undefined, {
|
|
247
|
+
lines: {
|
|
248
|
+
1: {
|
|
249
|
+
base: string;
|
|
250
|
+
};
|
|
251
|
+
2: {
|
|
252
|
+
base: string;
|
|
253
|
+
};
|
|
254
|
+
3: {
|
|
255
|
+
base: string;
|
|
256
|
+
leading: string;
|
|
257
|
+
trailing: string;
|
|
258
|
+
};
|
|
259
|
+
};
|
|
260
|
+
variant: {
|
|
261
|
+
standard: {
|
|
262
|
+
base: string;
|
|
263
|
+
nested: string;
|
|
264
|
+
};
|
|
265
|
+
segmented: {
|
|
266
|
+
base: string;
|
|
267
|
+
nested: string;
|
|
268
|
+
};
|
|
269
|
+
};
|
|
270
|
+
interactive: {
|
|
271
|
+
true: {
|
|
272
|
+
base: string[];
|
|
273
|
+
};
|
|
274
|
+
false: "";
|
|
275
|
+
};
|
|
276
|
+
selected: {
|
|
277
|
+
true: {
|
|
278
|
+
base: string;
|
|
279
|
+
leading: string;
|
|
280
|
+
overline: string;
|
|
281
|
+
headline: string;
|
|
282
|
+
supporting: string;
|
|
283
|
+
trailing: string;
|
|
284
|
+
trailingText: string;
|
|
285
|
+
};
|
|
286
|
+
false: "";
|
|
287
|
+
};
|
|
288
|
+
disabled: {
|
|
289
|
+
true: {
|
|
290
|
+
base: string;
|
|
291
|
+
leading: string;
|
|
292
|
+
overline: string;
|
|
293
|
+
headline: string;
|
|
294
|
+
supporting: string;
|
|
295
|
+
trailing: string;
|
|
296
|
+
trailingText: string;
|
|
297
|
+
};
|
|
298
|
+
false: "";
|
|
299
|
+
};
|
|
300
|
+
expanded: {
|
|
301
|
+
true: {
|
|
302
|
+
base: string;
|
|
303
|
+
expandIcon: string;
|
|
304
|
+
region: string;
|
|
305
|
+
nested: string;
|
|
306
|
+
};
|
|
307
|
+
false: {
|
|
308
|
+
expandIcon: string;
|
|
309
|
+
region: string;
|
|
310
|
+
};
|
|
311
|
+
};
|
|
312
|
+
}, {
|
|
313
|
+
base: string[];
|
|
314
|
+
selection: string;
|
|
315
|
+
leading: string;
|
|
316
|
+
body: string;
|
|
317
|
+
overline: string;
|
|
318
|
+
headline: string;
|
|
319
|
+
supporting: string;
|
|
320
|
+
trailing: string;
|
|
321
|
+
trailingText: string;
|
|
322
|
+
expandIcon: string;
|
|
323
|
+
group: string;
|
|
324
|
+
region: string;
|
|
325
|
+
regionInner: string;
|
|
326
|
+
nested: string;
|
|
327
|
+
}, import("tailwind-variants").TVReturnTypeLike<{
|
|
328
|
+
lines: {
|
|
329
|
+
1: {
|
|
330
|
+
base: string;
|
|
331
|
+
};
|
|
332
|
+
2: {
|
|
333
|
+
base: string;
|
|
334
|
+
};
|
|
335
|
+
3: {
|
|
336
|
+
base: string;
|
|
337
|
+
leading: string;
|
|
338
|
+
trailing: string;
|
|
339
|
+
};
|
|
340
|
+
};
|
|
341
|
+
variant: {
|
|
342
|
+
standard: {
|
|
343
|
+
base: string;
|
|
344
|
+
nested: string;
|
|
345
|
+
};
|
|
346
|
+
segmented: {
|
|
347
|
+
base: string;
|
|
348
|
+
nested: string;
|
|
349
|
+
};
|
|
350
|
+
};
|
|
351
|
+
interactive: {
|
|
352
|
+
true: {
|
|
353
|
+
base: string[];
|
|
354
|
+
};
|
|
355
|
+
false: "";
|
|
356
|
+
};
|
|
357
|
+
selected: {
|
|
358
|
+
true: {
|
|
359
|
+
base: string;
|
|
360
|
+
leading: string;
|
|
361
|
+
overline: string;
|
|
362
|
+
headline: string;
|
|
363
|
+
supporting: string;
|
|
364
|
+
trailing: string;
|
|
365
|
+
trailingText: string;
|
|
366
|
+
};
|
|
367
|
+
false: "";
|
|
368
|
+
};
|
|
369
|
+
disabled: {
|
|
370
|
+
true: {
|
|
371
|
+
base: string;
|
|
372
|
+
leading: string;
|
|
373
|
+
overline: string;
|
|
374
|
+
headline: string;
|
|
375
|
+
supporting: string;
|
|
376
|
+
trailing: string;
|
|
377
|
+
trailingText: string;
|
|
378
|
+
};
|
|
379
|
+
false: "";
|
|
380
|
+
};
|
|
381
|
+
expanded: {
|
|
382
|
+
true: {
|
|
383
|
+
base: string;
|
|
384
|
+
expandIcon: string;
|
|
385
|
+
region: string;
|
|
386
|
+
nested: string;
|
|
387
|
+
};
|
|
388
|
+
false: {
|
|
389
|
+
expandIcon: string;
|
|
390
|
+
region: string;
|
|
391
|
+
};
|
|
392
|
+
};
|
|
393
|
+
}, {
|
|
394
|
+
base: string[];
|
|
395
|
+
selection: string;
|
|
396
|
+
leading: string;
|
|
397
|
+
body: string;
|
|
398
|
+
overline: string;
|
|
399
|
+
headline: string;
|
|
400
|
+
supporting: string;
|
|
401
|
+
trailing: string;
|
|
402
|
+
trailingText: string;
|
|
403
|
+
expandIcon: string;
|
|
404
|
+
group: string;
|
|
405
|
+
region: string;
|
|
406
|
+
regionInner: string;
|
|
407
|
+
nested: string;
|
|
408
|
+
}>>>;
|
|
409
|
+
export type CommandItemVariants = VariantProps<typeof commandItem>;
|
|
410
|
+
export declare const commandDialog: import("tailwind-variants").TVReturnType<{
|
|
411
|
+
[key: string]: {
|
|
412
|
+
[key: string]: import("tailwind-variants").ClassValue | {
|
|
413
|
+
content?: import("tailwind-variants").ClassValue;
|
|
414
|
+
command?: import("tailwind-variants").ClassValue;
|
|
415
|
+
scrim?: import("tailwind-variants").ClassValue;
|
|
416
|
+
};
|
|
417
|
+
};
|
|
418
|
+
} | {
|
|
419
|
+
[x: string]: {
|
|
420
|
+
[x: string]: import("tailwind-variants").ClassValue | {
|
|
421
|
+
content?: import("tailwind-variants").ClassValue;
|
|
422
|
+
command?: import("tailwind-variants").ClassValue;
|
|
423
|
+
scrim?: import("tailwind-variants").ClassValue;
|
|
424
|
+
};
|
|
425
|
+
};
|
|
426
|
+
} | {}, {
|
|
427
|
+
scrim: string;
|
|
428
|
+
content: string;
|
|
429
|
+
command: string;
|
|
430
|
+
}, undefined, {
|
|
431
|
+
[key: string]: {
|
|
432
|
+
[key: string]: import("tailwind-variants").ClassValue | {
|
|
433
|
+
content?: import("tailwind-variants").ClassValue;
|
|
434
|
+
command?: import("tailwind-variants").ClassValue;
|
|
435
|
+
scrim?: import("tailwind-variants").ClassValue;
|
|
436
|
+
};
|
|
437
|
+
};
|
|
438
|
+
} | {}, {
|
|
439
|
+
scrim: string;
|
|
440
|
+
content: string;
|
|
441
|
+
command: string;
|
|
442
|
+
}, import("tailwind-variants").TVReturnTypeLike<unknown, {
|
|
443
|
+
scrim: string;
|
|
444
|
+
content: string;
|
|
445
|
+
command: string;
|
|
446
|
+
}>>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { tv } from '../../../utils/tv.js';
|
|
2
|
+
import { listItem } from '../../containers/list/theme.js';
|
|
2
3
|
export const command = tv({
|
|
3
4
|
slots: {
|
|
4
5
|
base: 'flex h-full w-full flex-col overflow-hidden rounded-2xl bg-md-sys-color-surface-container-high text-md-sys-color-on-surface shadow-elevation-3 ring-1 ring-md-sys-color-outline/20',
|
|
@@ -9,7 +10,37 @@ export const command = tv({
|
|
|
9
10
|
empty: 'py-spacing-300 text-center md-sys-typescale-body-medium text-md-sys-color-on-surface-variant',
|
|
10
11
|
group: 'overflow-hidden p-spacing-50 text-md-sys-color-on-surface-variant [&_[data-command-group-heading]]:px-spacing-150 [&_[data-command-group-heading]]:py-spacing-100 [&_[data-command-group-heading]]:md-sys-typescale-label-medium [&_[data-command-group-heading]]:text-md-sys-color-on-surface-variant',
|
|
11
12
|
separator: '-mx-spacing-50 h-px bg-md-sys-color-outline-variant',
|
|
12
|
-
item: 'relative flex cursor-default select-none items-center rounded-lg px-spacing-150 py-spacing-150 md-sys-typescale-body-medium outline-none data-[selected=true]:bg-md-sys-color-secondary-container data-[selected=true]:text-md-sys-color-on-secondary-container data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50 transition-colors md-sys-motion-fast-effects',
|
|
13
13
|
itemIcon: 'mr-spacing-150 h-spacing-250 w-spacing-250 shrink-0'
|
|
14
14
|
}
|
|
15
15
|
});
|
|
16
|
+
/*
|
|
17
|
+
Palette items are M3 list items (ListItem's anatomy, shape and type), sitting on the palette's
|
|
18
|
+
container colour. Focus stays in the input, so the item the arrow keys or pointer are on
|
|
19
|
+
(bits-ui's `data-selected`, an empty attribute) takes the focused item's state: a 10% on-surface
|
|
20
|
+
fill and the 12dp hover shape, the way menus show their highlighted item. A fill, not Layer's
|
|
21
|
+
tint: Layer's styles are unlayered component CSS, which beats any Tailwind utility.
|
|
22
|
+
*/
|
|
23
|
+
export const commandItem = tv({
|
|
24
|
+
extend: listItem,
|
|
25
|
+
slots: {
|
|
26
|
+
base: [
|
|
27
|
+
'cursor-default select-none outline-none',
|
|
28
|
+
'data-selected:[--li-shape:0.75rem] data-selected:bg-md-sys-color-on-surface/10'
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
// The list's own surface fill is a variant, so it's replaced here, not in the base.
|
|
32
|
+
variants: { variant: { standard: { base: 'bg-transparent' } } },
|
|
33
|
+
defaultVariants: { interactive: true, lines: 1 }
|
|
34
|
+
});
|
|
35
|
+
/*
|
|
36
|
+
The ⌘K palette. M3 has no command palette; it borrows the docked search view's width (360–720dp)
|
|
37
|
+
and a modal dialog's scrim, extra-large shape and motion, and sits in the top third of the
|
|
38
|
+
window so the list has room to grow downwards.
|
|
39
|
+
*/
|
|
40
|
+
export const commandDialog = tv({
|
|
41
|
+
slots: {
|
|
42
|
+
scrim: 'fixed inset-spacing-0 z-layer-modal bg-md-sys-color-scrim/32',
|
|
43
|
+
content: 'fixed inset-x-spacing-200 top-spacing-200 md:top-spacing-900 z-layer-modal mx-auto max-w-(--md-comp-search-view-contained-docked-container-width-maximum) outline-none',
|
|
44
|
+
command: 'h-auto rounded-xl'
|
|
45
|
+
}
|
|
46
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Shortcut } from '../../../utils/index.js';
|
|
1
2
|
import type { Command } from 'bits-ui';
|
|
2
3
|
import type { Snippet } from 'svelte';
|
|
3
4
|
import type { CommandVariants } from './theme.js';
|
|
@@ -13,9 +14,21 @@ export interface CommandListProps extends Command.ListProps {
|
|
|
13
14
|
class?: string;
|
|
14
15
|
children: Snippet;
|
|
15
16
|
}
|
|
16
|
-
export interface CommandItemProps extends Command.ItemProps {
|
|
17
|
+
export interface CommandItemProps extends Omit<Command.ItemProps, 'child' | 'children'> {
|
|
17
18
|
class?: string;
|
|
19
|
+
/** The headline. */
|
|
18
20
|
children: Snippet;
|
|
21
|
+
/** A leading icon, e.g. `<Icon name="settings" />`. */
|
|
22
|
+
leading?: Snippet;
|
|
23
|
+
/** A second line under the headline. */
|
|
24
|
+
supporting?: string;
|
|
25
|
+
/** Trailing label, e.g. a count. `shortcut` replaces it. */
|
|
26
|
+
trailingText?: string;
|
|
27
|
+
/**
|
|
28
|
+
* A keyboard shortcut to show at the end, written like `'Mod+,'` and labelled for the platform
|
|
29
|
+
* (`⌘,` on a Mac, `Ctrl+,` elsewhere). Only shown: bind the key yourself.
|
|
30
|
+
*/
|
|
31
|
+
shortcut?: Shortcut;
|
|
19
32
|
}
|
|
20
33
|
export interface CommandGroupProps extends Command.GroupProps {
|
|
21
34
|
class?: string;
|
|
@@ -29,3 +42,16 @@ export interface CommandEmptyProps extends Command.EmptyProps {
|
|
|
29
42
|
export interface CommandSeparatorProps extends Command.SeparatorProps {
|
|
30
43
|
class?: string;
|
|
31
44
|
}
|
|
45
|
+
export interface CommandDialogProps extends CommandProps {
|
|
46
|
+
/** Whether the palette is open. */
|
|
47
|
+
open?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Page-wide keys that open and close the palette. `Mod` is ⌘ on Apple platforms, Ctrl
|
|
50
|
+
* elsewhere. Fires inside text fields too, but not while another modal dialog is open. `null`
|
|
51
|
+
* turns it off.
|
|
52
|
+
* @default 'Mod+K'
|
|
53
|
+
*/
|
|
54
|
+
shortcut?: Shortcut | null;
|
|
55
|
+
/** Accessible name of the dialog. */
|
|
56
|
+
label?: string;
|
|
57
|
+
}
|
|
@@ -104,3 +104,12 @@ the view on click or typing, not on focus: closing hands focus back to the bar.
|
|
|
104
104
|
through the `role="option"` items (wrapping, skipping disabled ones) via
|
|
105
105
|
`aria-activedescendant`, so focus stays in the field while typing; Enter picks the highlighted
|
|
106
106
|
one. The highlighted item gets the M3 focus indicator.
|
|
107
|
+
- **Submitting.** Enter with no suggestion highlighted, or Enter in the bar itself, calls
|
|
108
|
+
`onsearch(query)` with the query as typed (never for an empty one). The view stays open; close
|
|
109
|
+
it yourself (`open = false`) when you navigate to results. This is where search differs from the
|
|
110
|
+
command palette (`CommandDialog`): a palette always highlights its best match and runs it, a
|
|
111
|
+
search can always search for exactly what was typed.
|
|
112
|
+
- **Shortcut.** `/` anywhere on the page, outside a text field, focuses the bar (or opens its view
|
|
113
|
+
when it has `results`), and the input advertises it with `aria-keyshortcuts`. `shortcut` changes
|
|
114
|
+
the key; `null` turns it off. With several bars on a page the first mounted takes it. It doesn't
|
|
115
|
+
fire while another modal dialog is open. ⌘K belongs to the command palette (`CommandDialog`).
|
|
@@ -47,6 +47,11 @@
|
|
|
47
47
|
query ? topics.filter((t) => t.toLowerCase().includes(query.toLowerCase())) : recent
|
|
48
48
|
);
|
|
49
49
|
|
|
50
|
+
const submit = (q: string) => {
|
|
51
|
+
picked = `search for “${q}”`;
|
|
52
|
+
open = false;
|
|
53
|
+
};
|
|
54
|
+
|
|
50
55
|
const pick = (item: string) => {
|
|
51
56
|
picked = item;
|
|
52
57
|
query = item;
|
|
@@ -101,7 +106,8 @@
|
|
|
101
106
|
|
|
102
107
|
<!--
|
|
103
108
|
Click or type in the bar. The view is full-screen below the medium window class and docked from
|
|
104
|
-
it up: resize the viewport to see both. Arrow keys move through the suggestions; Enter picks one
|
|
109
|
+
it up: resize the viewport to see both. Arrow keys move through the suggestions; Enter picks one,
|
|
110
|
+
or with nothing highlighted searches for what was typed (onsearch). / from anywhere opens it.
|
|
105
111
|
-->
|
|
106
112
|
<Story
|
|
107
113
|
name="Search view"
|
|
@@ -112,7 +118,13 @@
|
|
|
112
118
|
}}
|
|
113
119
|
>
|
|
114
120
|
<div class="gap-spacing-200 p-spacing-300 flex min-h-dvh flex-col items-center">
|
|
115
|
-
<Search
|
|
121
|
+
<Search
|
|
122
|
+
placeholder="Search components"
|
|
123
|
+
bind:value={query}
|
|
124
|
+
bind:open
|
|
125
|
+
results={suggestions}
|
|
126
|
+
onsearch={submit}
|
|
127
|
+
>
|
|
116
128
|
{#snippet trailing()}
|
|
117
129
|
<ButtonIcon variant="standard" iconProps={{ name: 'mic' }} aria-label="Voice search" />
|
|
118
130
|
{/snippet}
|
|
@@ -7,13 +7,16 @@ Search bars allow users to enter a query to find specific information within an
|
|
|
7
7
|
Give it `results` and the bar opens a search view (`SearchView`) when clicked or typed in:
|
|
8
8
|
full-screen on compact windows, docked from medium up, with a container transform between them.
|
|
9
9
|
|
|
10
|
+
`/` anywhere on the page (outside a text field) jumps to the bar, or opens its view; `shortcut`
|
|
11
|
+
changes the key, `null` turns it off.
|
|
12
|
+
|
|
10
13
|
@see https://m3.material.io/components/search/specs
|
|
11
14
|
-->
|
|
12
15
|
<script lang="ts">
|
|
13
16
|
import { search } from './theme.js';
|
|
14
17
|
import clsx from 'clsx';
|
|
15
18
|
import type { SearchProps } from './types.js';
|
|
16
|
-
import { Icon } from '../../../utils/index.js';
|
|
19
|
+
import { Icon, ariaKeyShortcut, isApplePlatform, triggersShortcut } from '../../../utils/index.js';
|
|
17
20
|
import ButtonIcon from '../../buttons/ButtonIcon.svelte';
|
|
18
21
|
import SearchView from './SearchView.svelte';
|
|
19
22
|
|
|
@@ -30,6 +33,8 @@ full-screen on compact windows, docked from medium up, with a container transfor
|
|
|
30
33
|
clearLabel = 'Clear search',
|
|
31
34
|
open = $bindable(false),
|
|
32
35
|
results,
|
|
36
|
+
shortcut = '/',
|
|
37
|
+
onsearch,
|
|
33
38
|
layout,
|
|
34
39
|
backLabel,
|
|
35
40
|
resultsLabel,
|
|
@@ -44,7 +49,7 @@ full-screen on compact windows, docked from medium up, with a container transfor
|
|
|
44
49
|
|
|
45
50
|
let bar = $state<HTMLElement>();
|
|
46
51
|
|
|
47
|
-
// With a search view, clicking the bar
|
|
52
|
+
// With a search view, clicking the bar or typing in it opens the view. Not focus:
|
|
48
53
|
// closing the view hands focus back here, which must not reopen it.
|
|
49
54
|
const opener = $derived(
|
|
50
55
|
results
|
|
@@ -58,18 +63,40 @@ full-screen on compact windows, docked from medium up, with a container transfor
|
|
|
58
63
|
oninput: (e: Event & { currentTarget: HTMLInputElement }) => {
|
|
59
64
|
restProps.oninput?.(e);
|
|
60
65
|
if (!e.defaultPrevented) open = true;
|
|
61
|
-
},
|
|
62
|
-
onkeydown: (e: KeyboardEvent & { currentTarget: HTMLInputElement }) => {
|
|
63
|
-
restProps.onkeydown?.(e);
|
|
64
|
-
if (!e.defaultPrevented && e.key === 'ArrowDown') {
|
|
65
|
-
e.preventDefault();
|
|
66
|
-
open = true;
|
|
67
|
-
}
|
|
68
66
|
}
|
|
69
67
|
}
|
|
70
68
|
: {}
|
|
71
69
|
);
|
|
72
70
|
|
|
71
|
+
// ↓ opens the view; Enter searches for the query as typed.
|
|
72
|
+
function onkeydown(e: KeyboardEvent & { currentTarget: HTMLInputElement }) {
|
|
73
|
+
restProps.onkeydown?.(e);
|
|
74
|
+
if (e.defaultPrevented) return;
|
|
75
|
+
if (e.key === 'ArrowDown' && results) {
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
open = true;
|
|
78
|
+
} else if (e.key === 'Enter' && onsearch && value?.trim()) {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
onsearch(value);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// `Mod` resolves on the client, so SSR and the first render agree.
|
|
85
|
+
let apple = $state(false);
|
|
86
|
+
$effect(() => {
|
|
87
|
+
apple = isApplePlatform();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
function onShortcut(e: KeyboardEvent) {
|
|
91
|
+
if (!triggersShortcut(e, shortcut, bar)) return;
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
if (results) open = true;
|
|
94
|
+
else {
|
|
95
|
+
elementRef?.focus();
|
|
96
|
+
elementRef?.select();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
73
100
|
const showClear = $derived(!!trailingIconProps && !!value);
|
|
74
101
|
|
|
75
102
|
const s = $derived(
|
|
@@ -80,6 +107,8 @@ full-screen on compact windows, docked from medium up, with a container transfor
|
|
|
80
107
|
);
|
|
81
108
|
</script>
|
|
82
109
|
|
|
110
|
+
<svelte:window onkeydown={onShortcut} />
|
|
111
|
+
|
|
83
112
|
<label for={id} class={s.base({ class: clsx(className) })} bind:this={bar}>
|
|
84
113
|
{#if leading}
|
|
85
114
|
<span class={s.leading()}>{@render leading()}</span>
|
|
@@ -89,7 +118,9 @@ full-screen on compact windows, docked from medium up, with a container transfor
|
|
|
89
118
|
<input
|
|
90
119
|
{...restProps}
|
|
91
120
|
{...opener}
|
|
121
|
+
{onkeydown}
|
|
92
122
|
{id}
|
|
123
|
+
aria-keyshortcuts={shortcut ? ariaKeyShortcut(shortcut, apple) : undefined}
|
|
93
124
|
placeholder={placeholder ?? undefined}
|
|
94
125
|
bind:this={elementRef}
|
|
95
126
|
bind:value
|
|
@@ -124,5 +155,6 @@ full-screen on compact windows, docked from medium up, with a container transfor
|
|
|
124
155
|
{resultsLabel}
|
|
125
156
|
{clearLabel}
|
|
126
157
|
{trailing}
|
|
158
|
+
{onsearch}
|
|
127
159
|
/>
|
|
128
160
|
{/if}
|
|
@@ -7,6 +7,9 @@ import type { SearchProps } from './types.js';
|
|
|
7
7
|
* Give it `results` and the bar opens a search view (`SearchView`) when clicked or typed in:
|
|
8
8
|
* full-screen on compact windows, docked from medium up, with a container transform between them.
|
|
9
9
|
*
|
|
10
|
+
* `/` anywhere on the page (outside a text field) jumps to the bar, or opens its view; `shortcut`
|
|
11
|
+
* changes the key, `null` turns it off.
|
|
12
|
+
*
|
|
10
13
|
* @see https://m3.material.io/components/search/specs
|
|
11
14
|
*/
|
|
12
15
|
declare const Search: import("svelte").Component<SearchProps, {}, "value" | "open" | "elementRef">;
|
|
@@ -12,7 +12,8 @@ open a view from a bar of your own, passing that bar as `anchor`.
|
|
|
12
12
|
the `spatial` spring; a crossfade under reduced motion.
|
|
13
13
|
- **Behavior.** A bits-ui `Dialog`: focus stays in the view, the page doesn't scroll, and Esc, the
|
|
14
14
|
back button or a click outside closes it. The field is a combobox over the `results` listbox:
|
|
15
|
-
arrow keys move through the `role="option"` items and Enter picks one.
|
|
15
|
+
arrow keys move through the `role="option"` items and Enter picks one. Enter with no item
|
|
16
|
+
highlighted searches for the query as typed (`onsearch`).
|
|
16
17
|
|
|
17
18
|
@see https://m3.material.io/components/search/specs
|
|
18
19
|
-->
|
|
@@ -39,7 +40,8 @@ open a view from a bar of your own, passing that bar as `anchor`.
|
|
|
39
40
|
clearLabel = 'Clear search',
|
|
40
41
|
trailing,
|
|
41
42
|
inputProps,
|
|
42
|
-
inputRef = $bindable()
|
|
43
|
+
inputRef = $bindable(),
|
|
44
|
+
onsearch
|
|
43
45
|
}: SearchViewProps = $props();
|
|
44
46
|
|
|
45
47
|
const listboxId = `${uid}-listbox`;
|
|
@@ -167,6 +169,9 @@ open a view from a bar of your own, passing that bar as `anchor`.
|
|
|
167
169
|
} else if (e.key === 'Enter' && active?.isConnected) {
|
|
168
170
|
e.preventDefault();
|
|
169
171
|
active.click();
|
|
172
|
+
} else if (e.key === 'Enter' && onsearch && value?.trim()) {
|
|
173
|
+
e.preventDefault();
|
|
174
|
+
onsearch(value);
|
|
170
175
|
}
|
|
171
176
|
}
|
|
172
177
|
|