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 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 blue outer frame and opt-in theme boundary.
21
+ The outer frame and opt-in theme boundary.
22
22
 
23
23
  - **Entry:** design26
24
- - **Props:** Native div props, children, className, ref
25
- - **Variants:** One faithful light theme
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.
@@ -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
- export declare function PageFrame({ className, ...props }: PageFrameProps): import("react").JSX.Element;
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;
@@ -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({ className, ...props }) { return _jsx("div", { ...props, className: cx("d26-theme d26-frame", className) }); }
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.0",
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
+ }