@kennycorrea/ui 0.7.0 → 0.8.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.1 — 2026-10-11
4
+
5
+ ### Fixed
6
+ - **`Badge` text passes WCAG AA in both appearances.** Soft badges wrote their text in step a11
7
+ on an a3 wash, which fails 4.5:1 in light for `success` (jade, 4.27:1), `warning` (amber,
8
+ 4.25:1), blue and several accents a business can pick for `brand`. Every variant is now
9
+ `highContrast` (step 12), which clears 9.8:1 on every Radix scale in light and dark. `Toast`
10
+ already passed (lowest: amber action text at 4.61:1) and is unchanged.
11
+
12
+ ### Added
13
+ - **`test/contrast.test.tsx`**: every `Badge` variant (`brand` against every Radix accent) and
14
+ every `Toast` type, light and dark, computed from Radix's own token files. It fails if
15
+ `highContrast` is dropped. Not axe: jsdom applies no stylesheet, so axe's colour-contrast rule
16
+ can only report "incomplete" there; this checks the same pairs it would check in a browser.
17
+ - **A dev-only warning when a needed colour scale is missing.** The first `Badge` or `Toaster`
18
+ to mount checks for `--{gray,jade,amber,red,blue}-9` and names the missing
19
+ `@radix-ui/themes/tokens/colors/*.css` imports.
20
+
21
+ ### Docs
22
+ - README: the colour scales the atoms need, and their order relative to `base.css`.
23
+
24
+ ## 0.8.0 — 2026-10-09
25
+
26
+ ### Added
27
+ - **Every Radix Themes component the atoms do not wrap**, re-exported unchanged from
28
+ `src/primitives/`: layout (Box, Flex, Grid, Container, Section, Inset), type (Text, Heading,
29
+ Link, Code, Kbd, Em, Strong, Quote, Blockquote), and AlertDialog, AspectRatio, Callout,
30
+ CheckboxCards, CheckboxGroup, DataList, DropdownMenu, HoverCard, IconButton, Radio,
31
+ RadioCards, RadioGroup, ScrollArea, SegmentedControl, TabNav, Table, Tabs, TextField,
32
+ AccessibleIcon, Portal, Reset, Slot, Slottable, ThemePanel, VisuallyHidden. Where an atom
33
+ exists it remains the export under that name.
34
+ - **`ThemeRoot`** — Themes' `<Theme>`, renamed because `Theme` is already this package's type.
35
+ - **All of Radix Icons** (`@radix-ui/react-icons`, 320 `*Icon` components). `Icon` is
36
+ unchanged and still the house glyph set.
37
+
3
38
  ## 0.7.0 — 2026-10-09
4
39
 
5
40
  ### Added
package/README.md CHANGED
@@ -63,6 +63,29 @@ And in the app's own `index.css`:
63
63
  every utility class used *inside* the atoms — `w-full`, `inline-flex` — is missing from the
64
64
  generated CSS. The components come out subtly unstyled and nothing appears in the console.
65
65
 
66
+ ### Colour scales — required
67
+
68
+ The atoms name five Radix scales: **gray**, **jade** (`Badge` success, `Toast` success),
69
+ **amber** (warning), **red** (danger/error) and **blue** (`Toast`/`Alert` info). If the app
70
+ imports Radix Themes piecemeal rather than the whole `@radix-ui/themes/styles.css`, every one of
71
+ them must be imported, **after `base.css`** as Radix documents it, plus the app's own accent:
72
+
73
+ ```css
74
+ @import '@radix-ui/themes/tokens/base.css';
75
+ @import '@radix-ui/themes/tokens/colors/gray.css';
76
+ @import '@radix-ui/themes/tokens/colors/jade.css';
77
+ @import '@radix-ui/themes/tokens/colors/amber.css';
78
+ @import '@radix-ui/themes/tokens/colors/red.css';
79
+ @import '@radix-ui/themes/tokens/colors/blue.css';
80
+ /* …the accent the app offers, if not one of the above */
81
+ @import '@radix-ui/themes/components.css';
82
+ @import '@radix-ui/themes/utilities.css';
83
+ ```
84
+
85
+ A missing scale is not an error to CSS — the variables are just undefined, and badges and toasts
86
+ render colourless. Since 0.8.1 the first `Badge` or `Toaster` to mount checks for them and
87
+ `console.warn`s the missing imports by name, outside production builds.
88
+
66
89
  ## Theming — 0.2.0
67
90
 
68
91
  **A product configures its own accents, default and storage namespace.** Until 0.2.0 all three
@@ -1,7 +1,14 @@
1
1
  import { Badge as ThemedBadge } from '@radix-ui/themes';
2
- import type { ComponentPropsWithoutRef } from 'react';
2
+ import type { ComponentPropsWithoutRef, Ref } from 'react';
3
3
  export type BadgeVariant = 'neutral' | 'brand' | 'success' | 'warning' | 'danger';
