@qeetrix/react 0.0.2 → 0.0.4

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 (104) hide show
  1. package/CHANGELOG.md +116 -1
  2. package/README.md +99 -0
  3. package/dist/button/button.d.ts +6 -2
  4. package/dist/button/button.js +5 -1
  5. package/dist/css/avatar.css +6 -0
  6. package/dist/css/badge.css +6 -0
  7. package/dist/css/button.css +81 -6
  8. package/dist/css/checkbox.css +9 -0
  9. package/dist/css/dialog.css +7 -0
  10. package/dist/css/drawer.css +6 -0
  11. package/dist/css/experimental/alert.css +89 -0
  12. package/dist/css/experimental/app-shell.css +258 -0
  13. package/dist/css/experimental/breadcrumb.css +67 -0
  14. package/dist/css/experimental/copy-button.css +13 -0
  15. package/dist/css/experimental/empty-state.css +63 -0
  16. package/dist/css/experimental/otp-input.css +84 -0
  17. package/dist/css/experimental/page-header.css +110 -0
  18. package/dist/css/experimental/password-input.css +21 -0
  19. package/dist/css/experimental/sidebar.css +234 -0
  20. package/dist/css/experimental/stat.css +115 -0
  21. package/dist/css/experimental/top-bar.css +299 -0
  22. package/dist/css/input.css +10 -0
  23. package/dist/css/menu.css +17 -0
  24. package/dist/css/progress.css +14 -0
  25. package/dist/css/scroll-area.css +7 -0
  26. package/dist/css/select.css +16 -0
  27. package/dist/css/separator.css +7 -0
  28. package/dist/css/skeleton.css +6 -0
  29. package/dist/css/slider.css +22 -0
  30. package/dist/css/switch.css +33 -0
  31. package/dist/css/tabs.css +11 -0
  32. package/dist/css/toast.css +7 -0
  33. package/dist/css/toggle.css +13 -0
  34. package/dist/css/tooltip.css +6 -0
  35. package/dist/dialog/dialog.d.ts +4 -1
  36. package/dist/dialog/dialog.js +4 -3
  37. package/dist/drawer/drawer.d.ts +4 -1
  38. package/dist/drawer/drawer.js +5 -4
  39. package/dist/experimental/alert/alert.d.ts +29 -0
  40. package/dist/experimental/alert/alert.js +72 -0
  41. package/dist/experimental/alert/index.d.ts +2 -0
  42. package/dist/experimental/app-shell/app-shell.d.ts +60 -0
  43. package/dist/experimental/app-shell/app-shell.js +224 -0
  44. package/dist/experimental/app-shell/index.d.ts +2 -0
  45. package/dist/experimental/breadcrumb/breadcrumb.d.ts +27 -0
  46. package/dist/experimental/breadcrumb/breadcrumb.js +48 -0
  47. package/dist/experimental/breadcrumb/index.d.ts +2 -0
  48. package/dist/experimental/copy-button/copy-button.d.ts +27 -0
  49. package/dist/experimental/copy-button/copy-button.js +61 -0
  50. package/dist/experimental/copy-button/index.d.ts +2 -0
  51. package/dist/experimental/empty-state/data-state.d.ts +23 -0
  52. package/dist/experimental/empty-state/data-state.js +40 -0
  53. package/dist/experimental/empty-state/empty-state.d.ts +26 -0
  54. package/dist/experimental/empty-state/empty-state.js +46 -0
  55. package/dist/experimental/empty-state/index.d.ts +3 -0
  56. package/dist/experimental/index.d.ts +24 -0
  57. package/dist/experimental/index.js +13 -0
  58. package/dist/experimental/otp-input/index.d.ts +2 -0
  59. package/dist/experimental/otp-input/otp-input.d.ts +27 -0
  60. package/dist/experimental/otp-input/otp-input.js +39 -0
  61. package/dist/experimental/page-header/index.d.ts +2 -0
  62. package/dist/experimental/page-header/page-header.d.ts +32 -0
  63. package/dist/experimental/page-header/page-header.js +73 -0
  64. package/dist/experimental/password-input/index.d.ts +2 -0
  65. package/dist/experimental/password-input/password-input.d.ts +26 -0
  66. package/dist/experimental/password-input/password-input.js +103 -0
  67. package/dist/experimental/sidebar/index.d.ts +2 -0
  68. package/dist/experimental/sidebar/sidebar.d.ts +64 -0
  69. package/dist/experimental/sidebar/sidebar.js +200 -0
  70. package/dist/experimental/stat/index.d.ts +2 -0
  71. package/dist/experimental/stat/stat.d.ts +31 -0
  72. package/dist/experimental/stat/stat.js +90 -0
  73. package/dist/experimental/top-bar/index.d.ts +2 -0
  74. package/dist/experimental/top-bar/top-bar.d.ts +60 -0
  75. package/dist/experimental/top-bar/top-bar.js +162 -0
  76. package/dist/field/field.d.ts +4 -1
  77. package/dist/field/field.js +6 -5
  78. package/dist/format/format.d.ts +12 -0
  79. package/dist/format/format.js +50 -0
  80. package/dist/format/index.d.ts +2 -0
  81. package/dist/format/index.js +1 -0
  82. package/dist/index.d.ts +6 -2
  83. package/dist/index.js +3 -1
  84. package/dist/internal/media.js +15 -0
  85. package/dist/internal/messages.d.ts +75 -0
  86. package/dist/internal/messages.js +44 -5
  87. package/dist/link/link.d.ts +7 -1
  88. package/dist/link/link.js +3 -2
  89. package/dist/locales/hi.d.ts +8 -0
  90. package/dist/locales/hi.js +60 -0
  91. package/dist/progress/progress.d.ts +2 -2
  92. package/dist/progress/progress.js +7 -2
  93. package/dist/provider/index.d.ts +3 -0
  94. package/dist/provider/index.js +1 -0
  95. package/dist/provider/provider.d.ts +46 -0
  96. package/dist/provider/provider.js +90 -0
  97. package/dist/slider/slider.d.ts +5 -2
  98. package/dist/slider/slider.js +10 -3
  99. package/dist/styles.css +1704 -104
  100. package/dist/text-field/text-field.d.ts +2 -2
  101. package/dist/text-field/text-field.js +4 -2
  102. package/dist/toast/toast.d.ts +4 -1
  103. package/dist/toast/toast.js +7 -6
  104. package/package.json +18 -2
