ionbase-ui 0.13.1 → 0.17.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.
Files changed (59) hide show
  1. package/README.md +62 -45
  2. package/dist/components/Popover.d.ts +47 -0
  3. package/dist/components/Popover.d.ts.map +1 -0
  4. package/dist/components/Popover.js +71 -0
  5. package/dist/components/Popover.js.map +1 -0
  6. package/dist/components/Toast.d.ts +57 -0
  7. package/dist/components/Toast.d.ts.map +1 -0
  8. package/dist/components/Toast.js +81 -0
  9. package/dist/components/Toast.js.map +1 -0
  10. package/dist/components/index.d.ts +4 -0
  11. package/dist/components/index.d.ts.map +1 -1
  12. package/dist/components/index.js +2 -0
  13. package/dist/components/index.js.map +1 -1
  14. package/dist/meta/Alert.json +248 -0
  15. package/dist/meta/Avatar.json +97 -0
  16. package/dist/meta/AvatarGroup.json +82 -0
  17. package/dist/meta/Badge.json +83 -0
  18. package/dist/meta/Button.json +518 -0
  19. package/dist/meta/Checkbox.json +113 -0
  20. package/dist/meta/Divider.json +30 -0
  21. package/dist/meta/FullCard.json +97 -0
  22. package/dist/meta/Header.json +91 -0
  23. package/dist/meta/Icon.json +35 -0
  24. package/dist/meta/Input.json +558 -0
  25. package/dist/meta/Link.json +239 -0
  26. package/dist/meta/Logo.json +46 -0
  27. package/dist/meta/LogoMark.json +41 -0
  28. package/dist/meta/Menu.json +50 -0
  29. package/dist/meta/MenuItem.json +75 -0
  30. package/dist/meta/Modal.json +241 -0
  31. package/dist/meta/NavItem.json +76 -0
  32. package/dist/meta/PhoneInput.json +472 -0
  33. package/dist/meta/Popover.json +124 -0
  34. package/dist/meta/Radio.json +104 -0
  35. package/dist/meta/RadioGroup.json +126 -0
  36. package/dist/meta/ScrollProgress.json +63 -0
  37. package/dist/meta/Select.json +195 -0
  38. package/dist/meta/TabItem.json +15 -0
  39. package/dist/meta/Table.json +135 -0
  40. package/dist/meta/TableBody.json +43 -0
  41. package/dist/meta/TableCell.json +105 -0
  42. package/dist/meta/TableHead.json +43 -0
  43. package/dist/meta/TableRow.json +56 -0
  44. package/dist/meta/Tabs.json +186 -0
  45. package/dist/meta/Toast.json +105 -0
  46. package/dist/meta/ToastProvider.json +75 -0
  47. package/dist/meta/Toggle.json +102 -0
  48. package/dist/meta/Tooltip.json +78 -0
  49. package/dist/meta/components.json +4757 -0
  50. package/dist/meta/index.json +479 -0
  51. package/dist/styles/index.css +2 -0
  52. package/dist/styles/popover.css +185 -0
  53. package/dist/styles/toast.css +216 -0
  54. package/dist/styles/tokens/base.css +26 -26
  55. package/dist/styles/tokens/theme-dark.css +11 -11
  56. package/dist/tokens/index.d.ts +9 -9
  57. package/dist/tokens/index.js +9 -9
  58. package/dist/tokens/index.js.map +1 -1
  59. package/package.json +12 -6
