@qeetrix/react 0.0.0-stage → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/CHANGELOG.md +181 -0
  2. package/README.md +167 -2
  3. package/dist/accordion/accordion.d.ts +42 -0
  4. package/dist/accordion/accordion.js +72 -0
  5. package/dist/accordion/index.d.ts +2 -0
  6. package/dist/accordion/index.js +1 -0
  7. package/dist/avatar/avatar.d.ts +21 -0
  8. package/dist/avatar/avatar.js +43 -0
  9. package/dist/avatar/index.d.ts +2 -0
  10. package/dist/avatar/index.js +1 -0
  11. package/dist/badge/badge.d.ts +14 -0
  12. package/dist/badge/badge.js +25 -0
  13. package/dist/badge/index.d.ts +2 -0
  14. package/dist/badge/index.js +1 -0
  15. package/dist/box/box.d.ts +27 -0
  16. package/dist/box/box.js +21 -0
  17. package/dist/box/index.d.ts +2 -0
  18. package/dist/box/index.js +1 -0
  19. package/dist/button/button.d.ts +46 -0
  20. package/dist/button/button.js +74 -0
  21. package/dist/button/index.d.ts +2 -0
  22. package/dist/button/index.js +1 -0
  23. package/dist/card/card.d.ts +30 -0
  24. package/dist/card/card.js +56 -0
  25. package/dist/card/index.d.ts +2 -0
  26. package/dist/card/index.js +1 -0
  27. package/dist/checkbox/checkbox.d.ts +33 -0
  28. package/dist/checkbox/checkbox.js +61 -0
  29. package/dist/checkbox/index.d.ts +2 -0
  30. package/dist/checkbox/index.js +1 -0
  31. package/dist/css/accordion.css +73 -0
  32. package/dist/css/avatar.css +44 -0
  33. package/dist/css/badge.css +53 -0
  34. package/dist/css/base.css +5 -0
  35. package/dist/css/box.css +163 -0
  36. package/dist/css/button.css +168 -0
  37. package/dist/css/card.css +75 -0
  38. package/dist/css/checkbox.css +58 -0
  39. package/dist/css/dialog.css +121 -0
  40. package/dist/css/drawer.css +123 -0
  41. package/dist/css/field.css +121 -0
  42. package/dist/css/fieldset.css +37 -0
  43. package/dist/css/grid.css +86 -0
  44. package/dist/css/heading.css +93 -0
  45. package/dist/css/inline.css +85 -0
  46. package/dist/css/input.css +98 -0
  47. package/dist/css/link.css +45 -0
  48. package/dist/css/menu.css +149 -0
  49. package/dist/css/popover.css +72 -0
  50. package/dist/css/progress.css +88 -0
  51. package/dist/css/radio.css +54 -0
  52. package/dist/css/scroll-area.css +63 -0
  53. package/dist/css/select.css +173 -0
  54. package/dist/css/separator.css +28 -0
  55. package/dist/css/skeleton.css +33 -0
  56. package/dist/css/skip-link.css +33 -0
  57. package/dist/css/slider.css +86 -0
  58. package/dist/css/spinner.css +55 -0
  59. package/dist/css/stack.css +76 -0
  60. package/dist/css/switch.css +79 -0
  61. package/dist/css/table.css +68 -0
  62. package/dist/css/tabs.css +112 -0
  63. package/dist/css/text-field.css +4 -0
  64. package/dist/css/text.css +106 -0
  65. package/dist/css/toast.css +108 -0
  66. package/dist/css/toggle.css +112 -0
  67. package/dist/css/tooltip.css +41 -0
  68. package/dist/css/visually-hidden.css +15 -0
  69. package/dist/dialog/dialog.d.ts +51 -0
  70. package/dist/dialog/dialog.js +120 -0
  71. package/dist/dialog/index.d.ts +2 -0
  72. package/dist/dialog/index.js +1 -0
  73. package/dist/drawer/drawer.d.ts +46 -0
  74. package/dist/drawer/drawer.js +101 -0
  75. package/dist/drawer/index.d.ts +2 -0
  76. package/dist/drawer/index.js +1 -0
  77. package/dist/field/choice.d.ts +21 -0
  78. package/dist/field/choice.js +67 -0
  79. package/dist/field/field.d.ts +28 -0
  80. package/dist/field/field.js +77 -0
  81. package/dist/field/index.d.ts +3 -0
  82. package/dist/field/index.js +1 -0
  83. package/dist/fieldset/fieldset.d.ts +19 -0
  84. package/dist/fieldset/fieldset.js +29 -0
  85. package/dist/fieldset/index.d.ts +2 -0
  86. package/dist/fieldset/index.js +1 -0
  87. package/dist/grid/grid.d.ts +30 -0
  88. package/dist/grid/grid.js +36 -0
  89. package/dist/grid/index.d.ts +2 -0
  90. package/dist/grid/index.js +1 -0
  91. package/dist/heading/heading.d.ts +23 -0
  92. package/dist/heading/heading.js +30 -0
  93. package/dist/heading/index.d.ts +2 -0
  94. package/dist/heading/index.js +1 -0
  95. package/dist/index.d.ts +80 -0
  96. package/dist/index.js +38 -0
  97. package/dist/inline/index.d.ts +2 -0
  98. package/dist/inline/index.js +1 -0
  99. package/dist/inline/inline.d.ts +19 -0
  100. package/dist/inline/inline.js +16 -0
  101. package/dist/input/index.d.ts +2 -0
  102. package/dist/input/index.js +1 -0
  103. package/dist/input/input.d.ts +37 -0
  104. package/dist/input/input.js +63 -0
  105. package/dist/internal/control.d.ts +9 -0
  106. package/dist/internal/control.js +4 -0
  107. package/dist/internal/cx.js +7 -0
  108. package/dist/internal/messages.js +22 -0
  109. package/dist/internal/refs.js +23 -0
  110. package/dist/internal/render.d.ts +6 -0
  111. package/dist/internal/render.js +22 -0
  112. package/dist/internal/space.d.ts +9 -0
  113. package/dist/internal/text.d.ts +5 -0
  114. package/dist/internal/text.js +18 -0
  115. package/dist/link/index.d.ts +2 -0
  116. package/dist/link/index.js +1 -0
  117. package/dist/link/link.d.ts +13 -0
  118. package/dist/link/link.js +31 -0
  119. package/dist/menu/index.d.ts +2 -0
  120. package/dist/menu/index.js +1 -0
  121. package/dist/menu/menu.d.ts +76 -0
  122. package/dist/menu/menu.js +172 -0
  123. package/dist/popover/index.d.ts +2 -0
  124. package/dist/popover/index.js +1 -0
  125. package/dist/popover/popover.d.ts +33 -0
  126. package/dist/popover/popover.js +48 -0
  127. package/dist/progress/index.d.ts +2 -0
  128. package/dist/progress/index.js +1 -0
  129. package/dist/progress/progress.d.ts +26 -0
  130. package/dist/progress/progress.js +57 -0
  131. package/dist/radio/index.d.ts +2 -0
  132. package/dist/radio/index.js +1 -0
  133. package/dist/radio/radio.d.ts +31 -0
  134. package/dist/radio/radio.js +47 -0
  135. package/dist/scroll-area/index.d.ts +2 -0
  136. package/dist/scroll-area/index.js +1 -0
  137. package/dist/scroll-area/scroll-area.d.ts +18 -0
  138. package/dist/scroll-area/scroll-area.js +39 -0
  139. package/dist/select/index.d.ts +2 -0
  140. package/dist/select/index.js +1 -0
  141. package/dist/select/select.d.ts +36 -0
  142. package/dist/select/select.js +82 -0
  143. package/dist/separator/index.d.ts +2 -0
  144. package/dist/separator/index.js +1 -0
  145. package/dist/separator/separator.d.ts +16 -0
  146. package/dist/separator/separator.js +16 -0
  147. package/dist/skeleton/index.d.ts +2 -0
  148. package/dist/skeleton/index.js +1 -0
  149. package/dist/skeleton/skeleton.d.ts +12 -0
  150. package/dist/skeleton/skeleton.js +18 -0
  151. package/dist/skip-link/index.d.ts +2 -0
  152. package/dist/skip-link/index.js +1 -0
  153. package/dist/skip-link/skip-link.d.ts +13 -0
  154. package/dist/skip-link/skip-link.js +18 -0
  155. package/dist/slider/index.d.ts +2 -0
  156. package/dist/slider/index.js +1 -0
  157. package/dist/slider/slider.d.ts +17 -0
  158. package/dist/slider/slider.js +51 -0
  159. package/dist/spinner/index.d.ts +2 -0
  160. package/dist/spinner/index.js +1 -0
  161. package/dist/spinner/spinner.d.ts +15 -0
  162. package/dist/spinner/spinner.js +19 -0
  163. package/dist/stack/index.d.ts +2 -0
  164. package/dist/stack/index.js +1 -0
  165. package/dist/stack/stack.d.ts +17 -0
  166. package/dist/stack/stack.js +15 -0
  167. package/dist/styles.css +2960 -0
  168. package/dist/switch/index.d.ts +2 -0
  169. package/dist/switch/index.js +1 -0
  170. package/dist/switch/switch.d.ts +18 -0
  171. package/dist/switch/switch.js +31 -0
  172. package/dist/table/index.d.ts +2 -0
  173. package/dist/table/index.js +1 -0
  174. package/dist/table/table.d.ts +36 -0
  175. package/dist/table/table.js +69 -0
  176. package/dist/tabs/index.d.ts +2 -0
  177. package/dist/tabs/index.js +1 -0
  178. package/dist/tabs/tabs.d.ts +43 -0
  179. package/dist/tabs/tabs.js +66 -0
  180. package/dist/text/index.d.ts +3 -0
  181. package/dist/text/index.js +1 -0
  182. package/dist/text/text.d.ts +24 -0
  183. package/dist/text/text.js +24 -0
  184. package/dist/text-field/index.d.ts +2 -0
  185. package/dist/text-field/index.js +1 -0
  186. package/dist/text-field/text-field.d.ts +15 -0
  187. package/dist/text-field/text-field.js +41 -0
  188. package/dist/toast/index.d.ts +2 -0
  189. package/dist/toast/index.js +1 -0
  190. package/dist/toast/toast.d.ts +54 -0
  191. package/dist/toast/toast.js +123 -0
  192. package/dist/toggle/index.d.ts +2 -0
  193. package/dist/toggle/index.js +1 -0
  194. package/dist/toggle/toggle.d.ts +48 -0
  195. package/dist/toggle/toggle.js +54 -0
  196. package/dist/tooltip/index.d.ts +2 -0
  197. package/dist/tooltip/index.js +1 -0
  198. package/dist/tooltip/tooltip.d.ts +26 -0
  199. package/dist/tooltip/tooltip.js +39 -0
  200. package/dist/visually-hidden/index.d.ts +2 -0
  201. package/dist/visually-hidden/index.js +1 -0
  202. package/dist/visually-hidden/visually-hidden.d.ts +8 -0
  203. package/dist/visually-hidden/visually-hidden.js +12 -0
  204. package/package.json +219 -4