package/CHANGELOG.md CHANGED
@@ -1,9 +1,89 @@
1
1
  # @qeetrix/react
2
2
 
3
+ ## 0.0.3
4
+
5
+ The first two Phase 5 increments, from `@qeetrix/react/experimental`: C1, the console frame, and
6
+ C2, secure entry. Experimental parts may change in a patch release; each moves to the root export
7
+ once it has shipped in its product (07-components.md).
8
+
9
+ ### C1 · Console frame
10
+
11
+ - **`AppShell`** (design B · Ruled, chosen from three researched concepts; 07-components.md):
12
+ - the frame of every Qeet console: a skip link, the sticky TopBar across the full width, the
13
+ navigation column on the canvas tone, the page, and an end-edge panel; every seam a 1px rule;
14
+ - from 840px the navigation collapses to a 48px rail (`sidebarCollapsed` /
15
+ `defaultSidebarCollapsed` / `onSidebarCollapsedChange`) that expands over the page while hovered
16
+ or focused; below 840px it opens as a drawer from the start edge;
17
+ - `drawers` (help, assistant, activity), opened from the TopBar's tools: one at a time, pushing
18
+ the page from 1200px; `notifications` above the page; `density`, compact by default;
19
+ - `useAppShell()` tells content inside the frame when the sidebar is a rail.
20
+ - **`TopBar`, `TopBarSwitcher`, `TopBarSearch`:**
21
+ - black in light and dark (the dark scheme on itself), topped by a 2px Ember line;
22
+ - product to global, left to right: launcher cell, product, the scope path with drawn slashes,
23
+ centred search, panel triggers and utilities, account;
24
+ - `TopBarSwitcher` is a step of the path (organisation mark, environment status dot, always a
25
+ word) and works as a Menu trigger; `TopBarSearch` is a field-shaped button with its shortcut.
26
+ - **`PageHeader`, `PageContent`:** breadcrumb, `title.lg` title with count and status, actions, a
27
+ `summary` strip bled to the page edges, and tabs at the foot; a 32px page gutter.
28
+ - **`Stat`, `StatGroup`:** key numbers in ruled cells; numeric values formatted for the locale; an
29
+ optional decorative trend line whose last point is the Ember mark. Server-safe.
30
+ - **`Sidebar`, `SidebarGroup`, `SidebarItem`:**
31
+ - links only, one level of nesting, every item in the Tab order;
32
+ - square rows edge to edge with round icons; the current page gets `aria-current`, the 3px Ember
33
+ bar, weight 600 and an Ember (optionally filled) icon;
34
+ - groups between hairlines under tracked small capitals (`typography.label.caps`; untracked for
35
+ Indic text, which has no case); an optional `title`, `filter` and `footer`;
36
+ - in the rail, icons stay where they are and labels become tooltips.
37
+ - **`Breadcrumb`, `BreadcrumbItem`:** server-safe; mirrored chevrons; long labels truncate and keep a
38
+ title.
39
+ - **`EmptyState`:** a 24px icon, a title (a heading when given a `level`), a description and actions,
40
+ in `page` or `section` size and `neutral` or `danger` tone. Server-safe.
41
+ - **`DataState`:** loading, then error, then empty, then the content. The error state never shows the
42
+ raw error, and `onRetry` adds "Try again".
43
+ - **Colour scheme:**
44
+ - `QeetrixProvider` takes `colorScheme`, `defaultColorScheme` and `onColorSchemeChange`, writes
45
+ `data-qx-color-scheme` on `<html>`, and follows the operating system while `system`;
46
+ - `useColorScheme()` returns the scheme and a setter. The product persists the choice.
47
+ - **`Button` and `IconButton`:**
48
+ - a `neutral` tone for ghost utility actions in toolbars and headers, whose icons stay neutral
49
+ (design rules §16);
50
+ - a Button's icon sits in its own zone (the new `button-zone` slot), behind a 1px rule as tall as
51
+ the icon, in the label's colour (design rules §7).
52
+ - **Strings:** new `appShell`, `sidebar`, `breadcrumb` and `dataState` groups, in English and Hindi.
53
+ - **Peer dependency:** `@qeetrix/tokens` `^0.0.7`, for `typography.label.caps`. Since tokens 0.0.6
54
+ the stylesheet also loads Qeet Sans and Fira Code, so the tests now render in the real typefaces.
55
+ - **Source check:** every `var(--qx-*)` must name a token in the installed `@qeetrix/tokens` or a
56
+ property the package declares itself, so a token from a newer tokens release can't ship
57
+ unresolved.
58
+ - **Platform:**
59
+ - the CSS build, the dist check and the screenshot coverage test understand
60
+ `src/experimental/<family>/`;
61
+ - the test helper treats cancelled animations as settled.
62
+
63
+ ### C2 · Secure entry
64
+
65
+ - **`CopyButton`:** copies a value (or what a function returns, even asynchronously); the icon
66
+ turns into a check, "Copied" is announced politely, focus stays, and it resets after 2 seconds.
67
+ It fails visibly where there's no clipboard. An IconButton named by `label`, or a Button with a
68
+ visible label.
69
+ - **`Alert`:** `info`, `success`, `warning` or `danger` on the status tint with the 3px status edge;
70
+ a title (led by a hidden tone word), a description, actions and an optional Dismiss. `announce`
71
+ makes it `role="status"` or `role="alert"` only when it appears after something happens. It never
72
+ takes focus or disappears by itself.
73
+ - **`PasswordInput`:** an Input with a pressed-state Show password toggle, announced; hidden by
74
+ default and hidden again when its form submits; `current-password` / `new-password` / `off`;
75
+ spelling and capitalisation tools off; a Caps Lock warning; `endAdornment` for a CopyButton.
76
+ - **`OTPInput`:** one-time codes in slots on Base UI's OTP Field: typing moves on, paste fills
77
+ every slot, SMS autofill lands at once; numeric by default; `groups`, `md` and `lg` sizes; the
78
+ first slot takes the field's label, the rest are "Character 2 of 6".
79
+ - **Strings:** new `copyButton`, `alert`, `passwordInput` and `otpInput` groups, in English and
80
+ Hindi.
81
+
3
82
  ## 0.0.2
