@putdotio/design 5.0.0 → 5.1.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 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`. On every tier, file-type icons are brand-yellow Phosphor icons.
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:** colour drops to `--solid` at full opacity: filled variants take a `--solid` fill with `--solid-foreground` labels; quiet ones take `--solid` text. A finished action stays filled with `data-state="success"` on `.btn-success`.
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-ui`.
141
- - Text is one size: everything except the title and fields is `--fs-ui`. That covers labels, hints, `.auth-note` lines, errors, the link row, and the footer. Fields take `--fs-field`.
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. Secondary actions sit under the form as muted text links: “Forgot password?”, “New to put.io? Sign up”, “Wrong account? Sign out”.
146
- - Tab order runs fields, 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.
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@putdotio/design",
3
- "version": "5.0.0",
3
+ "version": "5.1.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 && node scripts/build-canvases.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\"",
package/system/README.md CHANGED
@@ -87,6 +87,7 @@ its cards use itself.
87
87
  - **Yellow as text on light** uses `--yellow-text-secondary` (≈5:1, AA); `--yellow-solid` is fill-only on light backgrounds.
88
88
  - **Labels on yellow** use `--primary-foreground`, a warm dark that passes AA on the yellow fill.
89
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.
90
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.
91
92
 
92
93
  Icons, fields and panels, and raw filenames follow the root
@@ -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
- Filled variants instead drop their fill to --solid at full opacity, so a
152
- disabled "Verifying…" stays legible. A finished action keeps its success
153
- fill with data-state="success" (2FA "Verified"). */
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-row: label on the left, link on the right (e.g. "Forgot your
364
- password?"). Sits ABOVE the field, inside a .form-group. */
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-ui);
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. All other text
575
- (labels, hints, notes, errors, links, footer) is the UI role, --fs-ui;
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 {
@@ -632,6 +639,13 @@ input, textarea, select { text-transform: none; letter-spacing: normal; }
632
639
  .auth-note a { color: var(--text); text-underline-offset: 3px; }
633
640
  .auth-column form > .form-reveal .form-callout { padding-top: 8px; }
634
641
  .auth-column form > .form-reveal + .btn-block { margin-top: 16px; }
642
+ /* A legal line under the submit button centres on it. */
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; }
635
649
  .auth-links {
636
650
  margin: 24px 0 0;
637
651
  display: flex;
@@ -642,26 +656,52 @@ input, textarea, select { text-transform: none; letter-spacing: normal; }
642
656
  color: var(--text-secondary);
643
657
  font-size: var(--fs-ui);
644
658
  }
645
- /* A prompt's link reads as text ("New to put.io? Sign up"); a bare
646
- action link ("Use a recovery code") stays muted. The token base sizes
647
- every p at --fs-body, so a prompt inherits the row's size. */
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. */
648
663
  .auth-links p { margin: 0; font-size: inherit; }
649
- .auth-links a { color: var(--text); text-underline-offset: 3px; }
650
- .auth-links > a { color: var(--text-secondary); text-decoration: none; }
651
- .auth-links > a:hover { color: var(--text); }
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; }
673
+ /* Two items split to the edges; a lone link or prompt centres. */
674
+ .auth-links > :only-child { margin-inline: auto; text-align: center; }
675
+ /* A back link leads with Phosphor's arrow-left, an inline SVG (or the icon font). */
676
+ .auth-links > a:has(> :is(svg, i)) { display: inline-flex; align-items: center; gap: 4px; }
677
+ .auth-links > a > svg { width: 16px; height: 16px; flex-shrink: 0; }
678
+ .auth-links > a > i { font-size: 16px; }
652
679
  .auth-footer {
653
680
  display: flex;
654
681
  flex-wrap: wrap;
655
682
  justify-content: center;
656
683
  gap: 8px 16px;
657
- padding: 24px 32px 28px;
658
- font-size: var(--fs-ui);
684
+ padding: 24px 32px 16px;
685
+ font-size: var(--fs-caption);
659
686
  }
660
687
  .auth-footer a { color: var(--text-secondary); text-decoration: none; }
661
688
  .auth-footer a:hover { color: var(--text); }
689
+ /* A middot centres in the gap before each link after the first. The empty
690
+ alt keeps it out of the link's accessible name. */
691
+ .auth-footer > * + * { position: relative; }
692
+ .auth-footer > * + *::before {
693
+ content: "·";
694
+ content: "·" / "";
695
+ position: absolute;
696
+ right: 100%;
697
+ width: 16px;
698
+ text-align: center;
699
+ color: var(--text-secondary);
700
+ pointer-events: none;
701
+ }
662
702
  @media (max-width: 640px) {
663
703
  .auth-header { padding: 24px; }
664
- .auth-footer { padding: 24px; }
704
+ .auth-footer { padding: 24px 24px 16px; }
665
705
  }
666
706
 
667
707
  /* Floating-menu surface — one recipe for dropdowns, context menus,