4
4
  export interface BadgeProps extends Omit<ComponentPropsWithoutRef<typeof ThemedBadge>, 'color' | 'variant'> {
5
5
  variant?: BadgeVariant;
6
+ ref?: Ref<HTMLSpanElement>;
6
7
  }
7
- export declare function Badge({ variant, ...props }: BadgeProps): import("react").JSX.Element;
8
+ /**
9
+ * **Always `highContrast`**, which writes the text in step 12 instead of a11. Soft badges are
10
+ * 12px text on an a3 wash, and a11 there fails WCAG AA in light for jade (4.27:1), amber
11
+ * (4.25:1), blue and several accents a business can pick for `brand`. Step 12 clears 9.8:1
12
+ * on every scale in both appearances; `test/contrast.test.tsx` holds it there.
13
+ */
14
+ export declare function Badge({ variant, ref, ...props }: BadgeProps): import("react").JSX.Element;
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Badge as ThemedBadge } from '@radix-ui/themes';
3
+ import { warnMissingScales } from '../../utils/scales.js';
3
4
  /**
4
5
  * The five meanings this library names, in Themes' colour vocabulary.
5
6
  *
@@ -18,6 +19,18 @@ const COLORS = {
18
19
  warning: 'amber',
19
20
  danger: 'red',
20
21
  };
21
- export function Badge({ variant = 'neutral', ...props }) {
22
- return _jsx(ThemedBadge, { color: COLORS[variant], variant: "soft", radius: "full", ...props });
22
+ /**
23
+ * **Always `highContrast`**, which writes the text in step 12 instead of a11. Soft badges are
24
+ * 12px text on an a3 wash, and a11 there fails WCAG AA in light for jade (4.27:1), amber
25
+ * (4.25:1), blue and several accents a business can pick for `brand`. Step 12 clears 9.8:1
26
+ * on every scale in both appearances; `test/contrast.test.tsx` holds it there.
27
+ */
28
+ export function Badge({ variant = 'neutral', ref, ...props }) {
29
+ return (_jsx(ThemedBadge, { ref: (el) => {
30
+ warnMissingScales(el);
31
+ if (typeof ref === 'function')
32
+ ref(el);
33
+ else if (ref)
34
+ ref.current = el;
35
+ }, color: COLORS[variant], variant: "soft", radius: "full", highContrast: true, ...props }));
23
36
  }
@@ -3,6 +3,7 @@ import * as RadixToast from '@radix-ui/react-toast';
3
3
  import { useSyncExternalStore } from 'react';
4
4
  import { useUiStrings } from '../../strings.js';
5
5
  import { cn } from '../../utils/cn.js';
6
+ import { warnMissingScales } from '../../utils/scales.js';
6
7
  import { Icon } from '../Icon/Icon.js';
7
8
  /**
8
9
  * A module-level store, so `toast()` can be called from anywhere — an event handler, a
@@ -92,5 +93,5 @@ export function Toaster({ duration = 5000, position = 'bottom-right', label }) {
92
93
  if (item.action?.keepOpen)
93
94
  event.preventDefault();
94
95
  }, className: cn('shrink-0 rounded-md px-2 py-1 text-sm font-medium', 'text-[var(--accent-11)] hover:bg-[var(--accent-a3)]'), children: item.action.label })) : null, _jsx(RadixToast.Close, { "aria-label": strings.dismiss, className: "shrink-0 rounded-md p-1 text-ink-muted hover:bg-[var(--gray-a3)]", children: _jsx(Icon, { name: "close", className: "size-4" }) })] }, item.id));
95
- }), _jsx(RadixToast.Viewport, { className: cn('fixed z-50 m-0 flex w-full max-w-sm list-none flex-col gap-2 p-4 outline-none', POSITIONS[position]) })] }));
96
+ }), _jsx(RadixToast.Viewport, { ref: warnMissingScales, className: cn('fixed z-50 m-0 flex w-full max-w-sm list-none flex-col gap-2 p-4 outline-none', POSITIONS[position]) })] }));
96
97
  }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The whole Radix Icons set, drawn on a 15px grid to sit beside Themes components.
3
+ *
4
+ * `Icon` stays the house glyph set for navigation and status; these cover everything else.
5
+ */
6
+ export * from '@radix-ui/react-icons';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The whole Radix Icons set, drawn on a 15px grid to sit beside Themes components.
3
+ *
4
+ * `Icon` stays the house glyph set for navigation and status; these cover everything else.
5
+ */
6
+ export * from '@radix-ui/react-icons';
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export * from './atoms/index.js';
2
+ export * from './icons/index.js';
3
+ export * from './primitives/index.js';
2
4
  export * from './strings.js';
3
5
  export * from './utils/index.js';
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
1
1
  export * from './atoms/index.js';
2
+ export * from './icons/index.js';
3
+ export * from './primitives/index.js';
2
4
  export * from './strings.js';
3
5
  export * from './utils/index.js';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Every Radix Themes component the atoms do not already wrap, re-exported as is.