package/CHANGELOG.md ADDED
@@ -0,0 +1,181 @@
1
+ # @qeetrix/react
2
+
3
+ ## 0.0.2
4
+
5
+ Every primitive, in six increments: the foundation, actions, form controls, overlays, selection and
6
+ disclosure, and display and status.
7
+
8
+ ### Foundation
9
+
10
+ All nine are server-safe and render in React Server Components.
11
+
12
+ - **Layout:**
13
+ - `Box`: a token-only surface (padding, background, border, corners);
14
+ - `Stack` and `Inline`: vertical and horizontal flows with token gaps;
15
+ - `Grid`: responsive equal columns on the window size classes.
16
+ - **Type:**
17
+ - `Text`: body roles, weights, tones, monospace, truncation;
18
+ - `Heading`: the level sets the element, the size sets the look; Geo letterforms for display.
19
+ - **`Separator`:** 1px, horizontal or vertical, optionally decorative.
20
+ - **Accessibility:**
21
+ - `VisuallyHidden`: screen-reader-only content;
22
+ - `SkipLink`: the first focusable element on a page, hidden until focused.
23
+ - **Shared types:** `Space`, `Inset`, `TextAlign`, `RenderProp`.
24
+ - **Imports:** every component also has its own entry point (`@qeetrix/react/stack`) and stylesheet
25
+ (`@qeetrix/react/css/stack.css`).
26
+
27
+ ### Actions
28
+
29
+ - **`Button`** (on Base UI Button):
30
+ - the label comes first, with an optional sharp trailing icon;
31
+ - variants `solid` (Ember primary), `soft` (warm-graphite secondary) and `ghost`; tones `brand` and
32
+ `danger`; sizes that follow the density;
33
+ - `loading` sets `aria-busy` and shows a spinner in the icon's place;
34
+ - a disabled button stays focusable, so its reason can be read.
35
+ - **`IconButton`:** square and icon-only; its `aria-label` is required by its type.
36
+ - **`Link`:** default, subtle and inherit tones. `external` opens a new tab with
37
+ `rel="noopener noreferrer"`, an arrow mark and a screen-reader note.
38
+ - **`Toggle` and `ToggleGroup`** (on Base UI):
39
+ - pressed state with `onPressedChange`, and `filledWhenPressed` swaps to the icon's filled drawing;
40
+ - groups are segmented or separate, single-choice or `multiple`, with arrow-key focus.
41
+ - **`Spinner`:** four sizes. It is announced as a status unless `decorative`, and fades instead of
42
+ spinning under reduced motion.
43
+ - **Shared types:** `ControlSize`, `QeetrixIcon`.
44
+ - **Dependencies:**
45
+ - new peer `@base-ui/react` ^1.9.0;
46
+ - new dependency `@qeetrix/icons` ^1.0.12, for the external-link mark.
47
+
48
+ ### Form controls
49
+
50
+ All are client components on Base UI's Field, Fieldset, Input, Checkbox, Radio Group and Switch, so
51
+ labels, descriptions and errors link to their controls without ids. On every form control, `ref` and
52
+ extra props go to the control, while `className` and `style` place the row.
53
+
54
+ - **`Field`:**
55
+ - a label, help text and error around any control;
56
+ - `optional` adds "(optional)", and Qeetrix never marks required fields;
57
+ - `error` shows a message now (from the server, say) and marks the field invalid. Without it, the
58
+ browser's or `validate`'s message shows with a sharp alert icon;
59
+ - the error replaces the help text;
60
+ - `hideLabel` is for toolbar searches only.
61
+ - **`Input`:** the filled box with a bottom line, a leading `icon`, and start and end adornments.
62
+ It takes its focus, invalid, read-only and disabled looks from the input.
63
+ - **`Textarea`:** the same look. It grows with its content between `rows` and `maxRows` through CSS
64
+ `field-sizing`, with no script.
65
+ - **`TextField`:** Field and Input in one, the default form row.
66
+ - **`Fieldset`:** a section of fields under a `title.sm` legend, spaced by the density's form gap.
67
+ `disabled` disables everything inside.
68
+ - **`Checkbox` and `CheckboxGroup`:**
69
+ - the 16px box with a sharp tick, and the mixed state with a dash;
70
+ - a `parent` checkbox selects the whole group;
71
+ - the label row is at least 24px tall.
72
+ - **`Radio` and `RadioGroup`:** the 5px Ember ring. Arrow keys move and select, and RadioGroup is
73
+ generic over its value type.
74
+ - **`Switch`:** a 32×18 track with a square thumb that moves to the end side, in either text
75
+ direction.
76
+ - With no visible label, Checkbox, Radio and Switch require `aria-label` or `aria-labelledby` by
77
+ their type.
78
+ - **Shared types:** `FieldMessageProps`, `ChoiceLabelProps`, `ChoiceOrientation`.
79
+
80
+ ### Overlays
81
+
82
+ All are client components on Base UI. Each has a root, a trigger that renders your control, and a
83
+ content part that assembles the portal, positioner and popup.
84
+
85
+ - **`Popover`:** an anchored panel with an optional title and description. Focus moves in on open
86
+ and returns to the trigger on close.
87
+ - **`Tooltip`:** one component around its trigger. It opens after 500ms of hover or at once on
88
+ focus. `TooltipProvider` groups neighbours, so the next one opens at once.
89
+ - **`Menu`:**
90
+ - items with sharp icons, shortcuts and a `danger` tone;
91
+ - checkbox and radio items in a 16px mark column;
92
+ - group labels, separators and submenus;
93
+ - the 2px Ember start bar on the highlighted item, which follows the text direction.
94
+ - **`Dialog`:**
95
+ - `intent` is `standard`, `danger` or `warning`, the last two as alert dialogs with a red or amber
96
+ intent line;
97
+ - `dismissible`, off by default for `danger`, so only its actions close it;
98
+ - sizes 400, 560 and 800px;
99
+ - the close button comes last in the focus order, so a dialog opens on its first field or its
100
+ least destructive action.
101
+ - **`Drawer`:**
102
+ - a full-height side panel at the `end` or `start` edge;
103
+ - ruled header and footer, and a scrolling body;
104
+ - focus goes to the first field, except on touch;
105
+ - it can be swiped away.
106
+ - **`ToastProvider` and `useToast`:**
107
+ - `add`, `close`, `update` and `promise`;
108
+ - tones `info`, `success`, `warning` and `danger`, each with a 3px status edge and a sharp icon;
109
+ - one action at most;
110
+ - `danger` toasts stay until dismissed and are announced at once.
111
+ - **`IconButton`** now shows its `aria-label` as a tooltip. `tooltip={false}` turns it off, and
112
+ other text replaces it.
113
+ - **Stacking:** anchored layers use `z.popover`, so a menu opened in a dialog sits above it.
114
+
115
+ ### Selection and disclosure
116
+
117
+ - **`Select`:**
118
+ - a field-style trigger with a chevron, and a menu-style list below it;
119
+ - the chosen option carries an Ember check;
120
+ - `items` can be flat or grouped, and `multiple` lists the chosen labels;
121
+ - generic over the value. A Field labels it.
122
+ - **`Tabs`, `TabsList`, `Tab`, `TabsPanel`:**
123
+ - the 3px Ember bar slides under the selected tab, and `TabsList` draws it;
124
+ - a selected tab has weight 600 and can fill its icon;
125
+ - padding is 12px at compact density and 16px otherwise;
126
+ - `activateOnFocus` is on by default;
127
+ - vertical tabs carry the line and bar on the start edge.
128
+ - **`Slider`:**
129
+ - a 4px track, the Ember range, and a 16px square thumb ringed in the surface colour;
130
+ - `label` and `showValue` with `Intl` formatting;
131
+ - a range renders one thumb per value, named Minimum and Maximum.
132
+ - **`Accordion`, `AccordionItem`:**
133
+ - ruled sections with a `title.sm` trigger inside a heading (`headingLevel`), and a turning
134
+ chevron;
135
+ - the panel's height animates;
136
+ - find in page opens a closed section that holds the match.
137
+ - **`Collapsible`, `CollapsibleTrigger`, `CollapsiblePanel`:** one unruled section.
138
+ - **`ScrollArea`:** 8px scrollbars that show on hover, while scrolling and on keyboard focus. The
139
+ region takes focus when it overflows.
140
+
141
+ ### Display and status
142
+
143
+ - **`Card`:**
144
+ - a raised surface with a 1px line, 4px corners and no shadow;
145
+ - a ruled header with `title`, the required heading `level`, `description` and `actions`, and an
146
+ optional ruled `footer`;
147
+ - a titled card is a section labelled by its title, and a table in the body runs edge to edge.
148
+ - Server-safe.
149
+ - **`Table`, `TableCaption`, `TableHeader`, `TableBody`, `TableRow`, `TableHead`, `TableCell`:**
150
+ - a neutral header band and row heights from the density;
151
+ - `numeric` cells use tabular figures, end-aligned;
152
+ - `selected` rows keep their Ember tint under the pointer.
153
+ - Server-safe.
154
+ - **`Badge`:** pills in six tones with an optional sharp icon. The `brand` fill is
155
+ `bg.brand.subtle`, so its text meets AA. Server-safe.
156
+ - **`Avatar`:** a circle with the photo, or initials while it loads or when it fails. The initials
157
+ use whole graphemes, so Indic names stay intact. It can be `decorative`.
158
+ - **`Progress` and `Meter`:** Slider's header, a 4px track and an Ember indicator (Meter takes a
159
+ status tone). Indeterminate progress slides, and fades under reduced motion.
160
+ - **`Skeleton`:** `text`, `block` or `circle`. It pulses, holds still under reduced motion, and is
161
+ always hidden from assistive technology. Server-safe.
162
+
163
+ ### Platform
164
+
165
+ - Each family's stylesheet (`css/<family>.css`) now imports the base layer and the stylesheets of
166
+ the families it renders, so it works on its own.
167
+ - Screenshots wait for transitions to finish, and overlays are captured across the whole viewport.
168
+ The accessibility check also waits, since contrast measured mid-fade isn't real. It skips Base
169
+ UI's aria-hidden focus guards, which hand focus on the moment it lands.
170
+ - A type-only import between families no longer pulls in the other family's stylesheet.
171
+ - Screenshots freeze endless animations (spinner, skeleton, indeterminate progress), so they match
172
+ every run.
173
+ - The nightly Firefox and WebKit run tests without V8 coverage, which works only in Chromium.
174
+ Every browser test passes in all three engines.
175
+
176
+ ## 0.0.1
177
+
178
+ The engineering platform, with no components yet:
179
+ - an unbundled ESM build with type declarations;
180
+ - a precompiled stylesheet in the `qx.base` and `qx.components` cascade layers;
181
+ - quality gates, and a release path with npm trusted publishing and provenance.
package/README.md CHANGED
@@ -1,3 +1,168 @@
1
- # Temporary Holding Version
1
+ # @qeetrix/react
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The React components of **Qeetrix**, the Qeet Group design system: accessible, token-driven
4
+ primitives built on [Base UI](https://base-ui.com), styled by the Qeetrix design language.
5
+
6
+ > **Status: 0.0.x.** Every primitive is here: foundation, actions, form controls, overlays, selection
7
+ > and disclosure, and display and status. Components and patterns follow as Qeet products ask for
8
+ > them.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ bun add @qeetrix/react @qeetrix/tokens @base-ui/react @qeetrix/icons
14
+ ```
15
+
16
+ `@base-ui/react` powers the interactive components. Icons come from `@qeetrix/icons`: a component's
17
+ `icon` prop takes any of its icons, e.g. `icon={PlusIcon}`.
18
+
19
+ ## Styles
20
+
21
+ Load the tokens, then the component styles, once at the root of your app:
22
+
23
+ ```css
24
+ @import "@qeetrix/tokens";
25
+ @import "@qeetrix/react/styles.css" layer(components);
26
+ ```
27
+
28
+ The stylesheet is precompiled. Your Tailwind setup doesn't need to scan this package, and it ships no
29
+ utility classes. Qeetrix styles sit in the `qx.base` and `qx.components` cascade layers, so your own
30
+ layout utilities override them without `!important`.
31
+
32
+ ## Use
33
+
34
+ ```tsx
35
+ import { PlusIcon } from "@qeetrix/icons/icons/plus";
36
+ import { Box, Button, Grid, Heading, Inline, SkipLink, Stack, Text } from "@qeetrix/react";
37
+
38
+ export function Invoices() {
39
+ return (
40
+ <>
41
+ <SkipLink />
42
+ <main id="main">
43
+ <Stack gap="xl">
44
+ <Inline justify="between">
45
+ <Heading level={1}>Invoices</Heading>
46
+ <Button icon={PlusIcon}>New invoice</Button>
47
+ </Inline>
48
+ <Grid columns={{ base: 1, medium: 2, large: 3 }} gap="md">
49
+ <Box padding="lg" background="raised" border="default" radius="surface">
50
+ <Text tone="subtle">Due this month</Text>
51
+ </Box>
52
+ </Grid>
53
+ </Stack>
54
+ </main>
55
+ </>
56
+ );
57
+ }
58
+ ```
59
+
60
+ - **Server-safe:** layout, type, `Link`, `Spinner`, `Card`, `Table`, `Badge` and `Skeleton` render in
61
+ React Server Components. Everything built on Base UI is a client component; its `"use client"`
62
+ directive ships in the build.
63
+ - **`render` prop:** renders a different element with the same props, e.g. `render={<section />}`.
64
+ - **Per-component entry points:** `@qeetrix/react/stack`, plus its own stylesheet at
65
+ `@qeetrix/react/css/stack.css` if you'd rather not load the full one. A family's stylesheet brings
66
+ the base styles and those of the components it renders (`css/text-field.css` brings Field and
67
+ Input).
68
+
69
+ | Component | For |
70
+ |---|---|
71
+ | `Box` | A token-only surface: padding, background, border, corners |
72
+ | `Stack`, `Inline` | Vertical and horizontal flows with token gaps (`none` … `4xl`) |
73
+ | `Grid` | Equal columns per window size class (600 / 840 / 1200 / 1600px) |
74
+ | `Text` | Body text: size, weight, tone, monospace, truncation |
75
+ | `Heading` | `level` sets h1–h6; `size` sets the look (display, heading, title) |
76
+ | `Separator` | A 1px line, horizontal or vertical |
77
+ | `VisuallyHidden` | Content for screen readers only |
78
+ | `SkipLink` | "Skip to main content", hidden until focused |
79
+ | `Button` | Label first, an optional trailing icon; solid (primary), soft (secondary), ghost; brand or danger; `loading` |
80
+ | `IconButton` | A square, icon-only button; `aria-label` is required by its type and shows as a tooltip |
81
+ | `Link` | Inline navigation; `external` opens a new tab safely and says so to screen readers |
82
+ | `Toggle`, `ToggleGroup` | Pressed or not; segmented or separate groups, single or `multiple` |
83
+ | `Spinner` | An indeterminate wait, announced as a status unless `decorative` |
84
+ | `TextField` | Label, input, help text and error in one: the default form row |
85
+ | `Field` | The label, help text and error around any control; `optional` adds "(optional)" |
86
+ | `Input`, `Textarea` | The filled text box with a bottom line; Textarea grows with its content |
87
+ | `Fieldset` | A section of fields under a heading, spaced by the density's form gap |
88
+ | `Checkbox`, `CheckboxGroup` | On/off or several of a set; mixed state; a `parent` checkbox selects all |
89
+ | `Radio`, `RadioGroup` | One of a set; arrow keys move and select |
90
+ | `Switch` | A setting that applies at once |
91
+ | `Popover` | A panel anchored to a trigger: filters, details, a short form |
92
+ | `Tooltip` | A short label for a control; `TooltipProvider` groups neighbours |
93
+ | `Menu` | Actions and options from a trigger: icons, shortcuts, checkbox and radio items, submenus |
94
+ | `Dialog` | A focused task or decision; `intent="danger"` makes a confirmation only its actions close |
95
+ | `Drawer` | A side panel for details or an edit form, swipeable on touch |
96
+ | `ToastProvider`, `useToast` | Brief notifications with a status edge; errors stay until dismissed |
97
+ | `Select` | One option, or several, from up to about 15; looks like a field, opens like a menu |
98
+ | `Tabs` | Views of one context, with the sliding Ember bar |
99
+ | `Slider` | A number or a range on a scale, with a formatted value |
100
+ | `Accordion`, `Collapsible` | Ruled sections, or one section, that show and hide |
101
+ | `ScrollArea` | A scrolling region with Qeetrix scrollbars |
102
+ | `Card` | A raised, ruled surface with a title, actions and footer; a table inside runs edge to edge |
103
+ | `Table` | Semantic table parts with the header band, density row heights and selected rows |
104
+ | `Badge` | A static status pill in six tones |
105
+ | `Avatar` | A person's photo, or their initials |
106
+ | `Progress`, `Meter` | A task advancing, or a measurement within a range |
107
+ | `Skeleton` | A placeholder shaped like the content that's loading |
108
+
109
+ **Layout:** `className` is for placement only (margin, width, grid placement). Visual changes go
110
+ through props, so every Qeet product looks the same. On a form control, `className` and `style` place
111
+ the whole row, while `ref` and every other prop go to the control itself.
112
+
113
+ ```tsx
114
+ import { Checkbox, CheckboxGroup, Fieldset, Radio, RadioGroup, TextField } from "@qeetrix/react";
115
+
116
+ <Fieldset legend="Company">
117
+ <TextField label="Trade name" name="tradeName" optional />
118
+ <TextField label="GSTIN" name="gstin" required description="15 characters." />
119
+ <RadioGroup label="Billing cycle" name="cycle" defaultValue="monthly">
120
+ <Radio value="monthly" label="Monthly" />
121
+ <Radio value="yearly" label="Yearly" description="Two months free." />
122
+ </RadioGroup>
123
+ <CheckboxGroup label="Notify me by" name="notify" orientation="horizontal">
124
+ <Checkbox value="email" label="Email" />
125
+ <Checkbox value="sms" label="SMS" />
126
+ </CheckboxGroup>
127
+ </Fieldset>
128
+ ```
129
+
130
+ ## Overlays
131
+
132
+ Overlays share one shape: a root that holds the state, a trigger that renders your control, and a
133
+ content part that assembles everything else. Wrap the app once in `ToastProvider` (and, if you like,
134
+ `TooltipProvider`).
135
+
136
+ ```tsx
137
+ import {
138
+ Button, Dialog, DialogClose, DialogContent, DialogFooter, DialogTrigger, useToast,
139
+ } from "@qeetrix/react";
140
+
141
+ function RevokeKey({ onRevoke }: { onRevoke: () => Promise<void> }) {
142
+ const toast = useToast();
143
+ return (
144
+ <Dialog intent="danger">
145
+ <DialogTrigger render={<Button tone="danger">Revoke key</Button>} />
146
+ <DialogContent title="Revoke this API key?" description="Apps using it stop working at once.">
147
+ <DialogFooter>
148
+ <DialogClose render={<Button variant="ghost">Cancel</Button>} />
149
+ <DialogClose
150
+ render={<Button tone="danger">Revoke key</Button>}
151
+ onClick={() =>
152
+ toast.promise(onRevoke(), {
153
+ loading: { title: "Revoking…" },
154
+ success: { title: "Key revoked" },
155
+ error: { title: "Couldn't revoke the key" },
156
+ })
157
+ }
158
+ />
159
+ </DialogFooter>
160
+ </DialogContent>
161
+ </Dialog>
162
+ );
163
+ }
164
+ ```
165
+
166
+ ## License
167
+
168
+ [MIT](https://github.com/qeetgroup/qeetrix-react/blob/main/LICENSE) © Qeet Group
@@ -0,0 +1,42 @@
1
+ import { HeadingLevel } from "../heading/heading.js";
2
+ import { Accordion } from "@base-ui/react/accordion";
3
+ import { Collapsible } from "@base-ui/react/collapsible";
4
+ import { CSSProperties, ReactElement, ReactNode } from "react";
5
+ export interface AccordionProps<Value = string> extends Omit<Accordion.Root.Props<Value>, "className" | "style"> {
6
+ /** Placement only. */
7
+ className?: string;
8
+ style?: CSSProperties;
9
+ }
10
+ export interface AccordionItemProps extends Omit<Accordion.Item.Props, "className" | "style" | "title" | "children"> {
11
+ /** The section's title: the trigger's text. */
12
+ title: ReactNode;
13
+ /** The heading level that wraps the trigger. Default 3. */
14
+ headingLevel?: Exclude<HeadingLevel, 1>;
15
+ /** The section's content. */
16
+ children?: ReactNode;
17
+ className?: string;
18
+ style?: CSSProperties;
19
+ }
20
+ export type CollapsibleProps = Collapsible.Root.Props;
21
+ export type CollapsibleTriggerProps = Collapsible.Trigger.Props;
22
+ export interface CollapsiblePanelProps extends Omit<Collapsible.Panel.Props, "className" | "style"> {
23
+ className?: string;
24
+ style?: CSSProperties;
25
+ }
26
+ /**
27
+ * Stacked sections that show and hide, ruled between (06-component-specs.md, increment 5):
28
+ * `value` / `defaultValue` / `onValueChange(value[], details)`, `multiple`, `disabled`.
29
+ */
30
+ declare function Accordion$1<Value = string>({ className, ...props }: AccordionProps<Value>): ReactElement;
31
+ /**
32
+ * One section: a full-width trigger with a chevron, inside a heading, then the panel. Find in page
33
+ * opens a closed section that holds the match.
34
+ */
35
+ export declare function AccordionItem({ title, headingLevel, className, children, ...props }: AccordionItemProps): ReactElement;
36
+ /** One section without the rules, for "Show more" or advanced options. */
37
+ declare function Collapsible$1(props: CollapsibleProps): ReactElement;
38
+ /** Shows and hides the panel. Render your own control: `render={<Button variant="ghost" />}`. */
39
+ export declare function CollapsibleTrigger(props: CollapsibleTriggerProps): ReactElement;
40
+ /** The content that shows and hides, with the accordion's height animation. */
41
+ export declare function CollapsiblePanel({ className, ...props }: CollapsiblePanelProps): ReactElement;
42
+ export { Accordion$1 as Accordion, Collapsible$1 as Collapsible };
@@ -0,0 +1,72 @@
1
+ "use client";
2
+ import { cx } from "../internal/cx.js";
3
+ import { Accordion } from "@base-ui/react/accordion";
4
+ import { Collapsible } from "@base-ui/react/collapsible";
5
+ import { ChevronDownIcon } from "@qeetrix/icons/icons/chevron-down";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ // src/accordion/accordion.tsx
8
+ /**
9
+ * Stacked sections that show and hide, ruled between (06-component-specs.md, increment 5):
10
+ * `value` / `defaultValue` / `onValueChange(value[], details)`, `multiple`, `disabled`.
11
+ */
12
+ function Accordion$1({ className, ...props }) {
13
+ return /* @__PURE__ */ jsx(Accordion.Root, {
14
+ ...props,
15
+ className: cx("qx-accordion", className),
16
+ "data-slot": "accordion"
17
+ });
18
+ }
19
+ /**
20
+ * One section: a full-width trigger with a chevron, inside a heading, then the panel. Find in page
21
+ * opens a closed section that holds the match.
22
+ */
23
+ function AccordionItem({ title, headingLevel = 3, className, children, ...props }) {
24
+ const Heading = `h${headingLevel}`;
25
+ return /* @__PURE__ */ jsxs(Accordion.Item, {
26
+ ...props,
27
+ className: cx("qx-accordion-item", className),
28
+ "data-slot": "accordion-item",
29
+ children: [/* @__PURE__ */ jsx(Accordion.Header, {
30
+ render: /* @__PURE__ */ jsx(Heading, {}),
31
+ className: "qx-accordion-header",
32
+ children: /* @__PURE__ */ jsxs(Accordion.Trigger, {
33
+ className: "qx-accordion-trigger",
34
+ "data-slot": "accordion-trigger",
35
+ children: [/* @__PURE__ */ jsx("span", {
36
+ className: "qx-accordion-title",
37
+ children: title
38
+ }), /* @__PURE__ */ jsx(ChevronDownIcon, {
39
+ shape: "sharp",
40
+ size: 16,
41
+ "aria-hidden": true,
42
+ className: "qx-accordion-icon"
43
+ })]
44
+ })
45
+ }), /* @__PURE__ */ jsx(Accordion.Panel, {
46
+ hiddenUntilFound: true,
47
+ className: "qx-accordion-panel",
48
+ "data-slot": "accordion-panel",
49
+ children: /* @__PURE__ */ jsx("div", {
50
+ className: "qx-accordion-content",
51
+ children
52
+ })
53
+ })]
54
+ });
55
+ }
56
+ /** One section without the rules, for "Show more" or advanced options. */
57
+ function Collapsible$1(props) {
58
+ return /* @__PURE__ */ jsx(Collapsible.Root, { ...props });
59
+ }
60
+ /** Shows and hides the panel. Render your own control: `render={<Button variant="ghost" />}`. */
61
+ function CollapsibleTrigger(props) {
62
+ return /* @__PURE__ */ jsx(Collapsible.Trigger, { ...props });
63
+ }
64
+ /** The content that shows and hides, with the accordion's height animation. */
65
+ function CollapsiblePanel({ className, ...props }) {
66
+ return /* @__PURE__ */ jsx(Collapsible.Panel, {
67
+ ...props,
68
+ className: cx("qx-collapsible-panel", className),
69
+ "data-slot": "collapsible-panel"
70
+ });
71
+ }
72
+ export { Accordion$1 as Accordion, AccordionItem, Collapsible$1 as Collapsible, CollapsiblePanel, CollapsibleTrigger };
@@ -0,0 +1,2 @@
1
+ import { Accordion, AccordionItem, AccordionItemProps, AccordionProps, Collapsible, CollapsiblePanel, CollapsiblePanelProps, CollapsibleProps, CollapsibleTrigger, CollapsibleTriggerProps } from "./accordion.js";
2
+ export { Accordion, AccordionItem, type AccordionItemProps, type AccordionProps, Collapsible, CollapsiblePanel, type CollapsiblePanelProps, type CollapsibleProps, CollapsibleTrigger, type CollapsibleTriggerProps };
@@ -0,0 +1 @@
1
+ export { Accordion, AccordionItem, Collapsible, CollapsiblePanel, CollapsibleTrigger } from "./accordion.js";
@@ -0,0 +1,21 @@
1
+ import { Avatar } from "@base-ui/react/avatar";
2
+ import { CSSProperties, ReactElement } from "react";
3
+ export type AvatarSize = "sm" | "md" | "lg" | "xl";
4
+ export interface AvatarProps extends Omit<Avatar.Root.Props, "className" | "style" | "children"> {
5
+ /** The person's name: the image's text alternative, and the source of the initials. */
6
+ name: string;
7
+ /** The photo. The initials show while it loads, and if it fails. */
8
+ src?: string;
9
+ /** `sm` 24, `md` 32 (default), `lg` 40 or `xl` 48px. */
10
+ size?: AvatarSize;
11
+ /** The name is shown beside it: hide the avatar from assistive technology. */
12
+ decorative?: boolean;
13
+ className?: string;
14
+ style?: CSSProperties;
15
+ }
16
+ /**
17
+ * A person: their photo, or their initials (06-component-specs.md, increment 6). A circle, since
18
+ * people are one of the two soft shapes. It's named by `name`, unless `decorative`.
19
+ */
20
+ declare function Avatar$1({ name, src, size, decorative, className, ...props }: AvatarProps): ReactElement;
21
+ export { Avatar$1 as Avatar };
@@ -0,0 +1,43 @@
1
+ "use client";
2
+ import { cx } from "../internal/cx.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { Avatar } from "@base-ui/react/avatar";
5
+ // src/avatar/avatar.tsx
6
+ const graphemes = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : void 0;
7
+ function firstGrapheme(word) {
8
+ if (!graphemes) return Array.from(word)[0] ?? "";
9
+ for (const { segment } of graphemes.segment(word)) return segment;
10
+ return "";
11
+ }
12
+ /** The first grapheme of the first and last words: "Priya Sharma" is "PS". */
13
+ function initials(name) {
14
+ const words = name.trim().split(/\s+/).filter(Boolean);
15
+ const first = words[0] ?? "";
16
+ const last = words.length > 1 ? words.at(-1) ?? "" : "";
17
+ return (firstGrapheme(first) + firstGrapheme(last)).toLocaleUpperCase();
18
+ }
19
+ /**
20
+ * A person: their photo, or their initials (06-component-specs.md, increment 6). A circle, since
21
+ * people are one of the two soft shapes. It's named by `name`, unless `decorative`.
22
+ */
23
+ function Avatar$1({ name, src, size = "md", decorative, className, ...props }) {
24
+ return /* @__PURE__ */ jsxs(Avatar.Root, {
25
+ ...props,
26
+ role: decorative ? void 0 : "img",
27
+ "aria-label": decorative ? void 0 : name,
28
+ "aria-hidden": decorative || void 0,
29
+ className: cx("qx-avatar", className),
30
+ "data-slot": "avatar",
31
+ "data-size": size,
32
+ children: [src && /* @__PURE__ */ jsx(Avatar.Image, {
33
+ src,
34
+ alt: "",
35
+ className: "qx-avatar-image"
36
+ }), /* @__PURE__ */ jsx(Avatar.Fallback, {
37
+ className: "qx-avatar-fallback",
38
+ "aria-hidden": true,
39
+ children: initials(name)
40
+ })]
41
+ });
42
+ }
43
+ export { Avatar$1 as Avatar };
@@ -0,0 +1,2 @@
1
+ import { Avatar, AvatarProps, AvatarSize } from "./avatar.js";
2
+ export { Avatar, type AvatarProps, type AvatarSize };
@@ -0,0 +1 @@
1
+ export { Avatar } from "./avatar.js";
@@ -0,0 +1,14 @@
1
+ import { QeetrixIcon } from "../internal/control.js";
2
+ import { ComponentProps, ReactElement } from "react";
3
+ export type BadgeTone = "neutral" | "brand" | "info" | "success" | "warning" | "danger";
4
+ export interface BadgeProps extends ComponentProps<"span"> {
5
+ /** Default `neutral`. The word carries the meaning; the tone only reinforces it. */
6
+ tone?: BadgeTone;
7
+ /** A leading icon from `@qeetrix/icons`: sharp, 12px, decorative. */
8
+ icon?: QeetrixIcon;
9
+ }
10
+ /**
11
+ * A short, static status or count: one or two words (06-component-specs.md §10). It truncates with
12
+ * an ellipsis; pass `title` with the full text where it may be cut. Server-safe.
13
+ */
14
+ export declare function Badge({ tone, icon: Icon, className, children, ...props }: BadgeProps): ReactElement;
@@ -0,0 +1,25 @@
1
+ import { cx } from "../internal/cx.js";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ // src/badge/badge.tsx
4
+ /**
5
+ * A short, static status or count: one or two words (06-component-specs.md §10). It truncates with
6
+ * an ellipsis; pass `title` with the full text where it may be cut. Server-safe.
7
+ */
8
+ function Badge({ tone = "neutral", icon: Icon, className, children, ...props }) {
9
+ return /* @__PURE__ */ jsxs("span", {
10
+ ...props,
11
+ className: cx("qx-badge", className),
12
+ "data-slot": "badge",
13
+ "data-tone": tone,
14
+ children: [Icon && /* @__PURE__ */ jsx(Icon, {
15
+ shape: "sharp",
16
+ size: 12,
17
+ "aria-hidden": true,
18
+ className: "qx-badge-icon"
19
+ }), /* @__PURE__ */ jsx("span", {
20
+ className: "qx-badge-label",
21
+ children
22
+ })]
23
+ });
24
+ }
25
+ export { Badge };
@@ -0,0 +1,2 @@
1
+ import { Badge, BadgeProps, BadgeTone } from "./badge.js";
2
+ export { Badge, type BadgeProps, type BadgeTone };
@@ -0,0 +1 @@
1
+ export { Badge } from "./badge.js";
@@ -0,0 +1,27 @@
1
+ import { RenderProp } from "../internal/render.js";
2
+ import { Inset } from "../internal/space.js";
3
+ import { ComponentProps, ReactElement } from "react";
4
+ export type BoxBackground = "canvas" | "default" | "raised" | "sunken" | "overlay" | "inverse" | "neutral" | "brand" | "selected";
5
+ export type BoxBorder = "none" | "subtle" | "default" | "strong";
6
+ export type BoxRadius = "none" | "sm" | "control" | "surface" | "overlay" | "modal" | "chip" | "full";
7
+ export interface BoxProps extends ComponentProps<"div"> {
8
+ /** Padding on every side, from the inset tokens. */
9
+ padding?: Inset;
10
+ /** Padding at the start and end; overrides `padding` on those sides. */
11
+ paddingInline?: Inset;
12
+ /** Padding at the top and bottom; overrides `padding` on those sides. */
13
+ paddingBlock?: Inset;
14
+ /** A surface or tint from the tokens. `inverse` also sets inverse text colour. */
15
+ background?: BoxBackground;
16
+ /** A 1px border in a token colour. */
17
+ border?: BoxBorder;
18
+ /** Corners from the radius tokens. */
19
+ radius?: BoxRadius;
20
+ /** Render a different element, e.g. `<section />`. */
21
+ render?: RenderProp;
22
+ }
23
+ /**
24
+ * A token-only surface: the sanctioned way to give a region padding, a background, a border or
25
+ * corners. It has no margin: space between siblings belongs to the parent's `gap`.
26
+ */
27
+ export declare function Box({ padding, paddingInline, paddingBlock, background, border, radius, render, className, ...props }: BoxProps): ReactElement;
@@ -0,0 +1,21 @@
1
+ import { cx } from "../internal/cx.js";
2
+ import { renderElement } from "../internal/render.js";
3
+ // src/box/box.tsx
4
+ /**
5
+ * A token-only surface: the sanctioned way to give a region padding, a background, a border or
6
+ * corners. It has no margin: space between siblings belongs to the parent's `gap`.
7
+ */
8
+ function Box({ padding, paddingInline, paddingBlock, background, border, radius, render, className, ...props }) {
9
+ return renderElement("div", render, {
10
+ ...props,
11
+ className: cx("qx-box", className),
12
+ "data-slot": "box",
13
+ "data-padding": padding,
14
+ "data-padding-inline": paddingInline,
15
+ "data-padding-block": paddingBlock,
16
+ "data-background": background,
17
+ "data-border": border,
18
+ "data-radius": radius
19
+ });
20
+ }
21
+ export { Box };
@@ -0,0 +1,2 @@
1
+ import { Box, BoxBackground, BoxBorder, BoxProps, BoxRadius } from "./box.js";
2
+ export { Box, type BoxBackground, type BoxBorder, type BoxProps, type BoxRadius };
@@ -0,0 +1 @@
1
+ export { Box } from "./box.js";