@putdotio/design 5.0.1 → 5.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/DESIGN.md +9 -7
- package/README.md +3 -16
- package/package.json +3 -3
- package/system/README.md +59 -211
- package/system/assets/app-icon-monochrome.svg +9 -0
- package/system/assets/app-icon-nightly-stars.png +0 -0
- package/system/components.css +48 -29
package/DESIGN.md
CHANGED
|
@@ -74,7 +74,7 @@ A web mock is never a native spec. Every surface binds at one tier, and every ca
|
|
|
74
74
|
- **Tier 3: Roku.** Tier 2, plus put.io conventions in custom SceneGraph components.
|
|
75
75
|
- **Tier 4: tv.put.io · Tizen · webOS.** The web system, restrained to a calm, list-first 10-foot interface.
|
|
76
76
|
|
|
77
|
-
Tier 1, 3, and 4 cards are Components (put.io owns the recipe); tier 2 cards are Elements (the platform owns the control). Per-platform contracts: `platforms/<platform>/DESIGN.md
|
|
77
|
+
Tier 1, 3, and 4 cards are Components (put.io owns the recipe); tier 2 cards are Elements (the platform owns the control). Per-platform contracts: `platforms/<platform>/DESIGN.md`, or the app repository it points to. On every tier, file-type icons are brand-yellow Phosphor icons.
|
|
78
78
|
|
|
79
79
|
## Colors
|
|
80
80
|
|
|
@@ -116,7 +116,7 @@ Default radius `6px`; `4px` for tight controls, `8px` or `10px` for panels, pill
|
|
|
116
116
|
- **Primary:** `--button-primary-bg`, hover `--button-primary-bg-hover`, label `--button-primary-fg`. At most one per action group.
|
|
117
117
|
- **Secondary:** `--button-default-bg` plus a `--border` border; hover steps both (`--button-default-bg-hover`, `--border-hover`).
|
|
118
118
|
- **Ghost:** text only, `--button-ghost-fg`; hover adds `--button-ghost-bg-hover` and lifts the label to `--button-ghost-fg-hover`. Use it for anything quieter than secondary: header `Sign in`, toolbar actions, `Cancel` beside a confirm, icon-only buttons.
|
|
119
|
-
- **Disabled:**
|
|
119
|
+
- **Disabled:** submit buttons are never disabled at rest; the submit validates. A filled button is disabled only while its action runs (with a spinner) or once it is done, so it keeps its own fill and label at full opacity; a finished 2FA “Verified” is a `.btn-success` with `data-state="success"`. Secondary and quiet buttons drop their text to `--solid` at full opacity.
|
|
120
120
|
- **Focus:** `--shadow-focus`, a 2px `--bg` gap then a 2px `--solid-hover` ring, at least 3:1 against the page and panels in both modes.
|
|
121
121
|
- A bordered button always fills (secondary). Border-only buttons read smaller beside solid fills and vanish on dark.
|
|
122
122
|
- Four sizes share one box per size across tiers: default 36px, `md` 32px, `sm` 28px, `xs` 24px. Read `--button-height*`, `--button-padding-x*`, `--button-label-size*`, `--button-tracking*`, `--button-icon-size`, `--button-gap`.
|
|
@@ -127,6 +127,7 @@ Default radius `6px`; `4px` for tight controls, `8px` or `10px` for panels, pill
|
|
|
127
127
|
### Rows, fields, and panels
|
|
128
128
|
|
|
129
129
|
- File rows are the core component: raw names, compact metadata, obvious hover and focus at the same row height.
|
|
130
|
+
- A field's own link sits at the end of its label row: `.label-action`, placed after the field so Tab reaches the field first. It is `--fs-caption`, `--text-secondary` (hover `--text`), no underline, on the label's baseline.
|
|
130
131
|
- Fields read `--field-bg`, `--field-border`, and `--field-ring` (focus). They are filled: `--bg-secondary` on a `--line` border, so they read on the page as well as in panels. Hover steps the border to `--border-hover`; focus steps it to `--solid-hover` with a matching 1px ring, at least 3:1 against the fill, the page, and panels in both modes (WCAG 1.4.11). Disabled fields take `--component-bg`. `--bg-secondary` is the field fill, so fields never sit on it. `aria-invalid="true"` turns border and text red; the fill stays.
|
|
131
132
|
- Raised panels read `--panel-*`.
|
|
132
133
|
- Feedback uses `.form-callout`: `--alert-*` colours, `data-state` `info`, `success`, or `error`; `.inline` drops surface and border. It holds a message only. `.form-group` (`8px` gap) spaces a control and its feedback.
|
|
@@ -134,16 +135,17 @@ Default radius `6px`; `4px` for tight controls, `8px` or `10px` for panels, pill
|
|
|
134
135
|
|
|
135
136
|
### Auth
|
|
136
137
|
|
|
137
|
-
- Sign in, sign up, password reset, and two-factor pages have no card or panel: the form sits on `--bg`. Recipe: `.auth-page`, `.auth-header`, `.auth-main`, `.auth-column`, `.auth-title`, `.auth-note`, `.auth-links`, `.auth-footer`.
|
|
138
|
+
- Sign in, sign up, password reset, and two-factor pages have no card or panel: the form sits on `--bg`. Recipe: `.auth-page`, `.auth-header`, `.auth-main`, `.auth-column`, `.auth-title`, `.auth-note`, `.auth-actions`, `.auth-links`, `.auth-footer`.
|
|
138
139
|
- The logo sits top-left in the header, in a `30.33px` box (26px ink × 7/6). The top right stays empty by default.
|
|
139
140
|
- One left-aligned column, up to `340px`, is centred horizontally and optically centred vertically: the form's middle sits near 45% of the viewport height, never below the true middle.
|
|
140
|
-
- A quiet centred footer links Terms of Service, Privacy Policy, and Help in muted `--fs-
|
|
141
|
-
- Text is one size:
|
|
141
|
+
- A quiet centred footer links Terms of Service, Privacy Policy, and Help in muted `--fs-caption`, separated by middots, 16px above the page bottom.
|
|
142
|
+
- Text is one size: labels, hints, `.auth-note` lines, errors, and the link row take `--fs-ui`. The footer and a label's own action take `--fs-caption`; fields take `--fs-field`.
|
|
142
143
|
- One title per screen: `--fs-title` (`22px`), medium, `--tracking-snug` (`-0.01em`). No subtitles, taglines, or marketing lines; copy stays short.
|
|
143
144
|
- Fields and buttons keep the standard 36px box: the filled field and a full-width default-size primary.
|
|
144
145
|
- Labels are small, muted, and above the field.
|
|
145
|
-
- The primary button is full-width yellow.
|
|
146
|
-
-
|
|
146
|
+
- The primary button is full-width yellow. “Forgot password?” is the Password field's `.label-action`. Other secondary actions sit under the form in `.auth-links`: a bare link stays muted (“Use a recovery code”, “← Back to sign in”); a prompt's link (“New to put.io? Sign up”, “Wrong account? Sign out”) is `--text`, medium, and underlines only on hover. Two items split to the edges; a lone link or prompt centres.
|
|
147
|
+
- Buttons outside a form sit in `.auth-actions`, 16px below what precedes them; a description `.auth-note` right before them leaves 28px. A back link leads with Phosphor's arrow-left as inline SVG. A legal line under the submit button centres on it.
|
|
148
|
+
- Tab order runs fields (each followed by its label action), submit, then secondary links; DOM order follows visual order, with no positive `tabindex`. Two-factor runs the code, Verify, then its links. The reveal-password toggle takes `tabindex="-1"` and keeps its accessible name: keyboard-only users can't toggle visibility, screen-reader users still can in browse mode. No link leaves the tab order.
|
|
147
149
|
- Form errors: one message in a `.form-reveal` row right above the submit button, in red `--fs-ui` text, 8px under the fields. Invalid fields turn red. Auth forms never use the boxed `.form-callout`.
|
|
148
150
|
- Copy: “Sign in”, “Sign up”, and “That username or password doesn't look right.”
|
|
149
151
|
- auth.put.io still ships the older card layout: drift.
|
package/README.md
CHANGED
|
@@ -47,28 +47,15 @@ keeps the token file's element styles below utilities:
|
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
Utilities then read the tokens: `bg-bg` is `var(--bg)`, `rounded-sm` is
|
|
50
|
-
`var(--radius-sm)`, `text-lg` is `var(--fs-lg)`.
|
|
50
|
+
`var(--radius-sm)`, `text-lg` is `var(--fs-lg)`. Theme rules:
|
|
51
51
|
[Tailwind v4 theme](system/README.md#tailwind-v4-theme).
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
| Subpath | File |
|
|
56
|
-
| -------------------------------- | ----------------------------------------------------------------------------------- |
|
|
57
|
-
| `@putdotio/design/css` | [`dist/css/tokens.css`](dist/css/tokens.css) |
|
|
58
|
-
| `@putdotio/design/components` | [`system/components.css`](system/components.css) |
|
|
59
|
-
| `@putdotio/design/tailwind` | [`dist/css/tailwind.css`](dist/css/tailwind.css) |
|
|
60
|
-
| `@putdotio/design/tokens` | [`dist/tokens.flat.json`](dist/tokens.flat.json) |
|
|
61
|
-
| `@putdotio/design/tokens/meta` | [`dist/tokens.js`](dist/tokens.js), typed by [`dist/tokens.d.ts`](dist/tokens.d.ts) |
|
|
62
|
-
| `@putdotio/design/tokens/dtcg` | [`dist/tokens.dtcg.json`](dist/tokens.dtcg.json) |
|
|
63
|
-
| `@putdotio/design/tokens/figma` | [`dist/figma/putio.tokens.json`](dist/figma/putio.tokens.json) |
|
|
64
|
-
| `@putdotio/design/assets/<file>` | [`system/assets`](system/assets) |
|
|
65
|
-
| `@putdotio/design/design.md` | [`DESIGN.md`](DESIGN.md) |
|
|
53
|
+
Every entrypoint is listed under `exports` in [package.json](package.json).
|
|
66
54
|
|
|
67
55
|
## Docs
|
|
68
56
|
|
|
69
57
|
- [Design contract](DESIGN.md)
|
|
70
|
-
- [Design guide
|
|
71
|
-
- [Distribution](docs/distribution.md)
|
|
58
|
+
- [Design guide](system/README.md)
|
|
72
59
|
- [Contributing](CONTRIBUTING.md)
|
|
73
60
|
- [Security](https://github.com/putdotio/.github/blob/main/SECURITY.md)
|
|
74
61
|
- [Agent guide](AGENTS.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@putdotio/design",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"description": "Public put.io design tokens, brand assets, and design-system guidance.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"dev": "node scripts/serve-system.ts",
|
|
70
70
|
"tokens:build": "node scripts/build-tokens.ts",
|
|
71
71
|
"tokens:check": "node scripts/build-tokens.ts && node scripts/check-component-css.ts && node scripts/check-tailwind-theme.ts && node scripts/verify-design-system.ts",
|
|
72
|
-
"artifact": "node scripts/build-artifact.ts
|
|
72
|
+
"artifact": "node scripts/build-artifact.ts",
|
|
73
73
|
"test:config": "node --test tests/playwright-config.test.mjs",
|
|
74
74
|
"typecheck:scripts": "tsc --noEmit --ignoreConfig --skipLibCheck --module ESNext --moduleResolution Bundler --target ES2022 --strict --types node scripts/*.ts playwright.config.ts tests/e2e/*.ts sst.config.ts infra/*.ts",
|
|
75
75
|
"html:check": "html-validate \"system/**/*.html\"",
|
|
@@ -93,5 +93,5 @@
|
|
|
93
93
|
"typescript": "^7.0.2",
|
|
94
94
|
"yaml": "^2.9.0"
|
|
95
95
|
},
|
|
96
|
-
"packageManager": "pnpm@12.
|
|
96
|
+
"packageManager": "pnpm@12.15.0"
|
|
97
97
|
}
|
package/system/README.md
CHANGED
|
@@ -1,228 +1,76 @@
|
|
|
1
1
|
# put.io design system
|
|
2
2
|
|
|
3
|
-
The guide site
|
|
4
|
-
|
|
3
|
+
The guide site and the package's component and Tailwind layers. The design
|
|
4
|
+
rules live in [DESIGN.md](../DESIGN.md).
|
|
5
5
|
|
|
6
|
-
##
|
|
7
|
-
|
|
8
|
-
- `index.html` redirects to `design-system.html`, the guide and preview-card
|
|
9
|
-
index. `design-system-light.html` redirects to
|
|
10
|
-
`design-system.html?theme=light`.
|
|
11
|
-
- `tokens.css` is generated from `../tokens/**/*.tokens.json`; edit the token
|
|
12
|
-
sources and rebuild.
|
|
13
|
-
- `components.css` is the Tier-1 component layer (the web contract).
|
|
14
|
-
- `tv.css` is the TV / 10-foot component CSS, scoped to `.tv` artboards.
|
|
15
|
-
- `assets/` holds logos, favicons, retro marks, and app icons.
|
|
16
|
-
- `preview/` holds specimen cards for the foundations and every platform tier.
|
|
17
|
-
Its `_`-prefixed files are card chrome only (frame, tier strip, HIG, M3, and
|
|
18
|
-
Roku shells, resize and password-toggle helpers).
|
|
19
|
-
|
|
20
|
-
Cards are named `<platform>-<section><n>-<subject>.html` with section letter `f`
|
|
21
|
-
foundations, `c` components, `e` elements, `p` patterns, or `s` screens
|
|
22
|
-
(`ios-e01-toggle.html`); the numbered `00-…13-…` files are the tier-0
|
|
23
|
-
foundations. Element cards are a fixed 1120×580 viewport: one control, its
|
|
24
|
-
states, a spec strip, a don't block.
|
|
25
|
-
|
|
26
|
-
## Fonts
|
|
27
|
-
|
|
28
|
-
Preview pages load the public put.io font CSS from `static.put.io`, the same
|
|
29
|
-
source as the product; font licensing is in
|
|
30
|
-
[Distribution](../docs/distribution.md#fonts-and-assets). Each preview links
|
|
31
|
-
both families in `<head>`:
|
|
32
|
-
|
|
33
|
-
```html
|
|
34
|
-
<link rel="stylesheet" href="https://static.put.io/fonts/gt-america/standard/font.css" />
|
|
35
|
-
<link rel="stylesheet" href="https://static.put.io/fonts/berkeley-mono/variable/font.css" />
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Those two links are the full font set: mono text reads `--font-mono` (Berkeley
|
|
39
|
-
Mono), whose tabular figures carry the numerics.
|
|
40
|
-
|
|
41
|
-
Weight names on the font host:
|
|
42
|
-
`100 ultra-light · 200 thin · 300 light · 400 regular · 500 medium · 700 bold · 900 black`.
|
|
43
|
-
The tokens use four of them through `--fw-regular`, `--fw-medium`, `--fw-bold`,
|
|
44
|
-
and `--fw-black`.
|
|
45
|
-
|
|
46
|
-
## Using tokens
|
|
47
|
-
|
|
48
|
-
Guide pages in `system/` load the generated site stylesheet:
|
|
49
|
-
|
|
50
|
-
```html
|
|
51
|
-
<link rel="stylesheet" href="./tokens.css" />
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
The canonical source is DTCG-compatible JSON in [`../tokens`](../tokens). This
|
|
55
|
-
site stylesheet (`system/tokens.css`) and the package stylesheet
|
|
56
|
-
(`dist/css/tokens.css`, exported as `@putdotio/design/css`) are generated from
|
|
57
|
-
the same source. Brand yellow is `var(--yellow-solid)`; its emitted hsl form is
|
|
58
|
-
pinned in the [`../DESIGN.md`](../DESIGN.md) frontmatter, and the
|
|
59
|
-
`--button-primary-bg-hover` derivation is in
|
|
60
|
-
[Distribution](../docs/distribution.md#generated-files).
|
|
61
|
-
|
|
62
|
-
Stacking, breakpoints, the root font-size, and the `@media` literal rule live
|
|
63
|
-
in [Layout & Spacing](../DESIGN.md#layout--spacing) and
|
|
64
|
-
[Typography](../DESIGN.md#typography).
|
|
65
|
-
|
|
66
|
-
Package consumers import as in the [README](../README.md#use).
|
|
67
|
-
|
|
68
|
-
For **TV preview cards**, also load `tv.css`, which adds the `.tv`, `.scr`,
|
|
69
|
-
`.row`, solid surface tiers, and player chrome on top of the tokens, authored
|
|
70
|
-
1:1 at 1920×1080 (a px in the file is a px on the TV). Every component selector
|
|
71
|
-
is scoped under `.tv`, so the file can be bundled globally alongside web
|
|
72
|
-
components. Platform repos implement TV components in their native UI stacks:
|
|
73
|
-
|
|
74
|
-
```html
|
|
75
|
-
<link rel="stylesheet" href="./tokens.css" /> <link rel="stylesheet" href="./tv.css" />
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
TV material, focus, and the `tv` token group rules live in
|
|
79
|
-
[Components](../DESIGN.md#components). Guide specifics: the `tv` group is
|
|
80
|
-
`mode: "tv"` and read from `dist/tokens.flat.json`; `--radius-tv` is the one
|
|
81
|
-
`tv` value the token CSS emits, so `tv.css` declares the `--tv-*` properties
|
|
82
|
-
its cards use itself.
|
|
83
|
-
|
|
84
|
-
## Core rules
|
|
85
|
-
|
|
86
|
-
- **Yellow** follows [Colors](../DESIGN.md#colors). Guide specifics: `--yellow-solid-hover` is the hover peer with one value in both modes, and button focus-visible rings use `--shadow-focus`: a 2px `--bg` gap, then a 2px `--solid-hover` ring, at least 3:1 against the page and panels. `--shadow-focus-color` (the yellow at 35% alpha) stays only as shadcn's `--ring`. Form fields focus with `--field-ring` instead.
|
|
87
|
-
- **Yellow as text on light** uses `--yellow-text-secondary` (≈5:1, AA); `--yellow-solid` is fill-only on light backgrounds.
|
|
88
|
-
- **Labels on yellow** use `--primary-foreground`, a warm dark that passes AA on the yellow fill.
|
|
89
|
-
- **One design, two modes.** Light and dark are the same markup and components with different token values. Toggle `.dark` on `<html>`; one page serves both modes. Components consume semantic tokens (`--bg`, `--text`, `--border`, `--accent`, …) instead of raw `#hex` or `hsl()`.
|
|
90
|
-
- **Text sizes are tokens.** The guide, the web previews, and their shared CSS (`_tier.css`, `_frame.css`, `_element.css`) set text with a role (`--fs-ui`, `--fs-body`, `--fs-caption`, `--fs-field`) or a heading step, per [Typography](../DESIGN.md#typography). Icon glyphs keep px, rules under `.tv` keep the TV grid, and native and TV previews keep platform px. `pnpm verify` fails on any other literal font size.
|
|
91
|
-
- **Wordmarks are optically pre-centred.** All three logo SVGs carry built-in top padding in the viewBox (retro pair `0 -16 376 112`, `logo.svg` `0 -24.44 621.79 176.055`). Flex-centre them as-is, with no `translateY` nudge. Height-set placements size boxes at 7/6 of the ink height.
|
|
92
|
-
|
|
93
|
-
Icons, fields and panels, and raw filenames follow the root
|
|
94
|
-
[Components](../DESIGN.md#components) and
|
|
95
|
-
[Content and Icons](../DESIGN.md#content-and-icons).
|
|
96
|
-
|
|
97
|
-
## Binding tiers
|
|
6
|
+
## Preview cards
|
|
98
7
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
card shows how a stock control receives tokens.
|
|
8
|
+
`design-system.html` indexes every card. Each card renders one concept against
|
|
9
|
+
the real tokens; read it as a spec.
|
|
102
10
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
11
|
+
- Name cards `<platform>-<section><n>-<subject>.html`, with section `f`
|
|
12
|
+
foundations, `c` components, `e` elements, `p` patterns, or `s` screens
|
|
13
|
+
(`ios-e01-toggle.html`). The numbered `00-…13-…` files are tier-0
|
|
14
|
+
foundations. `pnpm verify` rejects other names.
|
|
15
|
+
- Element cards are a fixed 1120×580 viewport: one control, its states, a spec
|
|
16
|
+
strip, a don't block.
|
|
17
|
+
- Native cards are drawn 1:1 in their own unit (iPhone 393×852pt, iPad
|
|
18
|
+
1024×768pt, Apple Watch 176×215pt, Android 412×915dp, TV 1920×1080px), so a
|
|
19
|
+
number on a card is the number in the HIG or the Material spec. Those
|
|
20
|
+
metrics, device bezels, and on-video chrome stay out of the token graph.
|
|
21
|
+
- Each card links the public font CSS from `static.put.io`; these two links are
|
|
22
|
+
the full font set:
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
<link rel="stylesheet" href="https://static.put.io/fonts/gt-america/standard/font.css" />
|
|
26
|
+
<link rel="stylesheet" href="https://static.put.io/fonts/berkeley-mono/variable/font.css" />
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- TV cards also load `tv.css`, authored 1:1 at 1920×1080 and scoped under
|
|
30
|
+
`.tv`. It declares the `--tv-*` properties its cards use, because the token
|
|
31
|
+
CSS emits only `--radius-tv` from the `tv` group.
|
|
32
|
+
- Wordmark SVGs carry their optical centring in the viewBox. Flex-centre them
|
|
33
|
+
as is, with no `translateY` nudge.
|
|
108
34
|
|
|
109
35
|
## Theme System
|
|
110
36
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
the same storage key on standalone open, and `preview/_resize.js` listens for
|
|
116
|
-
the parent broadcast when embedded. A URL query (`?theme=light` or
|
|
117
|
-
`?theme=dark`) overrides both, for screenshots.
|
|
37
|
+
Light and dark are the same markup with different token values: toggle `.dark`
|
|
38
|
+
on `<html>`. The guide stores the choice in `localStorage` and broadcasts it to
|
|
39
|
+
embedded cards; `?theme=light` or `?theme=dark` overrides both, for
|
|
40
|
+
screenshots.
|
|
118
41
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
surfaces, so the `ios-*`, `android-*`, `androidtv-*`, `tvos-*`, `watchos-*`,
|
|
123
|
-
`roku-*`, `tv-*`, and `mobile-*` cards plus `web-p06-player.html` are locked.
|
|
124
|
-
Locked previews ignore `localStorage`, parent broadcasts, and the URL query.
|
|
125
|
-
New component previews stay unlocked, so the tokens render them in both modes.
|
|
42
|
+
Native, TV, and on-video player cards are fixed-mode product mockups and set
|
|
43
|
+
`<html data-theme-lock="dark">`, which ignores the toggle and the query. Every
|
|
44
|
+
other card stays unlocked, and the e2e suite checks it in both modes.
|
|
126
45
|
|
|
127
46
|
## shadcn / Base UI interop
|
|
128
47
|
|
|
129
|
-
The token layer
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
own, so the two name systems cannot drift.
|
|
133
|
-
|
|
134
|
-
| shadcn name | put.io target |
|
|
135
|
-
| -------------------------------------------- | ---------------------------------------------------------- |
|
|
136
|
-
| `--primary` / `--primary-foreground` | `--yellow-solid` / `var(--primary-foreground)` (warm dark) |
|
|
137
|
-
| `--destructive` / `--destructive-foreground` | `--red-solid` / `var(--destructive-foreground)` (= `#fff`) |
|
|
138
|
-
| `--success` / `--success-foreground` | `--green-solid` / `var(--success-foreground)` (= `#fff`) |
|
|
139
|
-
| `--muted` / `--muted-foreground` | `--bg-secondary` / `--text-secondary` |
|
|
140
|
-
| `--accent` / `--accent-foreground` | `--component-bg` / `--text` |
|
|
141
|
-
| `--card` / `--card-foreground` | `--component-bg` / `--text` |
|
|
142
|
-
| `--popover` / `--popover-foreground` | `--component-bg` / `--text` |
|
|
143
|
-
| `--input` | `--field-border` |
|
|
144
|
-
| `--ring` | `--shadow-focus-color` |
|
|
145
|
-
| `--background` / `--foreground` | `--bg` / `--text` |
|
|
146
|
-
|
|
147
|
-
The `--input-*` set (`-bg`, `-border`, `-border-hover`, `-border-focus`,
|
|
148
|
-
`-ring`, `-text`, `-placeholder`, `-radius`) and `--foreground-muted` are
|
|
149
|
-
[deprecated](../docs/distribution.md#deprecated-tokens): shadcn itself names
|
|
150
|
-
only `--input` and `--muted-foreground`. Each deprecated name aliases its
|
|
151
|
-
replacement; **author against `--field-*` and `--text-secondary`.**
|
|
48
|
+
The token layer defines the shadcn names (`--primary`, `--muted-foreground`,
|
|
49
|
+
`--input`, `--ring`, …) as aliases of put.io tokens, so shadcn/ui blocks and
|
|
50
|
+
Base UI components resolve against put.io values without renaming.
|
|
152
51
|
|
|
153
|
-
|
|
154
|
-
`--
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
52
|
+
- Author against `--field-*` and `--text-secondary`. The `--input-*` set and
|
|
53
|
+
`--foreground-muted` are deprecated aliases.
|
|
54
|
+
- On a solid fill, read `--primary-foreground`, `--destructive-foreground`, or
|
|
55
|
+
`--success-foreground` instead of literal `#000` or `#fff`.
|
|
56
|
+
- Express state with `data-state` (`open`, `checked`, `completed`, …), the
|
|
57
|
+
attribute headless primitives set, not class flags such as `.is-active`.
|
|
58
|
+
- Mark compound parts with `data-slot` (`modal-header`, `card-title`, …) so
|
|
59
|
+
external CSS can target one part without nested class chains.
|
|
160
60
|
|
|
161
61
|
### Tailwind v4 theme
|
|
162
62
|
|
|
163
63
|
`@putdotio/design/tailwind` ([`dist/css/tailwind.css`](../dist/css/tailwind.css))
|
|
164
|
-
maps the tokens into Tailwind v4 theme keys
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
| `--radius`, `--radius-*` | `--radius`, `--radius-*`, `--<component>-radius` | `rounded`, `rounded-sm`, `rounded-panel` |
|
|
178
|
-
| `--shadow-*` | `--shadow-*`, `--<component>-shadow`, `--<component>-ring` | `shadow-sm`, `shadow-menu`, `shadow-field-ring` |
|
|
179
|
-
| `--ease-*` | `--ease-*` | `ease-out` |
|
|
180
|
-
| `--transition-duration-*` | `--dur-*` | `duration-base` |
|
|
181
|
-
|
|
182
|
-
- The block is `@theme inline reference`. Utilities read the put.io custom
|
|
183
|
-
property where they apply, so `.dark` works. Tailwind declares none of the
|
|
184
|
-
keys, so `--radius-sm` or `--font-sans` stays the token instead of a
|
|
185
|
-
self-reference. In custom CSS, read the put.io name (`var(--bg)`), not
|
|
186
|
-
`var(--color-bg)`.
|
|
187
|
-
- shadcn class names such as `bg-primary`, `border-input`, and
|
|
188
|
-
`text-muted-foreground` resolve through the aliases above.
|
|
189
|
-
- `text-*` sizes set no line height; the document's `--lh-normal` applies until
|
|
190
|
-
a `leading-*` utility sets one.
|
|
191
|
-
- TV tokens, z-indexes and breakpoints stay out. Deprecated tokens stay in until a major release removes them.
|
|
192
|
-
- `--spacing` is `var(--space-1)`, so `p-4` is 16px at any root size and equals
|
|
193
|
-
`--space-4`. Tailwind defaults that put.io does not define, such as
|
|
194
|
-
`rounded-xl`, stay available.
|
|
195
|
-
|
|
196
|
-
### State via `data-state`, not class flags
|
|
197
|
-
|
|
198
|
-
Base UI / headless primitives set
|
|
199
|
-
`data-state="open|closed|checked|unchecked|active|completed|pending|loading"`
|
|
200
|
-
on the DOM. put.io components target those same attribute values in CSS; see
|
|
201
|
-
the wizard step bar in `web-p00b-form-flows.html`
|
|
202
|
-
(`.step[data-state="completed"]`) and the menu/table cards. Express state with
|
|
203
|
-
`data-state` rather than class flags (`.is-active`, `.on`, `.current`), so the
|
|
204
|
-
same DOM contract works with any controlling code and devtools show the state
|
|
205
|
-
directly.
|
|
206
|
-
|
|
207
|
-
### Compound parts via `data-slot`
|
|
208
|
-
|
|
209
|
-
shadcn marks parts of compound components with `data-slot="card-header"`,
|
|
210
|
-
`data-slot="card-title"`, and so on. put.io does the same on the folder picker
|
|
211
|
-
(`data-slot="modal" / "modal-header" / "modal-body" / "modal-footer"`), so
|
|
212
|
-
external CSS can target one part without nested class chains.
|
|
213
|
-
|
|
214
|
-
## Design variants
|
|
215
|
-
|
|
216
|
-
Four documented type stacks; pick one per surface:
|
|
217
|
-
|
|
218
|
-
| Variant | Stack | Use when |
|
|
219
|
-
| ------------ | -------------------------------------------------- | ------------------------------------ |
|
|
220
|
-
| Clean Modern | GT America | default app UI |
|
|
221
|
-
| Monospace | Berkeley Mono | terminal, logs, technical dashboards |
|
|
222
|
-
| Brutalist | GT America Black + Berkeley Mono | marketing punctuation |
|
|
223
|
-
| Editorial | GT America Black 900 (tight tracking) + GT America | landing hero, about |
|
|
224
|
-
|
|
225
|
-
## Preview cards
|
|
226
|
-
|
|
227
|
-
`design-system.html` is the full index. Each card renders one concept against
|
|
228
|
-
the real tokens; read it as a spec.
|
|
64
|
+
maps the tokens into Tailwind v4 theme keys: `bg-bg`, `rounded-panel`,
|
|
65
|
+
`text-display`, `duration-base`. Import it after `tailwindcss` and
|
|
66
|
+
`@putdotio/design/css`, as in the [README](../README.md#use).
|
|
67
|
+
|
|
68
|
+
- The block is `@theme inline reference`: utilities read the put.io custom
|
|
69
|
+
property, so `.dark` works. In custom CSS, read the put.io name (`var(--bg)`),
|
|
70
|
+
not `var(--color-bg)`.
|
|
71
|
+
- `text-*` sizes set no line height; `--lh-normal` applies until a `leading-*`
|
|
72
|
+
utility sets one.
|
|
73
|
+
- `--spacing` is `var(--space-1)`, so `p-4` is 16px and equals `--space-4`.
|
|
74
|
+
- z-indexes, breakpoints, and the `tv` group except `--radius-tv` stay out.
|
|
75
|
+
Tailwind defaults that put.io does not define, such as `rounded-xl`, stay
|
|
76
|
+
available.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
108dp adaptive-icon grid, at the colour icon's scale (its 1024 master fills
|
|
3
|
+
the 72dp centre). Paths carry no fill: the platform tints them.
|
|
4
|
+
-->
|
|
5
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 108 108">
|
|
6
|
+
<path fill-rule="evenodd" d="M34.594 44.508H35.651V40.289H36.709V38.18H37.766V37.125H38.823V36.07H39.881V33.961H41.996V32.906H43.053V31.852H45.168V30.797H47.283V29.742L58.915 29.742V30.797H61.029V31.852H63.144V32.906H65.259V33.961H66.316V35.016H67.374V36.07H68.431V38.18H69.489V40.289H70.546V42.398H71.604V52.945H70.546V56.109H69.489V58.219H68.431V59.273H67.374V60.328H66.316V61.383H65.259V62.438H64.202V63.492H62.087V64.547H59.972V65.602H55.742V66.656H45.168V67.711H44.111V72.984H43.053V74.039H41.996V75.094H36.709V74.039H35.651V72.984H34.594L34.594 44.508ZM46.225 44.508V42.398H47.283V41.344H49.397V40.289H51.512V39.234H54.685V40.289H57.857V41.344H58.914V42.398H59.972V44.508H61.029V50.836H59.972V51.891H58.914V52.945H57.857V54H56.8V55.055H49.397V54H48.34V52.945H47.283V51.891H46.225V49.781H45.168V44.508H46.225Z"/>
|
|
7
|
+
<path d="M50.455 43.453H48.34V45.562H47.283V48.727H48.34V50.836H49.398V48.727H50.455V46.617H51.513V45.562H53.627V44.508H55.742V43.453H56.8V42.398H53.627V41.344H52.57V42.398H50.455V43.453Z"/>
|
|
8
|
+
<path d="M74.776 44.508H73.719V54H72.662V57.164H71.604V59.273H70.546V60.328H69.489V61.383H68.432V62.438H67.374V63.492H66.317V64.547H65.259V65.602H63.145V66.656H61.03V67.711H56.8V68.766H46.226L46.226 74.039H45.168V75.094H44.111V76.148H43.053V77.203H39.881V78.258H40.939V79.312H46.226V78.258H47.283V77.203H48.341V71.93H49.398V70.875H59.972V69.82H64.202V68.766H66.317V67.711H68.432V66.656H69.489V65.602H70.546V64.547H71.604V63.492H72.662V62.438H73.719V60.328H74.776V57.164H75.834V46.617H74.776V44.508Z"/>
|
|
9
|
+
</svg>
|
|
Binary file
|
package/system/components.css
CHANGED
|
@@ -148,22 +148,14 @@ button i { font-size: var(--button-icon-size); line-height: 1; display: inline-f
|
|
|
148
148
|
|
|
149
149
|
/* States */
|
|
150
150
|
/* Bare disabled buttons fade; hover styles above are gated on :not(:disabled).
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
fill
|
|
151
|
+
Submit buttons are never disabled at rest: the submit validates. A filled
|
|
152
|
+
button is disabled only while its action runs (with a spinner) or once it
|
|
153
|
+
is done, so it keeps its own fill and label at full opacity: "Verifying…"
|
|
154
|
+
stays yellow, a finished 2FA "Verified" stays green with
|
|
155
|
+
data-state="success". */
|
|
154
156
|
button:disabled { opacity: 0.3; cursor: not-allowed; }
|
|
155
|
-
:is(.btn-primary, .btn-success, .btn-danger, .btn-info, .btn-invert):not(.btn-quiet):disabled {
|
|
156
|
-
opacity: 1;
|
|
157
|
-
background: var(--solid);
|
|
158
|
-
border-color: var(--solid);
|
|
159
|
-
color: var(--solid-foreground);
|
|
160
|
-
}
|
|
157
|
+
:is(.btn-primary, .btn-success, .btn-danger, .btn-info, .btn-invert):not(.btn-quiet):disabled { opacity: 1; }
|
|
161
158
|
.btn-default:not(.btn-quiet):disabled { opacity: 1; color: var(--solid); }
|
|
162
|
-
.btn-success[data-state="success"]:disabled {
|
|
163
|
-
background: var(--button-success-bg);
|
|
164
|
-
border-color: var(--button-success-bg);
|
|
165
|
-
color: var(--button-success-fg);
|
|
166
|
-
}
|
|
167
159
|
/* An opaque double ring: a --bg gap, then --solid-hover, ≥3:1 on page and panels. */
|
|
168
160
|
button:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
169
161
|
|
|
@@ -360,8 +352,21 @@ input, textarea, select { text-transform: none; letter-spacing: normal; }
|
|
|
360
352
|
.form-group .hint { margin-top: 0; }
|
|
361
353
|
.form-group > .form-callout.inline { margin-top: 0; }
|
|
362
354
|
|
|
363
|
-
/* Label
|
|
364
|
-
password?").
|
|
355
|
+
/* Label action: a field's own link at the end of its label row (e.g.
|
|
356
|
+
"Forgot password?"). Place .label-action after the field in the DOM, so
|
|
357
|
+
Tab reaches the field before its link; the grid lifts it onto the label's
|
|
358
|
+
row and aligns the two on their baselines. */
|
|
359
|
+
.form-group:has(> .label-action) {
|
|
360
|
+
display: grid;
|
|
361
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
362
|
+
column-gap: 12px;
|
|
363
|
+
}
|
|
364
|
+
.form-group:has(> .label-action) > * { grid-column: 1 / -1; }
|
|
365
|
+
.form-group:has(> .label-action) > .label { grid-row: 1; grid-column: 1; align-self: baseline; }
|
|
366
|
+
.form-group > .label-action { grid-row: 1; grid-column: 2; align-self: baseline; }
|
|
367
|
+
|
|
368
|
+
/* Label-row: the older wrapper, label and link before the field. Prefer
|
|
369
|
+
.label-action, which keeps the link after its field in Tab order. */
|
|
365
370
|
.form-group .label-row {
|
|
366
371
|
display: flex;
|
|
367
372
|
justify-content: space-between;
|
|
@@ -369,12 +374,14 @@ input, textarea, select { text-transform: none; letter-spacing: normal; }
|
|
|
369
374
|
gap: 12px;
|
|
370
375
|
}
|
|
371
376
|
.form-group .label-row .label { margin-bottom: 0; }
|
|
377
|
+
.form-group > .label-action,
|
|
372
378
|
.form-group .label-row a {
|
|
373
|
-
font-size: var(--fs-
|
|
379
|
+
font-size: var(--fs-caption);
|
|
374
380
|
color: var(--text-secondary);
|
|
375
381
|
text-decoration: none;
|
|
376
382
|
white-space: nowrap;
|
|
377
383
|
}
|
|
384
|
+
.form-group > .label-action:hover,
|
|
378
385
|
.form-group .label-row a:hover { color: var(--text); }
|
|
379
386
|
|
|
380
387
|
/* ===========================================================
|
|
@@ -571,9 +578,9 @@ input, textarea, select { text-transform: none; letter-spacing: normal; }
|
|
|
571
578
|
Card-less: the form sits on the page background. Logo top-left
|
|
572
579
|
in .auth-header, one left-aligned 340px column centred in
|
|
573
580
|
.auth-main, a quiet link row in .auth-footer. Controls keep the
|
|
574
|
-
standard 36px box; the title is the only heading.
|
|
575
|
-
|
|
576
|
-
fields take --fs-field. Under
|
|
581
|
+
standard 36px box; the title is the only heading. Labels, hints, notes,
|
|
582
|
+
errors and links take the UI role, --fs-ui; the footer and a label's own
|
|
583
|
+
action take --fs-caption; fields take --fs-field. Under
|
|
577
584
|
640px the gutters drop to 24px so the logo lines up with the column.
|
|
578
585
|
=========================================================== */
|
|
579
586
|
.auth-page {
|
|
@@ -634,6 +641,11 @@ input, textarea, select { text-transform: none; letter-spacing: normal; }
|
|
|
634
641
|
.auth-column form > .form-reveal + .btn-block { margin-top: 16px; }
|
|
635
642
|
/* A legal line under the submit button centres on it. */
|
|
636
643
|
.auth-column form > .btn-block + .auth-note { margin-top: 16px; text-align: center; }
|
|
644
|
+
/* Buttons outside a form ("Open mail", "Resend link"): 16px under what
|
|
645
|
+
precedes them, but a description right before them spaces them like the
|
|
646
|
+
title does, 28px. */
|
|
647
|
+
.auth-actions { margin-top: 16px; display: flex; flex-direction: column; gap: 12px; }
|
|
648
|
+
.auth-column > .auth-note + .auth-actions { margin-top: 28px; }
|
|
637
649
|
.auth-links {
|
|
638
650
|
margin: 24px 0 0;
|
|
639
651
|
display: flex;
|
|
@@ -644,13 +656,20 @@ input, textarea, select { text-transform: none; letter-spacing: normal; }
|
|
|
644
656
|
color: var(--text-secondary);
|
|
645
657
|
font-size: var(--fs-ui);
|
|
646
658
|
}
|
|
647
|
-
/* A prompt's link reads as text ("New to put.io? Sign up")
|
|
648
|
-
action link ("Use a recovery code") stays
|
|
649
|
-
every p at --fs-body, so a prompt inherits
|
|
659
|
+
/* A prompt's link reads as text ("New to put.io? Sign up"): medium, no
|
|
660
|
+
underline until hover. A bare action link ("Use a recovery code") stays
|
|
661
|
+
muted. The token base sizes every p at --fs-body, so a prompt inherits
|
|
662
|
+
the row's size. */
|
|
650
663
|
.auth-links p { margin: 0; font-size: inherit; }
|
|
651
|
-
.auth-links a {
|
|
652
|
-
|
|
653
|
-
|
|
664
|
+
.auth-links a {
|
|
665
|
+
color: var(--text);
|
|
666
|
+
font-weight: var(--fw-medium);
|
|
667
|
+
text-decoration: none;
|
|
668
|
+
text-underline-offset: 3px;
|
|
669
|
+
}
|
|
670
|
+
.auth-links a:hover { text-decoration: underline; }
|
|
671
|
+
.auth-links > a { color: var(--text-secondary); font-weight: inherit; }
|
|
672
|
+
.auth-links > a:hover { color: var(--text); text-decoration: none; }
|
|
654
673
|
/* Two items split to the edges; a lone link or prompt centres. */
|
|
655
674
|
.auth-links > :only-child { margin-inline: auto; text-align: center; }
|
|
656
675
|
/* A back link leads with Phosphor's arrow-left, an inline SVG (or the icon font). */
|
|
@@ -662,8 +681,8 @@ input, textarea, select { text-transform: none; letter-spacing: normal; }
|
|
|
662
681
|
flex-wrap: wrap;
|
|
663
682
|
justify-content: center;
|
|
664
683
|
gap: 8px 16px;
|
|
665
|
-
padding: 24px 32px
|
|
666
|
-
font-size: var(--fs-
|
|
684
|
+
padding: 24px 32px 16px;
|
|
685
|
+
font-size: var(--fs-caption);
|
|
667
686
|
}
|
|
668
687
|
.auth-footer a { color: var(--text-secondary); text-decoration: none; }
|
|
669
688
|
.auth-footer a:hover { color: var(--text); }
|
|
@@ -682,7 +701,7 @@ input, textarea, select { text-transform: none; letter-spacing: normal; }
|
|
|
682
701
|
}
|
|
683
702
|
@media (max-width: 640px) {
|
|
684
703
|
.auth-header { padding: 24px; }
|
|
685
|
-
.auth-footer { padding: 24px; }
|
|
704
|
+
.auth-footer { padding: 24px 24px 16px; }
|
|
686
705
|
}
|
|
687
706
|
|
|
688
707
|
/* Floating-menu surface — one recipe for dropdowns, context menus,
|