@@ -0,0 +1,479 @@
1
+ {
2
+ "package": "ionbase-ui",
3
+ "version": "0.17.0",
4
+ "usage": "Pick a component here, then read dist/meta/<Name>.json for its full contract.",
5
+ "hooks": [
6
+ "useToast"
7
+ ],
8
+ "components": {
9
+ "Alert": {
10
+ "summary": "A persistent, in-page status message. Intent picks the ARIA role for you.",
11
+ "status": "stable",
12
+ "variants": {
13
+ "intent": [
14
+ "neutral",
15
+ "primary",
16
+ "success",
17
+ "warning",
18
+ "error",
19
+ "information"
20
+ ],
21
+ "emphasis": [
22
+ "subtle",
23
+ "solid"
24
+ ],
25
+ "layout": [
26
+ "inline",
27
+ "banner"
28
+ ]
29
+ },
30
+ "detail": "dist/meta/Alert.json",
31
+ "hasIntent": true
32
+ },
33
+ "Avatar": {
34
+ "summary": "Avatar picks its content the way Figma's `Type` variant does, but by",
35
+ "variants": {
36
+ "size": [
37
+ "mini",
38
+ "sm",
39
+ "md",
40
+ "lg"
41
+ ],
42
+ "shape": [
43
+ "circle",
44
+ "square"
45
+ ]
46
+ },
47
+ "detail": "dist/meta/Avatar.json",
48
+ "hasIntent": false
49
+ },
50
+ "AvatarGroup": {
51
+ "summary": "AvatarGroup overlaps its children by a quarter of the avatar size, matching",
52
+ "variants": {
53
+ "size": [
54
+ "mini",
55
+ "sm",
56
+ "md",
57
+ "lg"
58
+ ],
59
+ "shape": [
60
+ "circle",
61
+ "square"
62
+ ]
63
+ },
64
+ "detail": "dist/meta/AvatarGroup.json",
65
+ "hasIntent": false
66
+ },
67
+ "Badge": {
68
+ "summary": "Badge is presentational — no role, no interaction. React Aria has nothing to",
69
+ "variants": {
70
+ "intent": [
71
+ "neutral",
72
+ "primary",
73
+ "success",
74
+ "warning",
75
+ "error",
76
+ "information"
77
+ ]
78
+ },
79
+ "detail": "dist/meta/Badge.json",
80
+ "hasIntent": false
81
+ },
82
+ "Button": {
83
+ "summary": "Triggers an action in place. Never navigates.",
84
+ "status": "stable",
85
+ "variants": {
86
+ "variant": [
87
+ "success",
88
+ "primary-brand",
89
+ "primary-neutral",
90
+ "primary-soft",
91
+ "secondary",
92
+ "tertiary",
93
+ "destructive"
94
+ ],
95
+ "size": [
96
+ "sm",
97
+ "md",
98
+ "lg",
99
+ "xl"
100
+ ]
101
+ },
102
+ "detail": "dist/meta/Button.json",
103
+ "hasIntent": true
104
+ },
105
+ "Checkbox": {
106
+ "summary": "Checkbox wraps a native `<input type=\"checkbox\">` and hides it.",
107
+ "variants": {
108
+ "size": [
109
+ "sm",
110
+ "md",
111
+ "lg"
112
+ ],
113
+ "intent": [
114
+ "neutral",
115
+ "brand",
116
+ "danger"
117
+ ]
118
+ },
119
+ "detail": "dist/meta/Checkbox.json",
120
+ "hasIntent": false
121
+ },
122
+ "Divider": {
123
+ "summary": "A single `<hr>`, matching Figma's `Border` (70:22153).",
124
+ "variants": {
125
+ "orientation": [
126
+ "horizontal",
127
+ "vertical"
128
+ ]
129
+ },
130
+ "detail": "dist/meta/Divider.json",
131
+ "hasIntent": false
132
+ },
133
+ "FullCard": {
134
+ "summary": "Full Card — the full-bleed case study row from Figma `Full Card` (592:857).",
135
+ "variants": {
136
+ "alignment": [
137
+ "right",
138
+ "left"
139
+ ]
140
+ },
141
+ "detail": "dist/meta/FullCard.json",
142
+ "hasIntent": false
143
+ },
144
+ "Header": {
145
+ "summary": "Header renders Figma's four Device variants — Desktop, Tablet,",
146
+ "variants": {},
147
+ "detail": "dist/meta/Header.json",
148
+ "hasIntent": false
149
+ },
150
+ "Icon": {
151
+ "summary": "Wrapper that applies the design system's icon sizing and accessibility",
152
+ "variants": {},
153
+ "detail": "dist/meta/Icon.json",
154
+ "hasIntent": false
155
+ },
156
+ "Input": {
157
+ "summary": "A single-line text field, together with its label, helper text and error message.",
158
+ "status": "stable",
159
+ "variants": {
160
+ "size": [
161
+ "sm",
162
+ "md",
163
+ "lg"
164
+ ],
165
+ "autoCapitalize": [
166
+ "none",
167
+ "off",
168
+ "on",
169
+ "sentences",
170
+ "words",
171
+ "characters"
172
+ ]
173
+ },
174
+ "detail": "dist/meta/Input.json",
175
+ "hasIntent": true
176
+ },
177
+ "Link": {
178
+ "summary": "Link — Figma `Link` (774:1516).",
179
+ "variants": {
180
+ "variant": [
181
+ "inline",
182
+ "standalone"
183
+ ]
184
+ },
185
+ "detail": "dist/meta/Link.json",
186
+ "hasIntent": false
187
+ },
188
+ "Logo": {
189
+ "summary": "The full lockup: mark + wordmark, matching Figma's `Logo-Ionbase`",
190
+ "variants": {
191
+ "size": [
192
+ "sm",
193
+ "lg"
194
+ ],
195
+ "wordmark": [
196
+ "text",
197
+ "vector"
198
+ ]
199
+ },
200
+ "detail": "dist/meta/Logo.json",
201
+ "hasIntent": false
202
+ },
203
+ "LogoMark": {
204
+ "summary": "The bare icon glyph, no wordmark — what `Header` actually uses.",
205
+ "variants": {
206
+ "size": [
207
+ "sm",
208
+ "lg"
209
+ ]
210
+ },
211
+ "detail": "dist/meta/LogoMark.json",
212
+ "hasIntent": false
213
+ },
214
+ "Menu": {
215
+ "summary": "Menu is the list surface, not a popover.",
216
+ "variants": {},
217
+ "detail": "dist/meta/Menu.json",
218
+ "hasIntent": false
219
+ },
220
+ "MenuItem": {
221
+ "summary": null,
222
+ "variants": {},
223
+ "detail": "dist/meta/MenuItem.json",
224
+ "hasIntent": false
225
+ },
226
+ "Modal": {
227
+ "summary": "A focus-trapping dialog. Renders nothing when closed and portals when open.",
228
+ "status": "stable",
229
+ "variants": {
230
+ "size": [
231
+ "sm",
232
+ "md",
233
+ "lg",
234
+ "fullscreen"
235
+ ],
236
+ "align": [
237
+ "left",
238
+ "center"
239
+ ]
240
+ },
241
+ "detail": "dist/meta/Modal.json",
242
+ "hasIntent": true
243
+ },
244
+ "NavItem": {
245
+ "summary": "Nav Item renders an `<a>` when given `href`, a `<button>` otherwise —",
246
+ "variants": {},
247
+ "detail": "dist/meta/NavItem.json",
248
+ "hasIntent": false
249
+ },
250
+ "PhoneInput": {
251
+ "summary": "PhoneInput — Figma `Input/Phone` (80:372).",
252
+ "variants": {
253
+ "autoCapitalize": [
254
+ "none",
255
+ "off",
256
+ "on",
257
+ "sentences",
258
+ "words",
259
+ "characters"
260
+ ],
261
+ "size": [
262
+ "sm",
263
+ "md",
264
+ "lg"
265
+ ]
266
+ },
267
+ "detail": "dist/meta/PhoneInput.json",
268
+ "hasIntent": false
269
+ },
270
+ "Popover": {
271
+ "summary": "Popover — Figma `Popover` (825:1853).",
272
+ "variants": {
273
+ "placement": [
274
+ "right",
275
+ "left",
276
+ "top",
277
+ "bottom"
278
+ ],
279
+ "size": [
280
+ "sm",
281
+ "md",
282
+ "lg"
283
+ ]
284
+ },
285
+ "detail": "dist/meta/Popover.json",
286
+ "hasIntent": false
287
+ },
288
+ "Radio": {
289
+ "summary": null,
290
+ "variants": {
291
+ "size": [
292
+ "sm",
293
+ "md",
294
+ "lg"
295
+ ],
296
+ "intent": [
297
+ "neutral",
298
+ "brand",
299
+ "danger"
300
+ ]
301
+ },
302
+ "detail": "dist/meta/Radio.json",
303
+ "hasIntent": false
304
+ },
305
+ "RadioGroup": {
306
+ "summary": "Renders a `<fieldset>` with a `<legend>` rather than a div with",
307
+ "variants": {
308
+ "size": [
309
+ "sm",
310
+ "md",
311
+ "lg"
312
+ ],
313
+ "intent": [
314
+ "neutral",
315
+ "brand",
316
+ "danger"
317
+ ]
318
+ },
319
+ "detail": "dist/meta/RadioGroup.json",
320
+ "hasIntent": false
321
+ },
322
+ "ScrollProgress": {
323
+ "summary": "The compact rail is the disclosure trigger. It is a real `<button>`, so a",
324
+ "variants": {},
325
+ "detail": "dist/meta/ScrollProgress.json",
326
+ "hasIntent": false
327
+ },
328
+ "Select": {
329
+ "summary": "A dropdown over a native <select>, with the same label and error wiring as Input.",
330
+ "status": "stable",
331
+ "variants": {
332
+ "size": [
333
+ "sm",
334
+ "md",
335
+ "lg"
336
+ ]
337
+ },
338
+ "detail": "dist/meta/Select.json",
339
+ "hasIntent": true
340
+ },
341
+ "TabItem": {
342
+ "summary": null,
343
+ "variants": {},
344
+ "detail": "dist/meta/TabItem.json",
345
+ "hasIntent": false
346
+ },
347
+ "Table": {
348
+ "summary": "A data table. Table owns the scroll container and density; rows and cells are composed.",
349
+ "status": "stable",
350
+ "variants": {
351
+ "density": [
352
+ "compact",
353
+ "default",
354
+ "relaxed"
355
+ ]
356
+ },
357
+ "detail": "dist/meta/Table.json",
358
+ "hasIntent": true
359
+ },
360
+ "TableBody": {
361
+ "summary": null,
362
+ "variants": {},
363
+ "detail": "dist/meta/TableBody.json",
364
+ "hasIntent": false
365
+ },
366
+ "TableCell": {
367
+ "summary": "One component covers Figma's `Table Cell` + `Cell Text`: the two are never",
368
+ "variants": {
369
+ "scope": [
370
+ "row",
371
+ "rowgroup",
372
+ "col",
373
+ "colgroup"
374
+ ],
375
+ "align": [
376
+ "center",
377
+ "leading",
378
+ "trailing"
379
+ ],
380
+ "variant": [
381
+ "link",
382
+ "default"
383
+ ]
384
+ },
385
+ "detail": "dist/meta/TableCell.json",
386
+ "hasIntent": false
387
+ },
388
+ "TableHead": {
389
+ "summary": null,
390
+ "variants": {},
391
+ "detail": "dist/meta/TableHead.json",
392
+ "hasIntent": false
393
+ },
394
+ "TableRow": {
395
+ "summary": "A plain `<tr>`. Hover is CSS-only (`:hover` plus a `data-hovered` escape",
396
+ "variants": {},
397
+ "detail": "dist/meta/TableRow.json",
398
+ "hasIntent": false
399
+ },
400
+ "Tabs": {
401
+ "summary": null,
402
+ "variants": {
403
+ "type": [
404
+ "pill",
405
+ "underline"
406
+ ],
407
+ "size": [
408
+ "sm",
409
+ "md",
410
+ "lg"
411
+ ],
412
+ "orientation": [
413
+ "horizontal",
414
+ "vertical"
415
+ ]
416
+ },
417
+ "detail": "dist/meta/Tabs.json",
418
+ "hasIntent": false
419
+ },
420
+ "Toast": {
421
+ "summary": "Toast — Figma `Toast` (820:1655).",
422
+ "variants": {
423
+ "intent": [
424
+ "neutral",
425
+ "primary",
426
+ "success",
427
+ "warning",
428
+ "error",
429
+ "information"
430
+ ]
431
+ },
432
+ "detail": "dist/meta/Toast.json",
433
+ "hasIntent": false
434
+ },
435
+ "ToastProvider": {
436
+ "summary": "Renders the queue and supplies `useToast`.",
437
+ "variants": {
438
+ "placement": [
439
+ "top-left",
440
+ "top-right",
441
+ "bottom-left",
442
+ "bottom-right"
443
+ ]
444
+ },
445
+ "detail": "dist/meta/ToastProvider.json",
446
+ "hasIntent": false
447
+ },
448
+ "Toggle": {
449
+ "summary": "Toggle is a checkbox with `role=\"switch\"`.",
450
+ "variants": {
451
+ "size": [
452
+ "sm",
453
+ "md",
454
+ "lg"
455
+ ],
456
+ "intent": [
457
+ "neutral",
458
+ "brand",
459
+ "danger"
460
+ ]
461
+ },
462
+ "detail": "dist/meta/Toggle.json",
463
+ "hasIntent": false
464
+ },
465
+ "Tooltip": {
466
+ "summary": "Tooltip — Figma `Tooltip` (801:1568).",
467
+ "variants": {
468
+ "placement": [
469
+ "right",
470
+ "left",
471
+ "top",
472
+ "bottom"
473
+ ]
474
+ },
475
+ "detail": "dist/meta/Tooltip.json",
476
+ "hasIntent": false
477
+ }
478
+ }
479
+ }
@@ -45,7 +45,9 @@
45
45
  @import url('./divider.css');
