@irozon/design-system-react 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,63 +1,42 @@
1
1
  # @irozon/design-system-react
2
2
 
3
- Canonical React components for the Irozon Design System — thin,
4
- accessible wrappers over [`@irozon/design-system`](https://www.npmjs.com/package/@irozon/design-system)'s
5
- framework-neutral `iz-*` CSS.
3
+ React components for the Irozon Design System, built on the same
4
+ framework-neutral CSS as `@irozon/design-system`.
6
5
 
7
- ```text
8
- @irozon/design-tokens
9
- ↓
10
- @irozon/design-system framework-neutral CSS primitives
11
- ↓
12
- @irozon/design-system-react canonical React components ← you are here
13
- ↓
14
- Irozon React applications
15
- ```
16
-
17
- ## Why this exists
6
+ ## Install
18
7
 
19
- `@irozon/design-system` ships CSS, not React — every product that needed
20
- a `<Button>` was hand-rolling its own thin wrapper around `.iz-button`
21
- with its own prop names (`variant="danger"`, `size="sm"`, …). This
22
- package is the single canonical React API, so that stops happening: one
23
- `<Button variant="destructive" size="small">`, not N slightly-different
24
- re-implementations of the same markup.
8
+ ```bash
9
+ npm install @irozon/design-system-react @irozon/design-system
10
+ ```
25
11
 
26
- **This package owns presentation and accessibility wiring only.** It has
27
- no visual styling of its own (zero CSS shipped — every class it renders
28
- comes from `@irozon/design-system`), no business/domain logic, and no
29
- app-specific state (a toast's auto-dismiss *timing* is here because every
30
- known consumer wants the same default; the toast *list* — what's
31
- currently showing — is the app's own state. See each component's own
32
- doc comment for where that line is drawn).
12
+ ## Quick start
33
13
 
34
- ## Installation
14
+ ```tsx
15
+ import "@irozon/design-system/irozon.css";
16
+ import { Button } from "@irozon/design-system-react";
35
17
 
36
- ```bash
37
- npm install @irozon/design-system-react @irozon/design-system
18
+ export function SaveButton() {
19
+ return <Button variant="primary">Save</Button>;
20
+ }
38
21
  ```
39
22
 
40
- `react` and `react-dom` (>=18) are peer dependencies — this package
41
- never bundles its own copy of React.
23
+ Import the CSS once in your application entry point. The React package ships
24
+ components, while `@irozon/design-system` remains the source of their visual
25
+ styles.
42
26
 
43
- You still need the CSS import from `@irozon/design-system` once per app
44
- (see that package's own README) — this package only supplies the
45
- components that render the classes it defines.
27
+ ## What it provides
46
28
 
47
- ## Usage
29
+ - Typed React components for the Irozon Design System's shared UI.
30
+ - Accessible markup and state wiring for the underlying `iz-*` CSS classes.
31
+ - Native element props alongside the documented design-system props.
32
+ - No business logic or application-specific state.
48
33
 
49
- ```tsx
50
- import { Button, FormField, Input } from "@irozon/design-system-react";
34
+ For example, fields compose naturally from the exported components:
51
35
 
52
- function SaveButton() {
53
- return (
54
- <Button variant="primary" onClick={handleSave}>
55
- Save
56
- </Button>
57
- );
58
- }
36
+ ```tsx
37
+ import { FormField, Input } from "@irozon/design-system-react";
59
38
 
60
- function EmailField() {
39
+ export function EmailField() {
61
40
  return (
62
41
  <FormField label="Email" hint="We'll never share it">
63
42
  <Input type="email" name="email" />
@@ -66,6 +45,25 @@ function EmailField() {
66
45
  }
67
46
  ```
68
47
 
48
+ ## Full documentation
49
+
50
+ Visit the [Irozon Design System documentation](https://branding.irozon.se/)
51
+ for React usage, component behavior, foundations, tokens, examples, and
52
+ accessibility guidance.
53
+
54
+ ## Related Irozon packages
55
+
56
+ ```text
57
+ @irozon/design-tokens Design foundations and CSS variables
58
+ ↓
59
+ @irozon/design-system Framework-neutral component CSS
60
+ ↓
61
+ @irozon/design-system-react React components using the same system ← you are here
62
+ ```
63
+
64
+ `@irozon/design-system-react` depends on `@irozon/design-system`, but install
65
+ both packages because your app imports the CSS package directly.
66
+
69
67
  ## Components
70
68
 
71
69
  | Component | Wraps |
@@ -80,12 +78,47 @@ function EmailField() {
80
78
  | `Toast`, `ToastRegion` | `.iz-toast`, `.iz-toast-region` |
81
79
  | `ThemeToggle` | `.iz-theme-toggle` |
82
80
  | `Nav`, `NavGroup`, `NavItem` | `.iz-nav`, `.iz-nav-group`, `.iz-nav-item` |
81
+ | `Tabs`, `TabPanel` | `.iz-tabs`, `.iz-tab`, `.iz-tab-panel` |
82
+
83
+ `Tabs`/`TabPanel` implement the full WAI-ARIA Tabs pattern (automatic
84
+ activation): a roving tabindex so only the selected tab sits in the page's Tab
85
+ order, ArrowLeft/ArrowRight to move between tabs, Home/End to jump to the
86
+ first/last, and a plain click. Pair them via a shared `idPrefix`:
87
+
88
+ ```tsx
89
+ import { useState } from "react";
90
+ import { Tabs, TabPanel } from "@irozon/design-system-react";
91
+
92
+ function Example() {
93
+ const [tab, setTab] = useState("overview");
94
+ return (
95
+ <>
96
+ <Tabs
97
+ idPrefix="example"
98
+ aria-label="Example sections"
99
+ value={tab}
100
+ onValueChange={setTab}
101
+ tabs={[
102
+ { value: "overview", label: "Overview" },
103
+ { value: "details", label: "Details" },
104
+ ]}
105
+ />
106
+ <TabPanel idPrefix="example" value="overview" activeValue={tab}>
107
+ Overview content
108
+ </TabPanel>
109
+ <TabPanel idPrefix="example" value="details" activeValue={tab}>
110
+ Details content
111
+ </TabPanel>
112
+ </>
113
+ );
114
+ }
115
+ ```
116
+
117
+ The [component documentation](https://branding.irozon.se/components/overview.html)
118
+ includes live examples and React usage. TypeScript declarations in the
119
+ package provide the complete prop types.
83
120
 
84
- Full prop reference: each component's own `.tsx` doc comment, and the
85
- "React usage" section on the matching page at
86
- [branding.irozon.se/components](https://branding.irozon.se/components/overview.html) —
87
- that site remains the canonical visual/behavioral reference; this
88
- package is the implementation of the same contract.
121
+ ## Advanced usage
89
122
 
90
123
  ### Routed navigation
91
124
 
@@ -102,14 +135,14 @@ import { NavItem } from "@irozon/design-system-react";
102
135
  </NavItem>;
103
136
  ```
104
137
 
105
- ## Canonical API, not per-app aliases
138
+ ### Canonical API
106
139
 
107
140
  Prop names and values match the CSS package's own vocabulary exactly —
108
141
  `variant="destructive"`, not `variant="danger"`; `size="small"`, not
109
142
  `size="sm"`. Product apps migrating from a local wrapper should adopt
110
143
  these names at the call sites, not keep a permanent translation layer.
111
144
 
112
- ## What this package deliberately does not do
145
+ ### Package boundaries
113
146
 
114
147
  - No CSS of its own — if a component looks wrong, the fix belongs in
115
148
  `@irozon/design-system`, not here.
@@ -117,3 +150,15 @@ these names at the call sites, not keep a permanent translation layer.
117
150
  persistence, form validation — all owned by the consuming app.
118
151
  - No business/domain logic — nothing here knows what an "invoice" or a
119
152
  "proposal" is.
153
+
154
+ React and React DOM `>=18.0.0` are peer dependencies and are not bundled.
155
+
156
+ ## Versioning and compatibility
157
+
158
+ This package is currently in `0.x` and uses the component vocabulary from
159
+ `@irozon/design-system`. Minor releases may introduce API changes before
160
+ `1.0.0`; check the release notes when upgrading.
161
+
162
+ ## License
163
+
164
+ MIT © Irozon AB
package/dist/Tabs.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ import type { HTMLAttributes, ReactNode } from "react";
2
+ export interface TabItem {
3
+ value: string;
4
+ label: ReactNode;
5
+ }
6
+ export interface TabsProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
7
+ tabs: TabItem[];
8
+ value: string;
9
+ onValueChange: (value: string) => void;
10
+ /** Shared with `TabPanel` to derive matching tab/panel ids — see `tabId`/`tabPanelId`. */
11
+ idPrefix: string;
12
+ variant?: "stretched";
13
+ /** Required — a tablist needs an accessible name distinguishing it from any other on the page. */
14
+ "aria-label": string;
15
+ }
16
+ export declare function tabId(idPrefix: string, value: string): string;
17
+ export declare function tabPanelId(idPrefix: string, value: string): string;
18
+ /**
19
+ * Canonical `.iz-tabs` / `.iz-tab` — the WAI-ARIA Tabs pattern with automatic
20
+ * activation: a roving tabindex (only the selected tab is in the normal Tab order),
21
+ * ArrowLeft/ArrowRight move focus and selection together between tabs, Home/End jump
22
+ * to the first/last tab. Mouse click selects directly. Compose with `TabPanel`,
23
+ * matched via a shared `idPrefix` — see `tabs.css`'s own doc comment for the markup
24
+ * shape this renders.
25
+ */
26
+ export declare function Tabs({ tabs, value, onValueChange, idPrefix, variant, className, ...rest }: TabsProps): import("react").JSX.Element;
27
+ export interface TabPanelProps extends HTMLAttributes<HTMLDivElement> {
28
+ idPrefix: string;
29
+ value: string;
30
+ activeValue: string;
31
+ children: ReactNode;
32
+ }
33
+ /**
34
+ * Canonical `.iz-tab-panel` — rendered only while its `value` matches the currently
35
+ * active tab (unmounted otherwise, not just hidden). `id`/`aria-labelledby` are
36
+ * derived the same way `Tabs` derives its own tab ids, so pairing a panel with its
37
+ * tab only requires passing the same `idPrefix` and `value` to both.
38
+ */
39
+ export declare function TabPanel({ idPrefix, value, activeValue, className, children, ...rest }: TabPanelProps): import("react").JSX.Element | null;
package/dist/Tabs.js ADDED
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useRef } from "react";
3
+ import { cx } from "./internal/cx.js";
4
+ export function tabId(idPrefix, value) {
5
+ return `${idPrefix}-tab-${value}`;
6
+ }
7
+ export function tabPanelId(idPrefix, value) {
8
+ return `${idPrefix}-panel-${value}`;
9
+ }
10
+ /**
11
+ * Canonical `.iz-tabs` / `.iz-tab` — the WAI-ARIA Tabs pattern with automatic
12
+ * activation: a roving tabindex (only the selected tab is in the normal Tab order),
13
+ * ArrowLeft/ArrowRight move focus and selection together between tabs, Home/End jump
14
+ * to the first/last tab. Mouse click selects directly. Compose with `TabPanel`,
15
+ * matched via a shared `idPrefix` — see `tabs.css`'s own doc comment for the markup
16
+ * shape this renders.
17
+ */
18
+ export function Tabs({ tabs, value, onValueChange, idPrefix, variant, className, ...rest }) {
19
+ const buttonRefs = useRef(new Map());
20
+ function focusAndSelect(index) {
21
+ const next = tabs[(index + tabs.length) % tabs.length];
22
+ if (!next)
23
+ return;
24
+ onValueChange(next.value);
25
+ buttonRefs.current.get(next.value)?.focus();
26
+ }
27
+ function handleKeyDown(event, index) {
28
+ switch (event.key) {
29
+ case "ArrowRight":
30
+ event.preventDefault();
31
+ focusAndSelect(index + 1);
32
+ break;
33
+ case "ArrowLeft":
34
+ event.preventDefault();
35
+ focusAndSelect(index - 1);
36
+ break;
37
+ case "Home":
38
+ event.preventDefault();
39
+ focusAndSelect(0);
40
+ break;
41
+ case "End":
42
+ event.preventDefault();
43
+ focusAndSelect(tabs.length - 1);
44
+ break;
45
+ default:
46
+ break;
47
+ }
48
+ }
49
+ return (_jsx("div", { className: cx("iz-tabs", className), role: "tablist", "data-variant": variant, ...rest, children: tabs.map((tab, index) => {
50
+ const selected = tab.value === value;
51
+ return (_jsx("button", { ref: (el) => {
52
+ if (el)
53
+ buttonRefs.current.set(tab.value, el);
54
+ else
55
+ buttonRefs.current.delete(tab.value);
56
+ }, type: "button", role: "tab", id: tabId(idPrefix, tab.value), "aria-selected": selected, "aria-controls": tabPanelId(idPrefix, tab.value), tabIndex: selected ? 0 : -1, className: "iz-tab", onClick: () => onValueChange(tab.value), onKeyDown: (event) => handleKeyDown(event, index), children: tab.label }, tab.value));
57
+ }) }));
58
+ }
59
+ /**
60
+ * Canonical `.iz-tab-panel` — rendered only while its `value` matches the currently
61
+ * active tab (unmounted otherwise, not just hidden). `id`/`aria-labelledby` are
62
+ * derived the same way `Tabs` derives its own tab ids, so pairing a panel with its
63
+ * tab only requires passing the same `idPrefix` and `value` to both.
64
+ */
65
+ export function TabPanel({ idPrefix, value, activeValue, className, children, ...rest }) {
66
+ if (value !== activeValue)
67
+ return null;
68
+ return (_jsx("div", { className: cx("iz-tab-panel", className), role: "tabpanel", id: tabPanelId(idPrefix, value), "aria-labelledby": tabId(idPrefix, value), tabIndex: 0, ...rest, children: children }));
69
+ }
package/dist/index.d.ts CHANGED
@@ -18,3 +18,5 @@ export { ThemeToggle } from "./ThemeToggle.js";
18
18
  export type { ThemeToggleProps } from "./ThemeToggle.js";
19
19
  export { Nav, NavGroup, NavItem } from "./Nav.js";
20
20
  export type { NavProps, NavGroupProps, NavItemProps } from "./Nav.js";
21
+ export { Tabs, TabPanel, tabId, tabPanelId } from "./Tabs.js";
22
+ export type { TabsProps, TabPanelProps, TabItem } from "./Tabs.js";
package/dist/index.js CHANGED
@@ -8,3 +8,4 @@ export { ConfirmDialog } from "./ConfirmDialog.js";
8
8
  export { Toast, ToastRegion } from "./Toast.js";
9
9
  export { ThemeToggle } from "./ThemeToggle.js";
10
10
  export { Nav, NavGroup, NavItem } from "./Nav.js";
11
+ export { Tabs, TabPanel, tabId, tabPanelId } from "./Tabs.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@irozon/design-system-react",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Canonical React components for the Irozon Design System — thin, accessible wrappers over @irozon/design-system's framework-neutral iz-* CSS. No independent visual styling of its own; the CSS package stays the single source of truth for how these look.",
5
5
  "license": "MIT",
6
6
  "keywords": [