ionbase-ui 0.48.0 → 0.59.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/LICENSE +21 -0
- package/dist/components/Accordion.d.ts +54 -0
- package/dist/components/Accordion.d.ts.map +1 -0
- package/dist/components/Accordion.js +68 -0
- package/dist/components/Accordion.js.map +1 -0
- package/dist/components/Breadcrumb.d.ts +45 -0
- package/dist/components/Breadcrumb.d.ts.map +1 -0
- package/dist/components/Breadcrumb.js +31 -0
- package/dist/components/Breadcrumb.js.map +1 -0
- package/dist/components/Checkbox.d.ts +2 -1
- package/dist/components/Checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox.js +4 -2
- package/dist/components/Checkbox.js.map +1 -1
- package/dist/components/Combobox.d.ts +86 -0
- package/dist/components/Combobox.d.ts.map +1 -0
- package/dist/components/Combobox.js +261 -0
- package/dist/components/Combobox.js.map +1 -0
- package/dist/components/Drawer.d.ts +52 -0
- package/dist/components/Drawer.d.ts.map +1 -0
- package/dist/components/Drawer.js +66 -0
- package/dist/components/Drawer.js.map +1 -0
- package/dist/components/FileUpload.d.ts +79 -0
- package/dist/components/FileUpload.d.ts.map +1 -0
- package/dist/components/FileUpload.js +218 -0
- package/dist/components/FileUpload.js.map +1 -0
- package/dist/components/ProgressBar.d.ts +46 -0
- package/dist/components/ProgressBar.d.ts.map +1 -0
- package/dist/components/ProgressBar.js +52 -0
- package/dist/components/ProgressBar.js.map +1 -0
- package/dist/components/Select.d.ts +9 -2
- package/dist/components/Select.d.ts.map +1 -1
- package/dist/components/Select.js.map +1 -1
- package/dist/components/Skeleton.d.ts +39 -0
- package/dist/components/Skeleton.d.ts.map +1 -0
- package/dist/components/Skeleton.js +50 -0
- package/dist/components/Skeleton.js.map +1 -0
- package/dist/components/Spinner.d.ts +45 -0
- package/dist/components/Spinner.d.ts.map +1 -0
- package/dist/components/Spinner.js +44 -0
- package/dist/components/Spinner.js.map +1 -0
- package/dist/components/Textarea.d.ts +50 -0
- package/dist/components/Textarea.d.ts.map +1 -0
- package/dist/components/Textarea.js +48 -0
- package/dist/components/Textarea.js.map +1 -0
- package/dist/components/Toggle.d.ts +2 -1
- package/dist/components/Toggle.d.ts.map +1 -1
- package/dist/components/Toggle.js +4 -2
- package/dist/components/Toggle.js.map +1 -1
- package/dist/components/index.d.ts +18 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +9 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/resolve-selection.d.ts +36 -0
- package/dist/components/resolve-selection.d.ts.map +1 -0
- package/dist/components/resolve-selection.js +14 -0
- package/dist/components/resolve-selection.js.map +1 -0
- package/dist/figma-descriptions.json +204 -144
- package/dist/figma-map.json +2311 -1643
- package/dist/meta/Accordion.json +123 -0
- package/dist/meta/AccordionItem.json +90 -0
- package/dist/meta/Breadcrumb.json +87 -0
- package/dist/meta/BreadcrumbItem.json +78 -0
- package/dist/meta/Checkbox.json +18 -1
- package/dist/meta/Combobox.json +265 -0
- package/dist/meta/Drawer.json +234 -0
- package/dist/meta/EmptyState.json +1 -1
- package/dist/meta/FileUpload.json +246 -0
- package/dist/meta/Input.json +0 -1
- package/dist/meta/PhoneInput.json +39 -39
- package/dist/meta/Popover.json +3 -3
- package/dist/meta/ProgressBar.json +166 -0
- package/dist/meta/Select.json +4 -3
- package/dist/meta/Skeleton.json +120 -0
- package/dist/meta/Spinner.json +123 -0
- package/dist/meta/Textarea.json +520 -0
- package/dist/meta/Toggle.json +18 -1
- package/dist/meta/Tooltip.json +3 -3
- package/dist/meta/components.json +2447 -361
- package/dist/meta/contrast.json +3130 -227
- package/dist/meta/index.json +149 -7
- package/dist/meta/patterns/index.json +1 -1
- package/dist/styles/accordion.css +102 -0
- package/dist/styles/breadcrumb.css +72 -0
- package/dist/styles/combobox.css +154 -0
- package/dist/styles/drawer.css +181 -0
- package/dist/styles/empty-state.css +16 -4
- package/dist/styles/file-upload.css +236 -0
- package/dist/styles/index.css +9 -0
- package/dist/styles/input.css +34 -15
- package/dist/styles/progress-bar.css +107 -0
- package/dist/styles/skeleton.css +76 -0
- package/dist/styles/spinner.css +70 -0
- package/dist/styles/textarea.css +178 -0
- package/dist/styles/tokens/base.css +0 -1
- package/dist/styles/tokens/theme-dark.css +0 -1
- package/dist/tokens/index.d.ts +0 -6
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +0 -5
- package/dist/tokens/index.js.map +1 -1
- package/llms.txt +2 -2
- package/package.json +19 -19
package/dist/meta/index.json
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package": "ionbase-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.0",
|
|
4
4
|
"usage": "Pick a component here, then read dist/meta/<Name>.json for its full contract.",
|
|
5
5
|
"hooks": [
|
|
6
6
|
"useToast"
|
|
7
7
|
],
|
|
8
8
|
"components": {
|
|
9
|
+
"Accordion": {
|
|
10
|
+
"summary": "Sections that collapse, with the document's heading structure intact. Holds the open set and the heading level.",
|
|
11
|
+
"status": "stable",
|
|
12
|
+
"variants": {},
|
|
13
|
+
"detail": "dist/meta/Accordion.json",
|
|
14
|
+
"hasIntent": true
|
|
15
|
+
},
|
|
16
|
+
"AccordionItem": {
|
|
17
|
+
"summary": "One collapsible section: an always-visible title that toggles a region beneath it.",
|
|
18
|
+
"status": "stable",
|
|
19
|
+
"variants": {},
|
|
20
|
+
"detail": "dist/meta/AccordionItem.json",
|
|
21
|
+
"hasIntent": true
|
|
22
|
+
},
|
|
9
23
|
"AgentActivity": {
|
|
10
24
|
"summary": "An ordered, plain-language log of what the agent is doing. Status is text and shape, never colour alone.",
|
|
11
25
|
"status": "stable",
|
|
@@ -184,6 +198,20 @@
|
|
|
184
198
|
"detail": "dist/meta/Badge.json",
|
|
185
199
|
"hasIntent": true
|
|
186
200
|
},
|
|
201
|
+
"Breadcrumb": {
|
|
202
|
+
"summary": "Where this page sits in the hierarchy, and how to get back up. An ordered list inside a named landmark.",
|
|
203
|
+
"status": "stable",
|
|
204
|
+
"variants": {},
|
|
205
|
+
"detail": "dist/meta/Breadcrumb.json",
|
|
206
|
+
"hasIntent": true
|
|
207
|
+
},
|
|
208
|
+
"BreadcrumbItem": {
|
|
209
|
+
"summary": "One crumb. A link to an ancestor, or the current page rendered as text.",
|
|
210
|
+
"status": "stable",
|
|
211
|
+
"variants": {},
|
|
212
|
+
"detail": "dist/meta/BreadcrumbItem.json",
|
|
213
|
+
"hasIntent": true
|
|
214
|
+
},
|
|
187
215
|
"Button": {
|
|
188
216
|
"summary": "Triggers an action in place. Never navigates.",
|
|
189
217
|
"status": "stable",
|
|
@@ -246,6 +274,24 @@
|
|
|
246
274
|
"detail": "dist/meta/CitationListItem.json",
|
|
247
275
|
"hasIntent": true
|
|
248
276
|
},
|
|
277
|
+
"Combobox": {
|
|
278
|
+
"summary": "A text field that filters a list of options down to one selected value.",
|
|
279
|
+
"status": "stable",
|
|
280
|
+
"variants": {
|
|
281
|
+
"size": [
|
|
282
|
+
"sm",
|
|
283
|
+
"md",
|
|
284
|
+
"lg"
|
|
285
|
+
],
|
|
286
|
+
"menuTrigger": [
|
|
287
|
+
"focus",
|
|
288
|
+
"input",
|
|
289
|
+
"manual"
|
|
290
|
+
]
|
|
291
|
+
},
|
|
292
|
+
"detail": "dist/meta/Combobox.json",
|
|
293
|
+
"hasIntent": true
|
|
294
|
+
},
|
|
249
295
|
"ConfidenceIndicator": {
|
|
250
296
|
"summary": "How much to trust the thing next to it — three levels with a required basis, never a percentage.",
|
|
251
297
|
"status": "stable",
|
|
@@ -271,6 +317,25 @@
|
|
|
271
317
|
"detail": "dist/meta/Divider.json",
|
|
272
318
|
"hasIntent": true
|
|
273
319
|
},
|
|
320
|
+
"Drawer": {
|
|
321
|
+
"summary": "A modal panel anchored to an edge. Modal behaviour — focus trap, Escape, scrim — with the shape of a side panel.",
|
|
322
|
+
"status": "stable",
|
|
323
|
+
"variants": {
|
|
324
|
+
"placement": [
|
|
325
|
+
"start",
|
|
326
|
+
"end",
|
|
327
|
+
"top",
|
|
328
|
+
"bottom"
|
|
329
|
+
],
|
|
330
|
+
"size": [
|
|
331
|
+
"sm",
|
|
332
|
+
"md",
|
|
333
|
+
"lg"
|
|
334
|
+
]
|
|
335
|
+
},
|
|
336
|
+
"detail": "dist/meta/Drawer.json",
|
|
337
|
+
"hasIntent": true
|
|
338
|
+
},
|
|
274
339
|
"EmptyState": {
|
|
275
340
|
"summary": "The state a region shows when it has nothing to show, and why — first run, no results, no access, or a failed fetch.",
|
|
276
341
|
"status": "stable",
|
|
@@ -290,6 +355,18 @@
|
|
|
290
355
|
"detail": "dist/meta/EmptyState.json",
|
|
291
356
|
"hasIntent": true
|
|
292
357
|
},
|
|
358
|
+
"FileUpload": {
|
|
359
|
+
"summary": "A drop target wrapped around a real file input, with client-side accept and size checks.",
|
|
360
|
+
"status": "stable",
|
|
361
|
+
"variants": {
|
|
362
|
+
"size": [
|
|
363
|
+
"sm",
|
|
364
|
+
"md"
|
|
365
|
+
]
|
|
366
|
+
},
|
|
367
|
+
"detail": "dist/meta/FileUpload.json",
|
|
368
|
+
"hasIntent": true
|
|
369
|
+
},
|
|
293
370
|
"FullCard": {
|
|
294
371
|
"summary": "A full-bleed row: a text column beside a framed media panel, mirrored by `alignment`.",
|
|
295
372
|
"status": "stable",
|
|
@@ -459,10 +536,10 @@
|
|
|
459
536
|
"status": "stable",
|
|
460
537
|
"variants": {
|
|
461
538
|
"placement": [
|
|
462
|
-
"right",
|
|
463
|
-
"left",
|
|
464
539
|
"top",
|
|
465
|
-
"bottom"
|
|
540
|
+
"bottom",
|
|
541
|
+
"right",
|
|
542
|
+
"left"
|
|
466
543
|
],
|
|
467
544
|
"size": [
|
|
468
545
|
"sm",
|
|
@@ -473,6 +550,24 @@
|
|
|
473
550
|
"detail": "dist/meta/Popover.json",
|
|
474
551
|
"hasIntent": true
|
|
475
552
|
},
|
|
553
|
+
"ProgressBar": {
|
|
554
|
+
"summary": "A determinate or indeterminate measure of work, with the name of the work attached. Omitting `value` is what makes it indeterminate.",
|
|
555
|
+
"status": "stable",
|
|
556
|
+
"variants": {
|
|
557
|
+
"intent": [
|
|
558
|
+
"primary",
|
|
559
|
+
"success",
|
|
560
|
+
"warning",
|
|
561
|
+
"error"
|
|
562
|
+
],
|
|
563
|
+
"size": [
|
|
564
|
+
"sm",
|
|
565
|
+
"md"
|
|
566
|
+
]
|
|
567
|
+
},
|
|
568
|
+
"detail": "dist/meta/ProgressBar.json",
|
|
569
|
+
"hasIntent": true
|
|
570
|
+
},
|
|
476
571
|
"Radio": {
|
|
477
572
|
"summary": "One option inside a RadioGroup. Meaningless on its own — the group owns the name and the selected value.",
|
|
478
573
|
"status": "stable",
|
|
@@ -529,6 +624,32 @@
|
|
|
529
624
|
"detail": "dist/meta/Select.json",
|
|
530
625
|
"hasIntent": true
|
|
531
626
|
},
|
|
627
|
+
"Skeleton": {
|
|
628
|
+
"summary": "The shape of content that has not arrived. Always hidden from assistive technology — the caller owns the announcement.",
|
|
629
|
+
"status": "stable",
|
|
630
|
+
"variants": {
|
|
631
|
+
"variant": [
|
|
632
|
+
"circle",
|
|
633
|
+
"text",
|
|
634
|
+
"rect"
|
|
635
|
+
]
|
|
636
|
+
},
|
|
637
|
+
"detail": "dist/meta/Skeleton.json",
|
|
638
|
+
"hasIntent": true
|
|
639
|
+
},
|
|
640
|
+
"Spinner": {
|
|
641
|
+
"summary": "An indeterminate wait, announced once. A ring plus a politely-announced label, sized off the icon ramp.",
|
|
642
|
+
"status": "stable",
|
|
643
|
+
"variants": {
|
|
644
|
+
"size": [
|
|
645
|
+
"sm",
|
|
646
|
+
"md",
|
|
647
|
+
"lg"
|
|
648
|
+
]
|
|
649
|
+
},
|
|
650
|
+
"detail": "dist/meta/Spinner.json",
|
|
651
|
+
"hasIntent": true
|
|
652
|
+
},
|
|
532
653
|
"StreamingText": {
|
|
533
654
|
"summary": "Model output arriving a token at a time, in a container that does not move. Deliberately not a live region.",
|
|
534
655
|
"status": "stable",
|
|
@@ -621,6 +742,27 @@
|
|
|
621
742
|
"detail": "dist/meta/Tabs.json",
|
|
622
743
|
"hasIntent": true
|
|
623
744
|
},
|
|
745
|
+
"Textarea": {
|
|
746
|
+
"summary": "A multi-line text field. Every size and state is Input's; it wraps and it resizes vertically.",
|
|
747
|
+
"status": "stable",
|
|
748
|
+
"variants": {
|
|
749
|
+
"size": [
|
|
750
|
+
"sm",
|
|
751
|
+
"md",
|
|
752
|
+
"lg"
|
|
753
|
+
],
|
|
754
|
+
"autoCapitalize": [
|
|
755
|
+
"none",
|
|
756
|
+
"off",
|
|
757
|
+
"on",
|
|
758
|
+
"sentences",
|
|
759
|
+
"words",
|
|
760
|
+
"characters"
|
|
761
|
+
]
|
|
762
|
+
},
|
|
763
|
+
"detail": "dist/meta/Textarea.json",
|
|
764
|
+
"hasIntent": true
|
|
765
|
+
},
|
|
624
766
|
"Toast": {
|
|
625
767
|
"summary": "A transient notification on neutral chrome. Intent is carried by the icon, not by a tinted panel.",
|
|
626
768
|
"status": "stable",
|
|
@@ -674,10 +816,10 @@
|
|
|
674
816
|
"status": "stable",
|
|
675
817
|
"variants": {
|
|
676
818
|
"placement": [
|
|
677
|
-
"right",
|
|
678
|
-
"left",
|
|
679
819
|
"top",
|
|
680
|
-
"bottom"
|
|
820
|
+
"bottom",
|
|
821
|
+
"right",
|
|
822
|
+
"left"
|
|
681
823
|
]
|
|
682
824
|
},
|
|
683
825
|
"detail": "dist/meta/Tooltip.json",
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Accordion
|
|
3
|
+
*
|
|
4
|
+
* Drawn in Figma as `Accordion` (1360:267) and `Accordion Item` (1360:266),
|
|
5
|
+
* from this file rather than the reverse — so the code is the source when the
|
|
6
|
+
* two disagree.
|
|
7
|
+
*
|
|
8
|
+
* Figma's Accordion Item carries a Collapsed/Expanded axis that the React
|
|
9
|
+
* component does NOT own: the open set lives on the container, because a static
|
|
10
|
+
* frame cannot hold a selection. The axis is drawn on the item because that is
|
|
11
|
+
* where the difference is visible, and owned in code where it is decidable.
|
|
12
|
+
*
|
|
13
|
+
* The trigger fills its heading so the whole row is the hit target. A button
|
|
14
|
+
* that only covers its text makes a wide section header mostly dead space.
|
|
15
|
+
*/
|
|
16
|
+
.ion-accordion {
|
|
17
|
+
font-family: var(--font-family-sans);
|
|
18
|
+
border-top: var(--border-width-default) solid var(--border-subtle);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.ion-accordion__item {
|
|
22
|
+
border-bottom: var(--border-width-default) solid var(--border-subtle);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.ion-accordion__heading {
|
|
26
|
+
margin: 0;
|
|
27
|
+
|
|
28
|
+
/* The heading carries structure, not size: the visible scale belongs to the
|
|
29
|
+
* trigger, so changing headingLevel for document reasons does not resize the
|
|
30
|
+
* control. */
|
|
31
|
+
font: inherit;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.ion-accordion__trigger {
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: space-between;
|
|
38
|
+
gap: var(--spacing-12);
|
|
39
|
+
width: 100%;
|
|
40
|
+
padding: var(--spacing-16) var(--spacing-4);
|
|
41
|
+
border: 0;
|
|
42
|
+
background: none;
|
|
43
|
+
color: var(--text-default);
|
|
44
|
+
font-family: inherit;
|
|
45
|
+
font-size: var(--type-body);
|
|
46
|
+
line-height: var(--type-body-line-height);
|
|
47
|
+
font-weight: var(--font-weight-medium);
|
|
48
|
+
text-align: start;
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Focus and disabled before :hover, so specificity reads non-decreasing — the
|
|
53
|
+
* same ordering approval-gate.css documents and stylelint enforces. */
|
|
54
|
+
.ion-accordion__trigger:focus-visible {
|
|
55
|
+
outline: var(--border-width-thick) solid var(--border-focus);
|
|
56
|
+
outline-offset: -2px;
|
|
57
|
+
border-radius: var(--radius-xs);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.ion-accordion__trigger:disabled {
|
|
61
|
+
color: var(--text-disabled);
|
|
62
|
+
cursor: default;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.ion-accordion__trigger:hover:not(:disabled) {
|
|
66
|
+
color: var(--text-primary);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.ion-accordion__indicator {
|
|
70
|
+
display: inline-flex;
|
|
71
|
+
flex: none;
|
|
72
|
+
width: var(--icon-size-md);
|
|
73
|
+
height: var(--icon-size-md);
|
|
74
|
+
color: var(--icon-secondary);
|
|
75
|
+
transition: transform var(--ion-duration-base) var(--ion-ease-out);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.ion-accordion__indicator svg {
|
|
79
|
+
width: 100%;
|
|
80
|
+
height: 100%;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.ion-accordion__item[data-expanded] .ion-accordion__indicator {
|
|
84
|
+
transform: rotate(180deg);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.ion-accordion__trigger:disabled .ion-accordion__indicator {
|
|
88
|
+
color: var(--icon-disabled);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.ion-accordion__content {
|
|
92
|
+
padding: 0 var(--spacing-4) var(--spacing-16);
|
|
93
|
+
color: var(--text-secondary);
|
|
94
|
+
font-size: var(--type-body);
|
|
95
|
+
line-height: var(--type-body-line-height);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@media (prefers-reduced-motion: reduce) {
|
|
99
|
+
.ion-accordion__indicator {
|
|
100
|
+
transition-duration: 1ms;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Breadcrumb
|
|
3
|
+
*
|
|
4
|
+
* Drawn in Figma as `Breadcrumb` (1359:252) and `Breadcrumb Item` (1359:251) —
|
|
5
|
+
* FROM this file, not the other way round. Every other stylesheet here says
|
|
6
|
+
* "measured from Figma" because Figma came first; here the code did, and the
|
|
7
|
+
* drawing was made to match it. Worth stating, because the direction decides
|
|
8
|
+
* which side wins the next time the two disagree: this one.
|
|
9
|
+
*
|
|
10
|
+
* The separator is a CSS ::before on every item after the first. Drawing it in
|
|
11
|
+
* markup is the usual version of this component and the usual defect: a "/"
|
|
12
|
+
* character in the DOM is announced as punctuation between every crumb.
|
|
13
|
+
*/
|
|
14
|
+
.ion-breadcrumb {
|
|
15
|
+
font-family: var(--font-family-sans);
|
|
16
|
+
font-size: var(--type-body-sm);
|
|
17
|
+
line-height: var(--type-body-sm-line-height);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.ion-breadcrumb__list {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-wrap: wrap;
|
|
23
|
+
align-items: center;
|
|
24
|
+
gap: var(--spacing-8);
|
|
25
|
+
margin: 0;
|
|
26
|
+
padding: 0;
|
|
27
|
+
list-style: none;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.ion-breadcrumb__item {
|
|
31
|
+
display: inline-flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
gap: var(--spacing-8);
|
|
34
|
+
min-width: 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.ion-breadcrumb__item + .ion-breadcrumb__item::before {
|
|
38
|
+
content: '/';
|
|
39
|
+
color: var(--text-tertiary);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.ion-breadcrumb__link {
|
|
43
|
+
color: var(--text-secondary);
|
|
44
|
+
text-decoration: none;
|
|
45
|
+
border-radius: var(--radius-xs);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.ion-breadcrumb__link:hover {
|
|
49
|
+
color: var(--text-default);
|
|
50
|
+
text-decoration: underline;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.ion-breadcrumb__link:focus-visible {
|
|
54
|
+
outline: var(--border-width-thick) solid var(--border-focus);
|
|
55
|
+
outline-offset: 2px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/*
|
|
59
|
+
* The current page is the one crumb a user is most likely to be looking for,
|
|
60
|
+
* so it carries the stronger colour — the trail above it is navigation, and
|
|
61
|
+
* this is the position.
|
|
62
|
+
*/
|
|
63
|
+
.ion-breadcrumb__current {
|
|
64
|
+
color: var(--text-default);
|
|
65
|
+
font-weight: var(--font-weight-medium);
|
|
66
|
+
|
|
67
|
+
/* A long record title truncates rather than wrapping the trail onto three
|
|
68
|
+
* lines; the full value stays reachable through the element's own text. */
|
|
69
|
+
overflow: hidden;
|
|
70
|
+
text-overflow: ellipsis;
|
|
71
|
+
white-space: nowrap;
|
|
72
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Combobox
|
|
3
|
+
*
|
|
4
|
+
* Drawn in Figma as `Combobox` (1370:2359) and `Combobox Menu` (1372:262), from
|
|
5
|
+
* this file. The field is `Input`'s box unchanged — Combobox carries
|
|
6
|
+
* `.ion-input` alongside `.ion-combobox` rather than restating a border, a
|
|
7
|
+
* radius and three states that would then drift from the real thing. Only the
|
|
8
|
+
* disclosure button is new here. The menu borrows `Menu`'s row metrics.
|
|
9
|
+
*
|
|
10
|
+
* The two are one control in code and two components in Figma, because a frame
|
|
11
|
+
* cannot both hug a field and overlay a list on top of it. `Combobox Menu` is
|
|
12
|
+
* recorded in mapping.json's `unmapped` for exactly that reason: there is no
|
|
13
|
+
* React export behind it, and there should not be — a listbox with no combobox
|
|
14
|
+
* to own it is not a thing a caller should be able to mount.
|
|
15
|
+
*
|
|
16
|
+
* THE MENU IS ITS OWN BLOCK, `.ion-combobox-menu`
|
|
17
|
+
*
|
|
18
|
+
* Not `.ion-combobox__popover`. The contrast gate resolves a translucent
|
|
19
|
+
* colour's backdrop from the BLOCK's background, and `.ion-combobox` has no
|
|
20
|
+
* background of its own — it inherits Input's. An option's `surface/hover`
|
|
21
|
+
* would then have had no resolvable ground and come back *skipped*: not
|
|
22
|
+
* failing, not passing, unmeasured. Drawer shipped that exact bug in 0.57.0 and
|
|
23
|
+
* only a count in a summary line caught it.
|
|
24
|
+
*/
|
|
25
|
+
.ion-combobox__button {
|
|
26
|
+
display: inline-flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
justify-content: center;
|
|
29
|
+
flex-shrink: 0;
|
|
30
|
+
padding: 0;
|
|
31
|
+
background: none;
|
|
32
|
+
border: none;
|
|
33
|
+
color: var(--icon-secondary);
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.ion-combobox__button svg {
|
|
38
|
+
width: var(--ion-input-icon-size);
|
|
39
|
+
height: var(--ion-input-icon-size);
|
|
40
|
+
transition: transform var(--ion-duration-base) var(--ion-ease-out);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* The caret follows the list. A static caret beside an open menu is a lie. */
|
|
44
|
+
.ion-combobox[data-open='true'] .ion-combobox__button svg {
|
|
45
|
+
transform: rotate(180deg);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.ion-combobox[data-disabled='true'] .ion-combobox__button {
|
|
49
|
+
color: var(--icon-disabled);
|
|
50
|
+
cursor: not-allowed;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/*
|
|
54
|
+
* NO UNDERLAY, deliberately.
|
|
55
|
+
*
|
|
56
|
+
* Modal and Popover render one. A non-modal popover must not: `usePopover`
|
|
57
|
+
* already listens for outside interaction on the document, so the underlay adds
|
|
58
|
+
* nothing, and a fixed full-viewport element over the page swallows the first
|
|
59
|
+
* click on whatever is behind it. The page stays fully interactive while the
|
|
60
|
+
* list is open, which is the whole difference between this and a dialog.
|
|
61
|
+
*/
|
|
62
|
+
.ion-combobox-menu {
|
|
63
|
+
z-index: 1000;
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-direction: column;
|
|
66
|
+
padding: var(--spacing-4);
|
|
67
|
+
background-color: var(--surface-raised);
|
|
68
|
+
border: var(--border-width-default) solid var(--border-subtle);
|
|
69
|
+
border-radius: var(--radius-md);
|
|
70
|
+
box-shadow: var(--ion-shadow-shadow-lg);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
* Capped, and the cap is the whole reason the list scrolls rather than the
|
|
75
|
+
* page. `usePopover` also passes a maxHeight in the inline style when the
|
|
76
|
+
* viewport is tighter than this; the smaller of the two wins, which is correct
|
|
77
|
+
* either way.
|
|
78
|
+
*/
|
|
79
|
+
.ion-combobox-menu__list {
|
|
80
|
+
max-height: 16rem;
|
|
81
|
+
margin: 0;
|
|
82
|
+
padding: 0;
|
|
83
|
+
overflow-y: auto;
|
|
84
|
+
list-style: none;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.ion-combobox-menu__list:focus {
|
|
88
|
+
outline: none;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.ion-combobox-menu__option {
|
|
92
|
+
display: flex;
|
|
93
|
+
flex-direction: column;
|
|
94
|
+
gap: var(--spacing-2);
|
|
95
|
+
padding: var(--spacing-6) var(--spacing-8);
|
|
96
|
+
border-radius: var(--radius-sm);
|
|
97
|
+
cursor: pointer;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.ion-combobox-menu__label {
|
|
101
|
+
color: var(--text-default);
|
|
102
|
+
font-family: var(--font-family-sans);
|
|
103
|
+
font-size: var(--type-body);
|
|
104
|
+
line-height: var(--type-body-line-height);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.ion-combobox-menu__description {
|
|
108
|
+
color: var(--text-tertiary);
|
|
109
|
+
font-family: var(--font-family-sans);
|
|
110
|
+
font-size: var(--type-body-sm);
|
|
111
|
+
line-height: var(--type-body-sm-line-height);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/*
|
|
115
|
+
* Focus is a background, not an outline, and it is driven by data-focused
|
|
116
|
+
* rather than `:focus`.
|
|
117
|
+
*
|
|
118
|
+
* The real DOM focus never leaves the input — the list is driven by
|
|
119
|
+
* `aria-activedescendant`, so no option is ever `:focus` or `:hover` in the way
|
|
120
|
+
* a normal menu's rows are. Styling either one would produce a list where the
|
|
121
|
+
* keyboard highlight is invisible. React Aria also routes pointer hover into
|
|
122
|
+
* the same `isFocused` flag, so one rule covers both and the mouse and keyboard
|
|
123
|
+
* cannot end up highlighting two different rows.
|
|
124
|
+
*/
|
|
125
|
+
.ion-combobox-menu__option[data-focused='true'] {
|
|
126
|
+
background-color: var(--surface-hover);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.ion-combobox-menu__option[data-selected='true'] .ion-combobox-menu__label {
|
|
130
|
+
font-weight: var(--font-weight-medium);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.ion-combobox-menu__option[data-selected='true'] {
|
|
134
|
+
background-color: var(--surface-primary-subtle);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.ion-combobox-menu__option[data-disabled='true'] {
|
|
138
|
+
cursor: not-allowed;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.ion-combobox-menu__option[data-disabled='true'] .ion-combobox-menu__label,
|
|
142
|
+
.ion-combobox-menu__option[data-disabled='true']
|
|
143
|
+
.ion-combobox-menu__description {
|
|
144
|
+
color: var(--text-disabled);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.ion-combobox-menu__empty {
|
|
148
|
+
padding: var(--spacing-12) var(--spacing-8);
|
|
149
|
+
color: var(--text-tertiary);
|
|
150
|
+
font-family: var(--font-family-sans);
|
|
151
|
+
font-size: var(--type-body-sm);
|
|
152
|
+
line-height: var(--type-body-sm-line-height);
|
|
153
|
+
text-align: center;
|
|
154
|
+
}
|