46
46
  @import url('./link.css');
47
47
  @import url('./modal.css');
48
+ @import url('./popover.css');
48
49
  @import url('./tooltip.css');
50
+ @import url('./toast.css');
49
51
  @import url('./nav-item.css');
50
52
  @import url('./scroll-progress.css');
51
53
  @import url('./full-card.css');
@@ -0,0 +1,185 @@
1
+ /*
2
+ * Popover
3
+ *
4
+ * Measured from Figma `Popover` (825:1853). Placement x Size.
5
+ *
6
+ * IT SITS BETWEEN TOOLTIP AND MODAL
7
+ *
8
+ * A tooltip is a hint that cannot hold focusable content; a modal is a task
9
+ * that takes over the page. A popover holds interactive content but stays
10
+ * attached to the control that opened it — no visible scrim, anchored to its
11
+ * trigger, and `surface/raised` rather than Modal's `surface/overlay`.
12
+ */
13
+ .ion-popover {
14
+ --ion-popover-width: 320px;
15
+
16
+ position: absolute;
17
+ z-index: 1150;
18
+
19
+ /*
20
+ * Between Modal (1000) and Toast (1200). A popover opened from a control
21
+ * inside a dialog has to sit above the dialog, and a toast confirming what
22
+ * the popover did has to sit above both.
23
+ */
24
+ display: flex;
25
+ flex-direction: column;
26
+ gap: var(--spacing-12);
27
+ width: var(--ion-popover-width);
28
+ max-width: calc(100vw - var(--spacing-16) * 2);
29
+ padding: var(--spacing-16);
30
+ background-color: var(--surface-raised);
31
+
32
+ /* For dark mode, where surface/raised is neutral.800 against a near-black
33
+ * page and the shadow alone does not separate them. Same as Toast. */
34
+ border: var(--border-width-default) solid var(--border-subtle);
35
+ border-radius: var(--radius-md);
36
+ box-shadow: var(--ion-shadow-shadow-lg);
37
+ font-family: var(--font-family-sans);
38
+ }
39
+
40
+ .ion-popover--sm {
41
+ --ion-popover-width: 280px;
42
+ }
43
+
44
+ .ion-popover--lg {
45
+ --ion-popover-width: 400px;
46
+ }
47
+
48
+ /*
49
+ * The underlay listens, it does not paint.
50
+ *
51
+ * Modal's scrim is visible because a modal replaces the page. This one is
52
+ * transparent: it exists only so a click anywhere outside closes the popover,
53
+ * while everything behind stays legible.
54
+ */
55
+ .ion-popover__underlay {
56
+ position: fixed;
57
+ inset: 0;
58
+ z-index: 1140;
59
+ }
60
+
61
+ /*
62
+ * The arrow, as a rotated square rather than a border triangle.
63
+ *
64
+ * Tooltip uses borders because it has no outline to match. This panel has a
65
+ * 1px border, so the caret needs two of its edges drawn — a rotated square
66
+ * with a border on two sides is the only shape that produces a continuous
67
+ * outline where it meets the panel. React Aria positions it along the edge;
68
+ * these rules only decide which side it sits on and which edges are drawn.
69
+ */
70
+ .ion-popover__arrow {
71
+ position: absolute;
72
+ width: 12px;
73
+ height: 12px;
74
+ background-color: var(--surface-raised);
75
+ border: var(--border-width-default) solid var(--border-subtle);
76
+ transform: rotate(45deg);
77
+ }
78
+
79
+ /*
80
+ * `--top` means the popover is ABOVE the trigger, so the caret hangs off the
81
+ * bottom edge. These follow the RESOLVED placement: react-aria flips on
82
+ * collision, and an arrow left on the requested side points away from the
83
+ * trigger entirely.
84
+ *
85
+ * The half-width negative offset centres the rotated square on the edge, and
86
+ * clearing two borders leaves the seam continuous with the panel.
87
+ */
88
+ .ion-popover--top .ion-popover__arrow {
89
+ bottom: -7px;
90
+ border-top: none;
91
+ border-left: none;
92
+ }
93
+
94
+ .ion-popover--bottom .ion-popover__arrow {
95
+ top: -7px;
96
+ border-bottom: none;
97
+ border-right: none;
98
+ }
99
+
100
+ .ion-popover--left .ion-popover__arrow {
101
+ right: -7px;
102
+ border-bottom: none;
103
+ border-left: none;
104
+ }
105
+
106
+ .ion-popover--right .ion-popover__arrow {
107
+ left: -7px;
108
+ border-top: none;
109
+ border-right: none;
110
+ }
111
+
112
+ .ion-popover__header {
113
+ display: flex;
114
+ align-items: flex-start;
115
+ gap: var(--spacing-12);
116
+ }
117
+
118
+ .ion-popover__title {
119
+ margin: 0;
120
+ flex: 1 1 auto;
121
+ min-width: 0;
122
+ color: var(--text-default);
123
+ font-size: var(--type-body);
124
+ line-height: var(--type-body-line-height);
125
+ font-weight: var(--font-weight-medium);
126
+ }
127
+
128
+ /*
129
+ * 32px around a 20px glyph — WCAG 2.2 SC 2.5.8 asks 24x24 minimum. Same
130
+ * reasoning as Modal's close, Alert's dismiss and Toast's.
131
+ */
132
+ .ion-popover__close {
133
+ display: inline-flex;
134
+ align-items: center;
135
+ justify-content: center;
136
+ flex-shrink: 0;
137
+ width: var(--spacing-32);
138
+ height: var(--spacing-32);
139
+ padding: 0;
140
+ background: none;
141
+ border: none;
142
+ border-radius: var(--radius-sm);
143
+ color: var(--icon-secondary);
144
+ cursor: pointer;
145
+ transition: background-color var(--ion-duration-base) var(--ion-ease-out);
146
+ }
147
+
148
+ .ion-popover__close svg {
149
+ width: var(--icon-size-md);
150
+ height: var(--icon-size-md);
151
+ }
152
+
153
+ @media (hover: hover) {
154
+ .ion-popover__close:hover {
155
+ background-color: var(--surface-hover);
156
+ }
157
+ }
158
+
159
+ .ion-popover__close:focus-visible {
160
+ outline: var(--border-width-thick) solid var(--ring-focus);
161
+ outline-offset: 2px;
162
+ }
163
+
164
+ /*
165
+ * The body is the only part that scrolls, capped against the viewport. A
166
+ * popover anchored near an edge would otherwise run off it — Figma panels hug
167
+ * because a Figma frame has no viewport to overflow.
168
+ */
169
+ .ion-popover__body {
170
+ max-height: 60vh;
171
+ overflow-y: auto;
172
+ color: var(--text-secondary);
173
+ font-size: var(--type-body-sm);
174
+ line-height: var(--type-body-sm-line-height);
175
+ font-weight: var(--font-weight-regular);
176
+ overflow-wrap: anywhere;
177
+ }
178
+
179
+ .ion-popover__footer {
180
+ display: flex;
181
+ align-items: center;
182
+ justify-content: flex-end;
183
+ gap: var(--spacing-8);
184
+ flex-shrink: 0;
185
+ }