@irozon/design-system-react 0.1.2 → 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 +98 -53
- package/dist/Tabs.d.ts +39 -0
- package/dist/Tabs.js +69 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,63 +1,42 @@
|
|
|
1
1
|
# @irozon/design-system-react
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14
|
+
```tsx
|
|
15
|
+
import "@irozon/design-system/irozon.css";
|
|
16
|
+
import { Button } from "@irozon/design-system-react";
|
|
35
17
|
|
|
36
|
-
|
|
37
|
-
|
|
18
|
+
export function SaveButton() {
|
|
19
|
+
return <Button variant="primary">Save</Button>;
|
|
20
|
+
}
|
|
38
21
|
```
|
|
39
22
|
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
50
|
-
import { Button, FormField, Input } from "@irozon/design-system-react";
|
|
34
|
+
For example, fields compose naturally from the exported components:
|
|
51
35
|
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@irozon/design-system-react",
|
|
3
|
-
"version": "0.
|
|
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": [
|