4
83
 
5
84
  Every primitive, in six increments: the foundation, actions, form controls, overlays, selection and
6
- disclosure, and display and status.
85
+ disclosure, and display and status. Then the Phase 4 close-out: localisation, forced colours,
86
+ right-to-left, reduced motion and the sign-in exit screen.
7
87
 
8
88
  ### Foundation
9
89
 
@@ -160,8 +240,43 @@ content part that assembles the portal, positioner and popup.
160
240
  - **`Skeleton`:** `text`, `block` or `circle`. It pulses, holds still under reduced motion, and is
161
241
  always hidden from assistive technology. Server-safe.
162
242
 
243
+ ### Phase 4 close-out
244
+
245
+ - **`QeetrixProvider`:**
246
+ - `locale` (default `en-IN`) for formatting;
247
+ - `direction`, which wraps Base UI's DirectionProvider;
248
+ - `messages`, merged over the English defaults group by group.
249
+ - Nested providers inherit what they don't set. `useLocale()` reads the locale.
250
+ - **Strings:**
251
+ - every library string comes from one typed catalogue (`QeetrixMessages`);
252
+ - Field, TextField, DialogContent, DrawerContent, ToastProvider, Slider and Link take a `messages`
253
+ prop that wins over the provider;
254
+ - Link stays server-safe, so it takes its string per instance.
255
+ - **Locale packs:** Hindi (`@qeetrix/react/locales/hi`) is the first. It's a separate entry point,
256
+ typed as the complete catalogue, and uses Qeet ID's own Hindi words.
257
+ - **Formatting:** `formatNumber`, `formatCurrency` (lakh and crore grouping by default) and
258
+ `formatRelativeTime`. They're pure, server-safe and cache their formatters. Slider, Progress and
259
+ Meter format in the provider's locale.
260
+ - **Forced colours (Windows High Contrast):**
261
+ - surfaces that showed only a fill or a shadow get a system-colour edge;
262
+ - on states use `Highlight`: pressed Toggle, Switch, checked Checkbox, the Slider and Progress
263
+ fills, the Tabs bar, and highlighted Menu and Select items;
264
+ - separators and the tab list's base line stay visible.
265
+ - **Right-to-left:** tests cover arrow keys in Tabs, ToggleGroup, RadioGroup, Slider and the Menu
266
+ submenu, plus the Drawer edge, the Menu bar side and the Input adornment order.
267
+ - **Reduced motion:** a test checks that durations collapse and that Spinner and indeterminate
268
+ Progress fade instead.
269
+ - **Slider:** a slider named by `aria-label` (no visible label) now passes the name on to its
270
+ thumbs, which are what carry the slider role. A range adds Minimum and Maximum.
271
+ - **Sign-in exit screen:** built from primitives alone. Its keyboard order, axe, and its light,
272
+ dark, right-to-left and forced-colours screenshots are all tested.
273
+
163
274
  ### Platform
