design26 0.1.0 → 0.1.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.
- package/COMPONENTS.md +11 -11
- package/README.md +27 -0
- package/dist/ui/primitives.d.ts +7 -4
- package/dist/ui/primitives.js +16 -2
- package/package.json +6 -6
package/COMPONENTS.md
CHANGED
|
@@ -18,16 +18,16 @@ A gently drifting 38px technical mesh, refined in the reference during this extr
|
|
|
18
18
|
|
|
19
19
|
## PageFrame
|
|
20
20
|
|
|
21
|
-
The
|
|
21
|
+
The outer frame and opt-in theme boundary.
|
|
22
22
|
|
|
23
23
|
- **Entry:** design26
|
|
24
|
-
- **Props:**
|
|
25
|
-
- **Variants:**
|
|
26
|
-
- **Accessibility:** Use a main landmark inside the frame and a skip link before navigation.
|
|
27
|
-
- **Recommended use:** One outer frame per application. Use d26-theme alone for embedded UI.
|
|
24
|
+
- **Props:** primaryColor?: CSSProperties["color"]; native div props, children, className, style, ref
|
|
25
|
+
- **Variants:** Reference blue by default; optional custom primary color with derived hover and soft states.
|
|
26
|
+
- **Accessibility:** Use a main landmark inside the frame and a skip link before navigation. Choose a primary color with sufficient contrast against white button text and the paper surface.
|
|
27
|
+
- **Recommended use:** One outer frame per application. primaryColor sets the frame, accents and related interaction colors through semantic tokens; nested theme boundaries remain independent. Native style overrides take precedence. Use d26-theme alone for embedded UI.
|
|
28
28
|
|
|
29
29
|
```tsx
|
|
30
|
-
<PageFrame><Navigation brand="Project" /><Panel>Content</Panel></PageFrame>
|
|
30
|
+
<PageFrame primaryColor="#306b72"><Navigation brand="Project" brandSize={24} /><Panel>Content</Panel></PageFrame>
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
## Panel
|
|
@@ -119,13 +119,13 @@ Compact playback and circular paging actions.
|
|
|
119
119
|
White-on-blue wordmark, descriptor and links.
|
|
120
120
|
|
|
121
121
|
- **Entry:** design26
|
|
122
|
-
- **Props:** brand: ReactNode; description?: ReactNode; actions?: ReactNode; native header props
|
|
123
|
-
- **Variants:** Descriptor hides at 760px
|
|
124
|
-
- **Accessibility:** Brand links should identify the home destination. Actions are wrapped in a Main navigation landmark.
|
|
125
|
-
- **Recommended use:** Keep labels short. The header is part of the blue frame.
|
|
122
|
+
- **Props:** brand: ReactNode; brandSize?: CSSProperties["fontSize"] (number in px or CSS string); description?: ReactNode; actions?: ReactNode; native header props
|
|
123
|
+
- **Variants:** Brand defaults to 39px / 32px at mobile; brandSize overrides both. Descriptor hides at 760px.
|
|
124
|
+
- **Accessibility:** Brand links should identify the home destination. Actions are wrapped in a Main navigation landmark. Choose a readable brandSize at every viewport.
|
|
125
|
+
- **Recommended use:** Keep labels short. Set brandSize on Navigation so the brand and its line box resize together; leave the brand child font size inherited. The header is part of the blue frame.
|
|
126
126
|
|
|
127
127
|
```tsx
|
|
128
|
-
<Navigation brand={<a href="/">Project</a>} description="RESEARCH NOTES" actions={<a href="#work">Work</a>} />
|
|
128
|
+
<Navigation brand={<a href="/">Project</a>} brandSize={24} description="RESEARCH NOTES" actions={<a href="#work">Work</a>} />
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
## StepNavigation
|
package/README.md
CHANGED
|
@@ -77,6 +77,19 @@ Override semantic tokens on the same wrapper. CSS properties inherit; defaults l
|
|
|
77
77
|
|
|
78
78
|
Use `design26/tokens.css` alone when building a non-React composition. `design26/styles.css` already imports tokens. One faithful light theme is shipped; a dark theme is not invented. Exported `breakpoints` matches native CSS thresholds: mobile760px, tablet1100px, wide1500px. CSS variables cannot be used in native media-query conditions, so those literal thresholds are intentionally mirrored.
|
|
79
79
|
|
|
80
|
+
For a different primary color, set `primaryColor` on the app's `PageFrame`:
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
<PageFrame primaryColor="#306b72">
|
|
84
|
+
<Navigation brand={<a href="/">Conor Curley</a>} brandSize={24} />
|
|
85
|
+
<Panel><Button>Explore</Button></Panel>
|
|
86
|
+
</PageFrame>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The prop accepts a CSS color, including `var(--my-primary)`. It sets `--color-brand`, `--color-background` and `--color-accent`, derives darker hover and pale accent states with CSS `color-mix()`, and coordinates frame copy, text selection and status halos. Buttons, links, heading emphasis and other components that use the accent token follow automatically. Existing paper surfaces, navy body text, amber focus and scientific series colors keep their roles. Choose a primary color that remains readable on paper and behind white button text.
|
|
90
|
+
|
|
91
|
+
Omitting `primaryColor` preserves every original token value. The override is scoped to this frame; a nested theme wrapper starts its own defaults. Native `style` is merged last for explicit overrides. For embedded regions without PageFrame, override the same semantic tokens on `.d26-theme` or `[data-design26-theme="light"]`.
|
|
92
|
+
|
|
80
93
|
## Components
|
|
81
94
|
|
|
82
95
|
Generic: TechnicalGrid, PageFrame, Navigation, Panel, Eyebrow, SectionHeading, Button, TextLink, IconButton, StepNavigation, Tabs, Slider, Select, Checkbox, Disclosure, Dialog, StatusNote.
|
|
@@ -87,6 +100,20 @@ See [COMPONENTS.md](COMPONENTS.md) for every component contract and [AGENTS.md](
|
|
|
87
100
|
|
|
88
101
|
Every component exports a matching `*Props` type. Native HTML attributes, including React19 refs, are accepted. Tabs also exports TabItem. The repository's live catalogue documents every prop contract, state, usage example and accessibility note. Source declarations are the authoritative complete native-plus-custom API.
|
|
89
102
|
|
|
103
|
+
### Navigation brand size
|
|
104
|
+
|
|
105
|
+
Set `brandSize` on `Navigation` to resize `.d26-brand` and its line box together. Numbers are pixels; CSS strings support units and responsive expressions such as `clamp()`. Leave the brand link's font size inherited so it follows the prop.
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
<Navigation
|
|
109
|
+
brand={<a href="/">Conor Curley</a>}
|
|
110
|
+
brandSize={24}
|
|
111
|
+
actions={<a href="#work">Work</a>}
|
|
112
|
+
/>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
You can also use `brandSize="1.5rem"` or `brandSize="clamp(22px, 2vw, 30px)"`. When omitted, the existing 39px desktop / 32px mobile sizes apply. A supplied value overrides both defaults and affects only this Navigation instance. Keep the name readable at narrow widths and preserve meaningful link text. Header height and action text sizes are independent of `brandSize`.
|
|
116
|
+
|
|
90
117
|
## Interaction contracts
|
|
91
118
|
|
|
92
119
|
- Button defaults to type=button; use TextLink for navigation. IconButton requires aria-label.
|
package/dist/ui/primitives.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type { AnchorHTMLAttributes, ButtonHTMLAttributes, HTMLAttributes, ReactNode } from "react";
|
|
1
|
+
import type { AnchorHTMLAttributes, ButtonHTMLAttributes, CSSProperties, HTMLAttributes, ReactNode } from "react";
|
|
2
2
|
export declare const cx: (...values: Array<string | undefined | false>) => string;
|
|
3
|
-
export type PageFrameProps = HTMLAttributes<HTMLDivElement
|
|
4
|
-
|
|
3
|
+
export type PageFrameProps = HTMLAttributes<HTMLDivElement> & {
|
|
4
|
+
primaryColor?: CSSProperties["color"];
|
|
5
|
+
};
|
|
6
|
+
export declare function PageFrame({ primaryColor, style, className, ...props }: PageFrameProps): import("react").JSX.Element;
|
|
5
7
|
export type PanelProps = HTMLAttributes<HTMLDivElement> & {
|
|
6
8
|
variant?: "sheet" | "control" | "toolbar";
|
|
7
9
|
padding?: "none" | "content";
|
|
@@ -32,10 +34,11 @@ export type IconButtonProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "ari
|
|
|
32
34
|
export declare function IconButton({ variant, type, className, children, ...props }: IconButtonProps): import("react").JSX.Element;
|
|
33
35
|
export type NavigationProps = HTMLAttributes<HTMLElement> & {
|
|
34
36
|
brand: ReactNode;
|
|
37
|
+
brandSize?: CSSProperties["fontSize"];
|
|
35
38
|
description?: ReactNode;
|
|
36
39
|
actions?: ReactNode;
|
|
37
40
|
};
|
|
38
|
-
export declare function Navigation({ brand, description, actions, className, ...props }: NavigationProps): import("react").JSX.Element;
|
|
41
|
+
export declare function Navigation({ brand, brandSize, description, actions, className, ...props }: NavigationProps): import("react").JSX.Element;
|
|
39
42
|
export type StepNavigationProps = Omit<HTMLAttributes<HTMLElement>, "onChange"> & {
|
|
40
43
|
items: {
|
|
41
44
|
id: string;
|
package/dist/ui/primitives.js
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
export const cx = (...values) => values.filter(Boolean).join(" ");
|
|
3
|
-
export function PageFrame({
|
|
3
|
+
export function PageFrame({ primaryColor, style, className, ...props }) {
|
|
4
|
+
const themeStyle = primaryColor === undefined ? style : {
|
|
5
|
+
"--color-brand": primaryColor,
|
|
6
|
+
"--color-background": "var(--color-brand)",
|
|
7
|
+
"--color-accent": "var(--color-brand)",
|
|
8
|
+
"--color-accent-hover": "color-mix(in srgb, var(--color-accent) 84%, black)",
|
|
9
|
+
"--color-accent-soft": "color-mix(in srgb, var(--color-accent) 8%, white)",
|
|
10
|
+
"--color-text-on-frame": "color-mix(in srgb, var(--color-brand) 12%, white)",
|
|
11
|
+
"--color-text-footer": "color-mix(in srgb, var(--color-brand) 22%, white)",
|
|
12
|
+
"--color-selection": "color-mix(in srgb, var(--color-accent) 26%, white)",
|
|
13
|
+
"--color-status-halo": "color-mix(in srgb, var(--color-accent) 10%, white)",
|
|
14
|
+
...style,
|
|
15
|
+
};
|
|
16
|
+
return _jsx("div", { ...props, style: themeStyle, className: cx("d26-theme d26-frame", className) });
|
|
17
|
+
}
|
|
4
18
|
export function Panel({ variant = "sheet", padding = "content", className, ...props }) { return _jsx("div", { ...props, className: cx("d26-panel", `d26-panel--${variant}`, padding === "content" && "d26-panel--padded", className) }); }
|
|
5
19
|
export function Eyebrow({ dot = false, compact = false, className, children, ...props }) { return _jsxs("div", { ...props, className: cx("d26-eyebrow", compact && "d26-eyebrow--compact", className), children: [dot && _jsx("span", { className: "d26-status-dot", "aria-hidden": "true" }), children] }); }
|
|
6
20
|
export function SectionHeading({ as: Tag = "h2", size = "chapter", className, ...props }) { return _jsx(Tag, { ...props, className: cx("d26-heading", `d26-heading--${size}`, className) }); }
|
|
7
21
|
export function Button({ variant = "primary", type = "button", busy, endIcon, className, children, disabled, ...props }) { return _jsxs("button", { ...props, type: type, disabled: disabled || busy, "aria-busy": busy || undefined, className: cx("d26-button", `d26-button--${variant}`, className), children: [children, endIcon && _jsx("span", { className: "d26-button-icon", "aria-hidden": "true", children: endIcon })] }); }
|
|
8
22
|
export function TextLink({ className, ...props }) { return _jsx("a", { ...props, className: cx("d26-text-link", className) }); }
|
|
9
23
|
export function IconButton({ variant = "circle", type = "button", className, children, ...props }) { return _jsx("button", { ...props, type: type, className: cx("d26-icon-button", `d26-icon-button--${variant}`, className), children: _jsx("span", { "aria-hidden": "true", children: children }) }); }
|
|
10
|
-
export function Navigation({ brand, description, actions, className, ...props }) { return _jsxs("header", { ...props, className: cx("d26-navigation", className), children: [_jsx("div", { className: "d26-brand", children: brand }), description && _jsx("div", { className: "d26-navigation-description", children: description }), actions && _jsx("nav", { "aria-label": "Main", className: "d26-navigation-actions", children: actions })] }); }
|
|
24
|
+
export function Navigation({ brand, brandSize, description, actions, className, ...props }) { return _jsxs("header", { ...props, className: cx("d26-navigation", className), children: [_jsx("div", { className: "d26-brand", style: brandSize === undefined ? undefined : { fontSize: brandSize }, children: brand }), description && _jsx("div", { className: "d26-navigation-description", children: description }), actions && _jsx("nav", { "aria-label": "Main", className: "d26-navigation-actions", children: actions })] }); }
|
|
11
25
|
export function StepNavigation({ items, value, onValueChange, className, ...props }) { return _jsx("nav", { ...props, className: cx("d26-steps", className), children: items.map((item, i) => _jsxs("button", { type: "button", disabled: item.disabled, "aria-current": value === item.id ? "step" : undefined, onClick: () => onValueChange(item.id), children: [_jsx("span", { className: "d26-step-number", children: String(i + 1).padStart(2, "0") }), _jsx("span", { children: item.label }), _jsx("span", { className: "d26-step-dot", "aria-hidden": "true" })] }, item.id)) }); }
|
|
12
26
|
export function Disclosure({ title, className, children, ...props }) { return _jsxs("details", { ...props, className: cx("d26-disclosure", className), children: [_jsxs("summary", { children: [title, _jsx("span", { "aria-hidden": "true", children: "\u2304" })] }), _jsx("div", { children: children })] }); }
|
|
13
27
|
export function StatusNote({ tone = "info", className, ...props }) { return _jsx("p", { ...props, className: cx("d26-note", `d26-note--${tone}`, className) }); }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "design26",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A personal visual language for research, engineering and the web, extracted from the UVID demonstration.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
"./styles.css": "./dist/styles.css",
|
|
26
26
|
"./tokens.css": "./dist/tokens.css"
|
|
27
27
|
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc -p tsconfig.build.json && node build.mjs",
|
|
30
|
+
"typecheck": "tsc -p tsconfig.build.json --noEmit"
|
|
31
|
+
},
|
|
28
32
|
"peerDependencies": {
|
|
29
33
|
"react": "^19.0.0",
|
|
30
34
|
"react-dom": "^19.0.0"
|
|
@@ -33,9 +37,5 @@
|
|
|
33
37
|
"@types/react": "^19",
|
|
34
38
|
"@types/react-dom": "^19",
|
|
35
39
|
"typescript": "^5"
|
|
36
|
-
},
|
|
37
|
-
"scripts": {
|
|
38
|
-
"build": "tsc -p tsconfig.build.json && node build.mjs",
|
|
39
|
-
"typecheck": "tsc -p tsconfig.build.json --noEmit"
|
|
40
40
|
}
|
|
41
|
-
}
|
|
41
|
+
}
|