3
+ *
4
+ * The atoms stay the house API where one exists — `Button`, `Card`, `Dialog` and the rest
5
+ * carry decisions the raw component does not. These are for everything else, so an app
6
+ * reaches for a `Table` or `Tabs` through this package rather than importing Themes beside it
7
+ * and drifting from the version the atoms were built against.
8
+ */
9
+ export { AccessibleIcon, AlertDialog, AspectRatio, Blockquote, Box, Callout, CheckboxCards, CheckboxGroup, Code, Container, DataList, DropdownMenu, Em, Flex, Grid, Heading, HoverCard, IconButton, Inset, Kbd, Link, Portal, Quote, Radio, RadioCards, RadioGroup, Reset, ScrollArea, Section, SegmentedControl, Slot, Slottable, Strong, Table, TabNav, Tabs, Text, TextField, Theme as ThemeRoot, ThemePanel, VisuallyHidden, } from '@radix-ui/themes';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Every Radix Themes component the atoms do not already wrap, re-exported as is.
3
+ *
4
+ * The atoms stay the house API where one exists — `Button`, `Card`, `Dialog` and the rest
5
+ * carry decisions the raw component does not. These are for everything else, so an app
6
+ * reaches for a `Table` or `Tabs` through this package rather than importing Themes beside it
7
+ * and drifting from the version the atoms were built against.
8
+ */
9
+ export { AccessibleIcon, AlertDialog, AspectRatio, Blockquote, Box, Callout, CheckboxCards, CheckboxGroup, Code, Container, DataList, DropdownMenu, Em, Flex, Grid, Heading, HoverCard, IconButton, Inset, Kbd, Link, Portal, Quote, Radio, RadioCards, RadioGroup, Reset, ScrollArea, Section, SegmentedControl, Slot, Slottable, Strong, Table, TabNav, Tabs, Text, TextField, Theme as ThemeRoot, ThemePanel, VisuallyHidden, } from '@radix-ui/themes';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The Radix colour scales the atoms name. A consumer that imports only gray and its accent
3
+ * gets badges and toasts with no colour at all, and nothing says so — the variables are simply
4
+ * undefined. This is that something, in development only.
5
+ */
6
+ export declare const REQUIRED_SCALES: readonly ["gray", "jade", "amber", "red", "blue"];
7
+ export declare function warnMissingScales(el: Element | null): void;
8
+ /** Test seam. */
9
+ export declare function resetScaleCheck(): void;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The Radix colour scales the atoms name. A consumer that imports only gray and its accent
3
+ * gets badges and toasts with no colour at all, and nothing says so — the variables are simply
4
+ * undefined. This is that something, in development only.
5
+ */
6
+ export const REQUIRED_SCALES = ['gray', 'jade', 'amber', 'red', 'blue'];
7
+ const isDev = globalThis.process?.env?.NODE_ENV !==
8
+ 'production';
9
+ let checked = false;
10
+ export function warnMissingScales(el) {
11
+ if (checked || !el || typeof getComputedStyle === 'undefined')
12
+ return;
13
+ if (!isDev)
14
+ return;
15
+ const style = getComputedStyle(el);
16
+ const missing = REQUIRED_SCALES.filter((s) => !style.getPropertyValue(`--${s}-9`).trim());
17
+ // Everything missing means no stylesheet yet (or jsdom) — not a choice; check again later.
18
+ if (missing.length === REQUIRED_SCALES.length)
19
+ return;
20
+ checked = true;
21
+ if (missing.length === 0)
22
+ return;
23
+ console.warn(`@kennycorrea/ui: missing Radix colour scale(s) ${missing.join(', ')}. Import ` +
24
+ `${missing.map((s) => `@radix-ui/themes/tokens/colors/${s}.css`).join(', ')} after ` +
25
+ '@radix-ui/themes/tokens/base.css, or Badge and Toast render colourless.');
26
+ }
27
+ /** Test seam. */
28
+ export function resetScaleCheck() {
29
+ checked = false;
30
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kennycorrea/ui",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "Upfront's house component layer: atoms over Radix Themes, the token bridge, and the accent logic.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -35,6 +35,7 @@
35
35
  "react-dom": "^19.0.0"
36
36
  },
37
37
  "dependencies": {
38
+ "@radix-ui/react-icons": "^1.3.2",
38
39
  "@radix-ui/react-slider": "^1.5.0",
39
40
  "@radix-ui/react-toast": "^1.2.23",
40
41
  "@radix-ui/react-toolbar": "^1.1.20",
@@ -49,6 +50,7 @@
49
50
  "@radix-ui/themes": "^3.3.0",
50
51
  "@testing-library/react": "^16.3.3",
51
52
  "@testing-library/user-event": "^14.6.7",
53
+ "@types/node": "^24.19.2",
52
54
  "@types/react": "^19.2.18",
53
55
  "@types/react-dom": "^19.2.7",
54
56
  "jsdom": "^30.1.2",