164
275
 
276
+ - **Source check:**
277
+ - it bans physical CSS properties and system colours outside forced-colours blocks;
278
+ - it ignores comments when looking for client-only code.
279
+
165
280
  - Each family's stylesheet (`css/<family>.css`) now imports the base layer and the stylesheets of
166
281
  the families it renders, so it works on its own.
167
282
  - Screenshots wait for transitions to finish, and overlays are captured across the whole viewport.
package/README.md CHANGED
@@ -127,6 +127,105 @@ import { Checkbox, CheckboxGroup, Fieldset, Radio, RadioGroup, TextField } from
127
127
  </Fieldset>
128
128
  ```
129
129
 
130
+ ## Locale, direction and strings
131
+
132
+ Wrap the app in `QeetrixProvider` to set the locale (used for formatting), the reading direction and
133
+ the library's own strings ("Close", "(optional)", "Loading"…). Locale packs are separate imports, so
134
+ a language you don't use never ships:
135
+
136
+ ```tsx
137
+ import { QeetrixProvider } from "@qeetrix/react";
138
+ import { hi } from "@qeetrix/react/locales/hi";
139
+
140
+ <QeetrixProvider locale="hi-IN" messages={hi}>{app}</QeetrixProvider>
141
+ ```
142
+
143
+ - **Override one string:** pass a `messages` prop to the component, e.g.
144
+ `<Field messages={{ optional: "(if any)" }}>`.
145
+ - **Right-to-left:** use `direction="rtl"`, and set `dir="rtl"` on `<html>` too.
146
+ - **Server-safe components** (Link, Spinner, SkipLink) can't read the provider. Pass their strings
147
+ per instance.
148
+
149
+ Formatting helpers are plain functions and work on the server too:
150
+
151
+ ```ts
152
+ import { formatCurrency, formatNumber, formatRelativeTime } from "@qeetrix/react";
153
+
154
+ formatCurrency(124500, "INR"); // "₹1,24,500.00"
155
+ formatNumber(1234567); // "12,34,567"
156
+ formatRelativeTime(updatedAt, "en-IN", now); // "2 minutes ago"
157
+ ```
158
+
159
+ Every primitive also works in Windows High Contrast (forced colours) and under reduced motion. Tests
160
+ emulate both.
161
+
162
+ ## Experimental: the console frame
163
+
164
+ Components and patterns in their first product ship from `@qeetrix/react/experimental`. They may
165
+ change in a patch release until they're Ready.
166
+
167
+ ```tsx
168
+ import {
169
+ AppShell, PageContent, PageHeader, Sidebar, SidebarGroup, SidebarItem, Stat, StatGroup,
170
+ TopBar, TopBarSearch, TopBarSwitcher,
171
+ } from "@qeetrix/react/experimental";
172
+ import { Menu, MenuContent, MenuItem, MenuTrigger } from "@qeetrix/react";
173
+
174
+ <AppShell
175
+ topBar={
176
+ <TopBar
177
+ logo={<QeetLogo variant="dark" height={20} />}
178
+ product="Qeet ID"
179
+ context={
180
+ <>
181
+ <Menu>
182
+ <MenuTrigger render={<TopBarSwitcher mark="AC" label="Acme Pvt Ltd" />} />
183
+ <MenuContent>{organisations}</MenuContent>
184
+ </Menu>
185
+ <TopBarSwitcher status="success" label="Production" />
186
+ </>
187
+ }
188
+ search={<TopBarSearch placeholder="Search or jump to…" shortcut={["⌘", "K"]} onClick={openPalette} />}
189
+ account={accountMenu}
190
+ />
191
+ }
192
+ sidebar={
193
+ <Sidebar label="Qeet ID">
194
+ <SidebarGroup label="Directory">
195
+ <SidebarItem label="Users" icon={UsersIcon} current render={<Link to="/users" />} />
196
+ </SidebarGroup>
197
+ </Sidebar>
198
+ }
199
+ drawers={[{ id: "help", label: "Help", icon: CircleQuestionMarkIcon, content: help }]}
200
+ >
201
+ <PageHeader
202
+ title="Users"
203
+ actions={<Button icon={PlusIcon}>Invite users</Button>}
204
+ summary={
205
+ <StatGroup>
206
+ <Stat label="Total users" value={1284} note="+38 this week" tone="positive" trend={weekly} />
207
+ </StatGroup>
208
+ }
209
+ />
210
+ <PageContent>{table}</PageContent>
211
+ </AppShell>
212
+ ```
213
+
214
+ The bar is black in light and dark, topped by a 2px Ember line; the navigation collapses to a 48px
215
+ rail that expands while hovered. Also there: `Breadcrumb`, `EmptyState` and `DataState` (loading,
216
+ error, empty, content), and the secure-entry parts:
217
+
218
+ ```tsx
219
+ <Alert tone="warning" title="Copy this client secret now">It can't be shown again.</Alert>
220
+ <Field label="Client secret">
221
+ <PasswordInput readOnly autoComplete="off" defaultValue={secret}
222
+ endAdornment={<CopyButton value={secret} label="Copy client secret" size="sm" />} />
223
+ </Field>
224
+ <Field label="Verification code"><OTPInput size="lg" onValueComplete={verify} /></Field>
225
+ ```
226
+ Colour-scheme switching lives in `QeetrixProvider`
227
+ (`defaultColorScheme="system"`, `useColorScheme()`).
228
+
130
229
  ## Overlays
131
230
 
132
231
  Overlays share one shape: a root that holds the state, a trigger that renders your control, and a
@@ -2,11 +2,15 @@ import { ControlSize, QeetrixIcon } from "../internal/control.js";
2
2
  import { ComponentProps, ReactElement, ReactNode } from "react";
3
3
  import { Button } from "@base-ui/react/button";
4
4
  export type ButtonVariant = "solid" | "soft" | "ghost";
5
- export type ButtonTone = "brand" | "danger";
5
+ export type ButtonTone = "brand" | "danger" | "neutral";
6
6
  interface ButtonBaseProps extends Omit<ComponentProps<"button">, "children"> {
7
7
  /** Emphasis: `solid` (primary, Ember), `soft` (secondary, graphite), `ghost` (tertiary). */
8
8
  variant?: ButtonVariant;
9
- /** `danger` for the confirming action of a destructive flow. */
9
+ /**
10
+ * `danger` for the confirming action of a destructive flow. `neutral` for ghost utility actions
11
+ * in toolbars and headers, whose icons stay neutral (design rules §16); a solid or soft neutral
12
+ * button looks like the soft one.
13
+ */
10
14
  tone?: ButtonTone;
11
15
  /** Height from the density tokens. Default `md`. */
12
16
  size?: ControlSize;
@@ -43,7 +43,11 @@ function Button$1({ variant = "solid", tone = "brand", size = "md", icon, loadin
43
43
  className: "qx-button-label",
44
44
  "data-slot": "button-label",
45
45
  children
46
- }), content(icon, size, loading)]
46
+ }), (icon || loading) && /* @__PURE__ */ jsx("span", {
47
+ className: "qx-button-zone",
48
+ "data-slot": "button-zone",
49
+ children: content(icon, size, loading)
50
+ })]
47
51
  });
48
52
  }
49
53
  /**
@@ -41,4 +41,10 @@
41
41
  block-size: 100%;
42
42
  inline-size: 100%;
43
43
  }
44
+
45
+ @media (forced-colors: active) {
46
+ .qx-avatar {
47
+ border: var(--qx-border-width-default) solid CanvasText;
48
+ }
49
+ }
44
50
  }
@@ -50,4 +50,10 @@
50
50
  min-inline-size: 0;
51
51
  overflow: hidden;
52
52
  }
53
+
54
+ @media (forced-colors: active) {
55
+ .qx-badge {
56
+ border: var(--qx-border-width-default) solid CanvasText;
57
+ }
58
+ }
53
59
  }
@@ -18,8 +18,8 @@
18
18
  border: 0;
19
19
  justify-content: space-between;
20
20
  align-items: center;
21
- gap: 24px;
22
- padding-inline: 14px 12px;
21
+ gap: 12px;
22
+ padding-inline: 14px 0;
23
23
  text-decoration: none;
24
24
  display: inline-flex;
25
25
  position: relative;
@@ -33,8 +33,8 @@
33
33
  block-size: var(--qx-size-control-sm);
34
34
  font-size: var(--qx-typography-label-sm-font-size);
35
35
  line-height: var(--qx-typography-label-sm-line-height);
36
- gap: 16px;
37
- padding-inline: 10px 8px;
36
+ gap: 8px;
37
+ padding-inline: 10px 0;
38
38
  }
39
39
 
40
40
  .qx-button[data-size="sm"]:not([data-icon]):not([data-loading]) {
@@ -43,8 +43,8 @@
43
43
 
44
44
  .qx-button[data-size="lg"] {
45
45
  block-size: var(--qx-size-control-lg);
46
- gap: 32px;
47
- padding-inline: 16px 14px;
46
+ gap: 14px;
47
+ padding-inline: 16px 0;
48
48
  }
49
49
 
50
50
  .qx-button[data-size="lg"]:not([data-icon]):not([data-loading]) {
@@ -115,6 +115,24 @@
115
115
  background: var(--qx-color-bg-danger-subtle);
116
116
  }
117
117
 
118
+ .qx-button[data-tone="neutral"][data-variant="ghost"] {
119
+ color: var(--qx-color-text-subtle);
120
+ }
121
+
122
+ .qx-button[data-tone="neutral"][data-variant="ghost"]:hover:not([data-disabled]) {
123
+ background: var(--qx-color-bg-neutral-subtle);
124
+ color: var(--qx-color-text-default);
125
+ }
126
+
127
+ .qx-button[data-tone="neutral"][data-variant="ghost"]:active:not([data-disabled]) {
128
+ background: var(--qx-color-bg-neutral-subtle-pressed);
129
+ }
130
+
131
+ .qx-button[data-tone="neutral"]:not([data-variant="ghost"]) {
132
+ background: var(--qx-color-bg-neutral-bold);
133
+ color: var(--qx-color-text-inverse);
134
+ }
135
+
118
136
  .qx-button[data-disabled]:not([data-loading]) {
119
137
  background: var(--qx-color-bg-disabled);
120
138
  color: var(--qx-color-text-disabled);
@@ -146,6 +164,43 @@
146
164
  flex: none;
147
165
  }
148
166
 
167
+ .qx-button-zone {
168
+ flex: none;
169
+ align-self: stretch;
170
+ place-items: center;
171
+ padding-inline: 10px;
172
+ display: grid;
173
+ position: relative;
174
+ }
175
+
176
+ .qx-button-zone:before {
177
+ content: "";
178
+ inline-size: var(--qx-border-width-default);
179
+ block-size: var(--qx-size-icon-sm);
180
+ opacity: .55;
181
+ background: currentColor;
182
+ position: absolute;
183
+ inset-block-start: 50%;
184
+ inset-inline-start: 0;
185
+ translate: 0 -50%;
186
+ }
187
+
188
+ .qx-button[data-size="sm"] > .qx-button-zone {
189
+ padding-inline: 8px;
190
+ }
191
+
192
+ .qx-button[data-size="sm"] > .qx-button-zone:before {
193
+ block-size: var(--qx-size-icon-xs);
194
+ }
195
+
196
+ .qx-button[data-size="lg"] > .qx-button-zone {
197
+ padding-inline: 12px;
198
+ }
199
+
200
+ .qx-button[data-size="lg"] > .qx-button-zone:before {
201
+ block-size: var(--qx-size-icon-md);
202
+ }
203
+
149
204
  .qx-icon-button {
150
205
  inline-size: var(--qx-size-control-md);
151
206
  justify-content: center;
@@ -165,4 +220,24 @@
165
220
  .qx-icon-button:not([data-icon]):not([data-loading]) {
166
221
  padding-inline: 0;
167
222
  }
223
+
224
+ @media (forced-colors: active) {
225
+ .qx-button {
226
+ border: var(--qx-border-width-default) solid ButtonText;
227
+ }
228
+
229
+ .qx-button[data-disabled]:not([data-loading]) {
230
+ color: graytext;
231
+ border-color: graytext;
232
+ }
233
+
234
+ .qx-button-zone:before {
235
+ opacity: 1;
236
+ background: buttontext;
237
+ }
238
+
239
+ .qx-button[data-disabled]:not([data-loading]) .qx-button-zone:before {
240
+ background: graytext;
241
+ }
242
+ }
168
243
  }
@@ -55,4 +55,13 @@
55
55
  .qx-checkbox-mark {
56
56
  display: flex;
57
57
  }
58
+
59
+ @media (forced-colors: active) {
60
+ .qx-checkbox:is([data-checked], [data-indeterminate]):not([data-disabled]) {
61
+ forced-color-adjust: none;
62
+ color: highlighttext;
63
+ background: highlight;
64
+ border-color: highlight;
65
+ }
66
+ }
58
67
  }
@@ -118,4 +118,11 @@
118
118
  inset-block-start: 12px;
119
119
  inset-inline-end: 12px;
120
120
  }
121
+
122
+ @media (forced-colors: active) {
123
+ .qx-dialog {
124
+ border: var(--qx-border-width-default) solid CanvasText;
125
+ border-block-start-width: var(--qx-border-width-accent);
126
+ }
127
+ }
121
128
  }
@@ -120,4 +120,10 @@
120
120
  inset-block-start: 20px;
121
121
  inset-inline-end: 16px;
122
122
  }
123
+
124
+ @media (forced-colors: active) {
125
+ .qx-drawer {
126
+ border-inline: var(--qx-border-width-default) solid CanvasText;
127
+ }
128
+ }
123
129
  }
@@ -0,0 +1,89 @@
1
+ @import "../base.css";
2
+ @import "../button.css";
3
+ @import "../visually-hidden.css";
4
+
5
+ @layer qx.components {
6
+ .qx-alert {
7
+ align-items: flex-start;
8
+ gap: var(--qx-space-gap-md);
9
+ padding: var(--qx-space-inset-md) var(--qx-space-inset-sm) var(--qx-space-inset-md)
10
+ var(--qx-space-inset-md);
11
+ border-inline-start: var(--qx-border-width-accent) solid var(--qx-color-icon-info);
12
+ background: var(--qx-color-bg-info-subtle);
13
+ color: var(--qx-color-text-default);
14
+ font: var(--qx-typography-body-md);
15
+ font-variation-settings: "GEOM" var(--qx-font-geometry-body);
16
+ display: flex;
17
+ }
18
+
19
+ .qx-alert[data-tone="success"] {
20
+ border-inline-start-color: var(--qx-color-icon-success);
21
+ background: var(--qx-color-bg-success-subtle);
22
+ }
23
+
24
+ .qx-alert[data-tone="warning"] {
25
+ border-inline-start-color: var(--qx-color-icon-warning);
26
+ background: var(--qx-color-bg-warning-subtle);
27
+ }
28
+
29
+ .qx-alert[data-tone="danger"] {
30
+ border-inline-start-color: var(--qx-color-icon-danger);
31
+ background: var(--qx-color-bg-danger-subtle);
32
+ }
33
+
34
+ .qx-alert-icon {
35
+ color: var(--qx-color-icon-info);
36
+ flex: none;
37
+ margin-block-start: 2px;
38
+ }
39
+
40
+ [data-tone="success"] > .qx-alert-icon {
41
+ color: var(--qx-color-icon-success);
42
+ }
43
+
44
+ [data-tone="warning"] > .qx-alert-icon {
45
+ color: var(--qx-color-icon-warning);
46
+ }
47
+
48
+ [data-tone="danger"] > .qx-alert-icon {
49
+ color: var(--qx-color-icon-danger);
50
+ }
51
+
52
+ .qx-alert-body {
53
+ min-inline-size: 0;
54
+ flex-direction: column;
55
+ flex: 1;
56
+ gap: 2px;
57
+ padding-inline-end: var(--qx-space-gap-xs);
58
+ display: flex;
59
+ }
60
+
61
+ .qx-alert-title {
62
+ font: var(--qx-typography-label-md);
63
+ font-weight: var(--qx-typography-title-sm-font-weight);
64
+ font-variation-settings: "GEOM" var(--qx-font-geometry-body);
65
+ }
66
+
67
+ .qx-alert-description {
68
+ color: var(--qx-color-text-subtle);
69
+ }
70
+
71
+ .qx-alert-actions {
72
+ gap: var(--qx-space-gap-sm);
73
+ flex-wrap: wrap;
74
+ margin-block-start: var(--qx-space-gap-sm);
75
+ display: flex;
76
+ }
77
+
78
+ .qx-alert-dismiss {
79
+ flex: none;
80
+ margin-block: -2px;
81
+ }
82
+
83
+ @media (forced-colors: active) {
84
+ .qx-alert {
85
+ border: var(--qx-border-width-default) solid CanvasText;
86
+ border-inline-start: var(--qx-border-width-accent) solid CanvasText;
87
+ }
88
+ }
89
+ }