@phcdevworks/spectre-ui 1.7.0 → 1.8.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/CHANGELOG.md CHANGED
@@ -6,6 +6,28 @@ reflects package releases published to npm.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.8.0] - 2026-06-07
10
+
11
+ Release Title: Spinner Contract and Token Focus Alignment
12
+
13
+ Contract change type: additive
14
+
15
+ ### Added
16
+
17
+ - **Spinner Component**: Added standard brand and status variants (`primary`,
18
+ `secondary`, `success`, `warning`, `danger`, `info`, `neutral`, `accent`,
19
+ `cta`) and states (`disabled`, `loading` with `[aria-busy="true"]` support).
20
+ - Added `@phcdevworks/spectre-manifest` as a devDependency. `spectre.manifest.json`
21
+ at the repo root declares this package's ecosystem role, layer, exports, and
22
+ allowed dependency targets. `check:ecosystem` validates it in the check pipeline.
23
+ - **Token Alignment**: Updated `@phcdevworks/spectre-tokens` dependency to
24
+ `^2.8.0`.
25
+ - **Button Focus Ring Parity**: Consumed `buttons.danger.focusVisible` and
26
+ `buttons.success.focusVisible` tokens newly published in `2.8.0`. Danger and
27
+ success button variants now render a semantically correct focus ring (red and
28
+ green alpha respectively) on `:focus-visible`, matching the parity already
29
+ present for primary, secondary, ghost, and accent variants.
30
+
9
31
  ## [1.7.0] - 2026-06-03
10
32
 
11
33
  Release Title: Tag Variant Expansion and Token Alignment
package/README.md CHANGED
@@ -27,25 +27,25 @@ belongs in adapter packages such as `@phcdevworks/spectre-ui-astro`).
27
27
  semantic meaning. `ui-contract.manifest.json` is the machine-readable contract
28
28
  authority for this package's public styling surface.
29
29
 
30
- | Layer | Path | Rule |
31
- |---|---|---|
32
- | Token authority | Published `@phcdevworks/spectre-tokens` package | Design values and semantic meaning start there |
33
- | UI contract authority | `ui-contract.manifest.json` | Governs public recipes, CSS entry points, and Tailwind exports |
34
- | Source CSS | `src/styles/` | Token-backed CSS classes and bundle entry points |
35
- | Source recipes | `src/recipes/` | Framework-agnostic class string APIs |
36
- | Tailwind helpers | `src/tailwind/` | Tailwind theme and preset integration |
37
- | Generated dist | `dist/` | **Never edit directly** — regenerated by `npm run build` |
30
+ | Layer | Path | Rule |
31
+ | --------------------- | ----------------------------------------------- | -------------------------------------------------------------- |
32
+ | Token authority | Published `@phcdevworks/spectre-tokens` package | Design values and semantic meaning start there |
33
+ | UI contract authority | `ui-contract.manifest.json` | Governs public recipes, CSS entry points, and Tailwind exports |
34
+ | Source CSS | `src/styles/` | Token-backed CSS classes and bundle entry points |
35
+ | Source recipes | `src/recipes/` | Framework-agnostic class string APIs |
36
+ | Tailwind helpers | `src/tailwind/` | Tailwind theme and preset integration |
37
+ | Generated dist | `dist/` | **Never edit directly** — regenerated by `npm run build` |
38
38
 
39
39
  After any contract-facing source change: run `npm run check` to validate the
40
40
  full UI contract.
41
41
 
42
42
  ## Architecture
43
43
 
44
- | Layer | Package or consumer | Responsibility | Relationship to this package |
45
- |---|---|---|---|
46
- | 1 | `@phcdevworks/spectre-tokens` | Defines design values and semantic token meaning | Upstream source of truth |
47
- | 2 | `@phcdevworks/spectre-ui` | Translates tokens into CSS bundles, Tailwind helpers, and class recipes | This package |
48
- | 3 | Adapters and apps, such as `@phcdevworks/spectre-ui-astro` | Deliver Spectre through framework-native ergonomics | Downstream consumers |
44
+ | Layer | Package or consumer | Responsibility | Relationship to this package |
45
+ | ----- | ---------------------------------------------------------- | ----------------------------------------------------------------------- | ---------------------------- |
46
+ | 1 | `@phcdevworks/spectre-tokens` | Defines design values and semantic token meaning | Upstream source of truth |
47
+ | 2 | `@phcdevworks/spectre-ui` | Translates tokens into CSS bundles, Tailwind helpers, and class recipes | This package |
48
+ | 3 | Adapters and apps, such as `@phcdevworks/spectre-ui-astro` | Deliver Spectre through framework-native ergonomics | Downstream consumers |
49
49
 
50
50
  `@phcdevworks/spectre-components` is a separate component package that can wrap
51
51
  this styling contract in Lit web components. This package owns Layer 2 only: it
@@ -71,8 +71,8 @@ does not deliver components and it does not define tokens.
71
71
  - Tailwind preset and theme helpers in `src/tailwind/`
72
72
  - Contract validation that keeps CSS, recipes, exports, and docs aligned
73
73
 
74
- This package is the correct place to define reusable styling structure on top
75
- of Spectre tokens.
74
+ This package is the correct place to define reusable styling structure on top of
75
+ Spectre tokens.
76
76
 
77
77
  ## What this package does not own
78
78
 
@@ -100,7 +100,10 @@ No framework needed. Import the CSS and use the `sp-*` classes directly:
100
100
  <!doctype html>
101
101
  <html>
102
102
  <head>
103
- <link rel="stylesheet" href="node_modules/@phcdevworks/spectre-ui/dist/index.css" />
103
+ <link
104
+ rel="stylesheet"
105
+ href="node_modules/@phcdevworks/spectre-ui/dist/index.css"
106
+ />
104
107
  </head>
105
108
  <body>
106
109
  <button class="sp-btn sp-btn--primary sp-btn--md">Save</button>
@@ -197,17 +200,17 @@ Do not use `@phcdevworks/spectre-ui` when you need to:
197
200
 
198
201
  ## What belongs here vs elsewhere
199
202
 
200
- | What | Where it lives |
201
- |---|---|
202
- | Semantic color values, spacing scale, type scale | `@phcdevworks/spectre-tokens` |
203
- | Token-to-CSS variable mapping | **here** — `src/styles/` |
204
- | Precompiled CSS bundles | **here** — built to `dist/*.css` |
205
- | Class recipe functions (input → class string) | **here** — `src/recipes/` |
206
- | Tailwind preset and theme helpers | **here** — `src/tailwind/` |
207
- | Astro, React, Vue, Lit, Svelte components | Adapter packages (e.g. `spectre-ui-astro`) |
208
- | WordPress shortcodes or PHP templates | A WordPress adapter package |
209
- | App-level layout, routing, or data fetching | Consuming apps |
210
- | New design decisions (new colors, new spacing) | `@phcdevworks/spectre-tokens` |
203
+ | What | Where it lives |
204
+ | ------------------------------------------------ | ------------------------------------------ |
205
+ | Semantic color values, spacing scale, type scale | `@phcdevworks/spectre-tokens` |
206
+ | Token-to-CSS variable mapping | **here** — `src/styles/` |
207
+ | Precompiled CSS bundles | **here** — built to `dist/*.css` |
208
+ | Class recipe functions (input → class string) | **here** — `src/recipes/` |
209
+ | Tailwind preset and theme helpers | **here** — `src/tailwind/` |
210
+ | Astro, React, Vue, Lit, Svelte components | Adapter packages (e.g. `spectre-ui-astro`) |
211
+ | WordPress shortcodes or PHP templates | A WordPress adapter package |
212
+ | App-level layout, routing, or data fetching | Consuming apps |
213
+ | New design decisions (new colors, new spacing) | `@phcdevworks/spectre-tokens` |
211
214
 
212
215
  Golden rule: this package consumes tokens and exposes class contracts. It does
213
216
  not define tokens and it does not deliver framework components.
@@ -219,21 +222,21 @@ not define tokens and it does not deliver framework components.
219
222
  All recipe functions accept a plain options object and return a class string.
220
223
  All options are optional and fall back to sensible defaults.
221
224
 
222
- | Recipe | Function | Variants | Sizes | Common boolean flags |
223
- |---|---|---|---|---|
224
- | Button | `getButtonClasses` | `primary` `secondary` `ghost` `danger` `success` `cta` `accent` | `sm` `md` `lg` | `disabled` `loading` `fullWidth` `pill` `iconOnly` |
225
- | Badge | `getBadgeClasses` | `primary` `secondary` `success` `warning` `danger` `neutral` `info` `ghost` `accent` `cta` | `sm` `md` `lg` | `interactive` `disabled` `loading` `fullWidth` |
226
- | Card | `getCardClasses` | `elevated` `flat` `outline` `ghost` | — | `interactive` `padded` `fullHeight` `disabled` `loading` |
227
- | Input | `getInputClasses` | — | `sm` `md` `lg` | `disabled` `loading` `fullWidth` `pill` |
228
- | Input state | `getInputClasses` | `state`: `default` `error` `success` `disabled` `loading` | — | — |
229
- | IconBox | `getIconBoxClasses` | `primary` `secondary` `success` `warning` `danger` `info` `neutral` `ghost` `accent` `cta` | `sm` `md` `lg` | `interactive` `disabled` `loading` `pill` `fullWidth` |
230
- | PricingCard | `getPricingCardClasses` | — | — | `featured` `interactive` `disabled` `loading` `fullHeight` |
231
- | Rating | `getRatingClasses` | — | `sm` `md` `lg` | `interactive` `disabled` `loading` `pill` `fullWidth` |
232
- | Testimonial | `getTestimonialClasses` | `elevated` `flat` `outline` `ghost` | — | `interactive` `disabled` `loading` `fullHeight` |
233
- | Alert | `getAlertClasses` | `info` `success` `warning` `danger` `neutral` | `sm` `md` `lg` | `dismissed` |
234
- | Avatar | `getAvatarClasses` | — | `sm` `md` `lg` `xl` | shape: `circle` `square` |
235
- | Tag | `getTagClasses` | `default` `primary` `secondary` `success` `warning` `danger` `info` `neutral` `accent` `cta` `outline` `ghost` | `sm` `md` `lg` | `dismissible` `selected` `disabled` `loading` `interactive` `fullWidth` |
236
- | Spinner | `getSpinnerClasses` | — | `sm` `md` `lg` | — |
225
+ | Recipe | Function | Variants | Sizes | Common boolean flags |
226
+ | ----------- | ----------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------- | ----------------------------------------------------------------------- |
227
+ | Button | `getButtonClasses` | `primary` `secondary` `ghost` `danger` `success` `cta` `accent` | `sm` `md` `lg` | `disabled` `loading` `fullWidth` `pill` `iconOnly` |
228
+ | Badge | `getBadgeClasses` | `primary` `secondary` `success` `warning` `danger` `neutral` `info` `ghost` `accent` `cta` | `sm` `md` `lg` | `interactive` `disabled` `loading` `fullWidth` |
229
+ | Card | `getCardClasses` | `elevated` `flat` `outline` `ghost` | — | `interactive` `padded` `fullHeight` `disabled` `loading` |
230
+ | Input | `getInputClasses` | — | `sm` `md` `lg` | `disabled` `loading` `fullWidth` `pill` |
231
+ | Input state | `getInputClasses` | `state`: `default` `error` `success` `disabled` `loading` | — | — |
232
+ | IconBox | `getIconBoxClasses` | `primary` `secondary` `success` `warning` `danger` `info` `neutral` `ghost` `accent` `cta` | `sm` `md` `lg` | `interactive` `disabled` `loading` `pill` `fullWidth` |
233
+ | PricingCard | `getPricingCardClasses` | — | — | `featured` `interactive` `disabled` `loading` `fullHeight` |
234
+ | Rating | `getRatingClasses` | — | `sm` `md` `lg` | `interactive` `disabled` `loading` `pill` `fullWidth` |
235
+ | Testimonial | `getTestimonialClasses` | `elevated` `flat` `outline` `ghost` | — | `interactive` `disabled` `loading` `fullHeight` |
236
+ | Alert | `getAlertClasses` | `info` `success` `warning` `danger` `neutral` | `sm` `md` `lg` | `dismissed` |
237
+ | Avatar | `getAvatarClasses` | — | `sm` `md` `lg` `xl` | shape: `circle` `square` |
238
+ | Tag | `getTagClasses` | `default` `primary` `secondary` `success` `warning` `danger` `info` `neutral` `accent` `cta` `outline` `ghost` | `sm` `md` `lg` | `dismissible` `selected` `disabled` `loading` `interactive` `fullWidth` |
239
+ | Spinner | `getSpinnerClasses` | — | `sm` `md` `lg` | — |
237
240
 
238
241
  Each recipe family also exports sub-element helpers for its structural parts
239
242
  (labels, wrappers, sub-containers, text elements). See the full list below.
@@ -344,8 +347,8 @@ Practical guidance:
344
347
 
345
348
  - additive recipes, variants, states, and helpers are intended to be safe for
346
349
  existing consumers
347
- - semantic shifts may keep the same class or option name but still affect
348
- visual output
350
+ - semantic shifts may keep the same class or option name but still affect visual
351
+ output
349
352
  - renames, removals, and behavior changes to existing classes or options are
350
353
  breaking
351
354
  - generated JS, TypeScript declarations, CSS bundles, Tailwind exports, README
@@ -363,11 +366,11 @@ Tailwind helper:
363
366
  Contract-affecting changes should be classified in `CHANGELOG.md [Unreleased]`
364
367
  before release.
365
368
 
366
- | Classification | When to use | Examples |
367
- |---|---|---|
368
- | `additive` | New public styling surface that does not break existing consumers | Adding a recipe helper, variant, state, or CSS entry point |
369
- | `semantic change` | Public name remains but behavior or visual meaning shifts | Adjusting an existing class or recipe option to map to different token intent |
370
- | `breaking` | Existing consumers may need code changes | Renaming or removing a class, option, export, or CSS entry point |
369
+ | Classification | When to use | Examples |
370
+ | ----------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------- |
371
+ | `additive` | New public styling surface that does not break existing consumers | Adding a recipe helper, variant, state, or CSS entry point |
372
+ | `semantic change` | Public name remains but behavior or visual meaning shifts | Adjusting an existing class or recipe option to map to different token intent |
373
+ | `breaking` | Existing consumers may need code changes | Renaming or removing a class, option, export, or CSS entry point |
371
374
 
372
375
  Renames and removals are always breaking regardless of perceived scope.
373
376
 
@@ -413,28 +416,28 @@ npm install
413
416
  npm run ci:verify
414
417
  ```
415
418
 
416
- This project requires Node.js `^22.13.0 || >=24.0.0` and npm `>=10.0.0`.
417
- The checked-in package manager is `npm@11.15.0`.
419
+ This project requires Node.js `^22.13.0 || >=24.0.0` and npm `>=10.0.0`. The
420
+ checked-in package manager is `npm@11.16.0`.
418
421
 
419
422
  ### Common commands
420
423
 
421
- | Command | What it does |
422
- |---|---|
423
- | `npm run check` | Full validation gate — run before every PR |
424
- | `npm run ci:verify` | Underlying verification sequence used by `npm run check` |
425
- | `npm test` | Build then run the contract and regression test suite |
426
- | `npm run build` | Emit TypeScript and CSS artifacts to `dist/` |
427
- | `npm run lint` | ESLint with TypeScript-aware config |
428
- | `npm run validate:exports` | Verify root export surface against snapshot |
429
- | `npm run validate:exports:update` | Update the export snapshot after adding a public export |
430
- | `npm run validate:tailwind` | Verify Tailwind exports and emitted subpath artifacts |
431
- | `npm run validate:tailwind:update` | Update the Tailwind export snapshot |
432
- | `npm run validate:tokens` | Check for token drift against latest published release |
424
+ | Command | What it does |
425
+ | ---------------------------------- | -------------------------------------------------------- |
426
+ | `npm run check` | Full validation gate — run before every PR |
427
+ | `npm run ci:verify` | Underlying verification sequence used by `npm run check` |
428
+ | `npm test` | Build then run the contract and regression test suite |
429
+ | `npm run build` | Emit TypeScript and CSS artifacts to `dist/` |
430
+ | `npm run lint` | ESLint with TypeScript-aware config |
431
+ | `npm run validate:exports` | Verify root export surface against snapshot |
432
+ | `npm run validate:exports:update` | Update the export snapshot after adding a public export |
433
+ | `npm run validate:tailwind` | Verify Tailwind exports and emitted subpath artifacts |
434
+ | `npm run validate:tailwind:update` | Update the Tailwind export snapshot |
435
+ | `npm run validate:tokens` | Check for token drift against latest published release |
433
436
 
434
437
  ### Troubleshooting
435
438
 
436
- **`validate:runtime` fails** — you are on the wrong Node version. Run
437
- `nvm use` to switch to the version in `.nvmrc`, or install Node 22 or 24.
439
+ **`validate:runtime` fails** — you are on the wrong Node version. Run `nvm use`
440
+ to switch to the version in `.nvmrc`, or install Node 22 or 24.
438
441
 
439
442
  **`validate:tokens` fails with a network error** — the check requires outbound
440
443
  npm registry access. In a restricted environment, run the other validators
@@ -445,8 +448,8 @@ automatically via the `pretest` hook. If you ran `vitest` directly, run
445
448
  `npm run build` first.
446
449
 
447
450
  **Lint fails locally but passes in CI** — confirm you are on the same Node
448
- version as CI (Node 22.x or 24.x). ESLint plugin resolution can differ
449
- across runtimes.
451
+ version as CI (Node 22.x or 24.x). ESLint plugin resolution can differ across
452
+ runtimes.
450
453
 
451
454
  **Export snapshot out of date** — run `npm run validate:exports:update` after
452
455
  adding a public export, then commit the updated `scripts/export-snapshot.json`.
@@ -462,6 +465,13 @@ adding a public export, then commit the updated `scripts/export-snapshot.json`.
462
465
  Planning artifacts for contract hardening live in [ROADMAP.md](ROADMAP.md) and
463
466
  [TODO.md](TODO.md).
464
467
 
468
+ The current roadmap focus is post-v1.7.0 expansion: consume newly published
469
+ Spectre token contracts for links, interactive surfaces, dividers, and common
470
+ application UI patterns before adding matching Layer 2 classes or recipes.
471
+ Planned upstream token work is tracked in
472
+ [`@phcdevworks/spectre-tokens`](https://github.com/phcdevworks/spectre-tokens),
473
+ but this package synchronizes only against published npm releases.
474
+
465
475
  ## Examples
466
476
 
467
477
  Use [`examples/examples.html`](examples/examples.html) as the visual index for
@@ -481,16 +491,16 @@ Run the full validation gate before any pull request:
481
491
  npm run check
482
492
  ```
483
493
 
484
- This runs: runtime check → lint → export validation → README validation →
485
- token drift check → build → Tailwind contract → CSS contract → tests. All
486
- steps must pass.
494
+ This runs: runtime check → lint → changelog validation → export validation →
495
+ README validation → token drift check → build → Tailwind contract → CSS contract
496
+ → tests. All steps must pass.
487
497
 
488
498
  ## AI and automation boundaries
489
499
 
490
500
  Claude Code (`claude-sonnet-4-6`) is the primary development agent for this
491
501
  repository. Codex handles releases and production stabilization. Jules handles
492
- small automated fixes and token sync passes. GitHub Copilot provides
493
- development support.
502
+ small automated fixes and token sync passes. GitHub Copilot provides development
503
+ support.
494
504
 
495
505
  Claude Code, Codex, and Copilot do not create git commits by default. Jules may
496
506
  commit only bounded automated maintenance when the `JULES.md` scope and
package/dist/base.css CHANGED
@@ -116,17 +116,24 @@
116
116
  --sp-color-white: #ffffff;
117
117
  --sp-color-black: #000000;
118
118
  --sp-space-0: 0rem;
119
+ --sp-space-1: 0.0625rem;
120
+ --sp-space-2: 0.125rem;
119
121
  --sp-space-4: 0.25rem;
122
+ --sp-space-6: 0.375rem;
120
123
  --sp-space-8: 0.5rem;
124
+ --sp-space-10: 0.625rem;
121
125
  --sp-space-12: 0.75rem;
126
+ --sp-space-14: 0.875rem;
122
127
  --sp-space-16: 1rem;
123
128
  --sp-space-20: 1.25rem;
124
129
  --sp-space-24: 1.5rem;
130
+ --sp-space-28: 1.75rem;
125
131
  --sp-space-32: 2rem;
126
132
  --sp-space-40: 2.5rem;
127
133
  --sp-space-48: 3rem;
128
134
  --sp-space-56: 3.5rem;
129
135
  --sp-space-64: 4rem;
136
+ --sp-space-72: 4.5rem;
130
137
  --sp-space-80: 5rem;
131
138
  --sp-space-96: 6rem;
132
139
  --sp-layout-section-padding-sm: 1.5rem;
@@ -142,16 +149,21 @@
142
149
  --sp-layout-container-padding-inline-md: 1.5rem;
143
150
  --sp-layout-container-padding-inline-lg: 2rem;
144
151
  --sp-layout-container-max-width: 72rem;
152
+ --sp-border-width-none: 0;
145
153
  --sp-border-width-base: 1px;
146
154
  --sp-border-width-thick: 2px;
147
155
  --sp-border-style-none: none;
148
156
  --sp-border-style-solid: solid;
157
+ --sp-border-style-dashed: dashed;
158
+ --sp-border-style-dotted: dotted;
149
159
  --sp-radius-none: 0;
150
160
  --sp-radius-sm: 2px;
151
161
  --sp-radius-md: 4px;
152
162
  --sp-radius-lg: 8px;
153
163
  --sp-radius-xl: 12px;
154
164
  --sp-radius-2xl: 16px;
165
+ --sp-radius-3xl: 24px;
166
+ --sp-radius-4xl: 32px;
155
167
  --sp-radius-pill: 999px;
156
168
  --sp-font-family-sans: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
157
169
  --sp-font-family-serif: 'Times New Roman', Times, serif;
@@ -276,6 +288,7 @@
276
288
  --sp-button-danger-text: #ffffff;
277
289
  --sp-button-danger-textdisabled: #8a96ad;
278
290
  --sp-button-danger-focusring: rgba(239, 68, 68, 0.4);
291
+ --sp-button-danger-focusvisible: rgba(239, 68, 68, 0.4);
279
292
  --sp-button-success-bg: #15803d;
280
293
  --sp-button-success-bghover: #166534;
281
294
  --sp-button-success-bgactive: #14532d;
@@ -283,6 +296,7 @@
283
296
  --sp-button-success-text: #ffffff;
284
297
  --sp-button-success-textdisabled: #8a96ad;
285
298
  --sp-button-success-focusring: rgba(34, 197, 94, 0.4);
299
+ --sp-button-success-focusvisible: rgba(34, 197, 94, 0.4);
286
300
  --sp-button-cta-bg: #1f57db;
287
301
  --sp-button-cta-bghover: #1946b4;
288
302
  --sp-button-cta-bgactive: #173b8f;
@@ -116,17 +116,24 @@
116
116
  --sp-color-white: #ffffff;
117
117
  --sp-color-black: #000000;
118
118
  --sp-space-0: 0rem;
119
+ --sp-space-1: 0.0625rem;
120
+ --sp-space-2: 0.125rem;
119
121
  --sp-space-4: 0.25rem;
122
+ --sp-space-6: 0.375rem;
120
123
  --sp-space-8: 0.5rem;
124
+ --sp-space-10: 0.625rem;
121
125
  --sp-space-12: 0.75rem;
126
+ --sp-space-14: 0.875rem;
122
127
  --sp-space-16: 1rem;
123
128
  --sp-space-20: 1.25rem;
124
129
  --sp-space-24: 1.5rem;
130
+ --sp-space-28: 1.75rem;
125
131
  --sp-space-32: 2rem;
126
132
  --sp-space-40: 2.5rem;
127
133
  --sp-space-48: 3rem;
128
134
  --sp-space-56: 3.5rem;
129
135
  --sp-space-64: 4rem;
136
+ --sp-space-72: 4.5rem;
130
137
  --sp-space-80: 5rem;
131
138
  --sp-space-96: 6rem;
132
139
  --sp-layout-section-padding-sm: 1.5rem;
@@ -142,16 +149,21 @@
142
149
  --sp-layout-container-padding-inline-md: 1.5rem;
143
150
  --sp-layout-container-padding-inline-lg: 2rem;
144
151
  --sp-layout-container-max-width: 72rem;
152
+ --sp-border-width-none: 0;
145
153
  --sp-border-width-base: 1px;
146
154
  --sp-border-width-thick: 2px;
147
155
  --sp-border-style-none: none;
148
156
  --sp-border-style-solid: solid;
157
+ --sp-border-style-dashed: dashed;
158
+ --sp-border-style-dotted: dotted;
149
159
  --sp-radius-none: 0;
150
160
  --sp-radius-sm: 2px;
151
161
  --sp-radius-md: 4px;
152
162
  --sp-radius-lg: 8px;
153
163
  --sp-radius-xl: 12px;
154
164
  --sp-radius-2xl: 16px;
165
+ --sp-radius-3xl: 24px;
166
+ --sp-radius-4xl: 32px;
155
167
  --sp-radius-pill: 999px;
156
168
  --sp-font-family-sans: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
157
169
  --sp-font-family-serif: 'Times New Roman', Times, serif;
@@ -276,6 +288,7 @@
276
288
  --sp-button-danger-text: #ffffff;
277
289
  --sp-button-danger-textdisabled: #8a96ad;
278
290
  --sp-button-danger-focusring: rgba(239, 68, 68, 0.4);
291
+ --sp-button-danger-focusvisible: rgba(239, 68, 68, 0.4);
279
292
  --sp-button-success-bg: #15803d;
280
293
  --sp-button-success-bghover: #166534;
281
294
  --sp-button-success-bgactive: #14532d;
@@ -283,6 +296,7 @@
283
296
  --sp-button-success-text: #ffffff;
284
297
  --sp-button-success-textdisabled: #8a96ad;
285
298
  --sp-button-success-focusring: rgba(34, 197, 94, 0.4);
299
+ --sp-button-success-focusvisible: rgba(34, 197, 94, 0.4);
286
300
  --sp-button-cta-bg: #1f57db;
287
301
  --sp-button-cta-bghover: #1946b4;
288
302
  --sp-button-cta-bgactive: #173b8f;
@@ -417,12 +431,14 @@
417
431
  --sp-component-button-danger-bg-disabled: var(--sp-button-danger-bgdisabled);
418
432
  --sp-component-button-danger-text: var(--sp-button-danger-text);
419
433
  --sp-component-button-danger-text-disabled: var(--sp-button-danger-textdisabled);
434
+ --sp-component-button-danger-focus-visible: var(--sp-button-danger-focusvisible);
420
435
  --sp-component-button-success-bg: var(--sp-button-success-bg);
421
436
  --sp-component-button-success-bg-hover: var(--sp-button-success-bghover);
422
437
  --sp-component-button-success-bg-active: var(--sp-button-success-bgactive);
423
438
  --sp-component-button-success-bg-disabled: var(--sp-button-success-bgdisabled);
424
439
  --sp-component-button-success-text: var(--sp-button-success-text);
425
440
  --sp-component-button-success-text-disabled: var(--sp-button-success-textdisabled);
441
+ --sp-component-button-success-focus-visible: var(--sp-button-success-focusvisible);
426
442
  /* CTA button alignment restored to official tokens as they now use the brand palette (Blue). */
427
443
  --sp-component-button-cta-bg: var(--sp-button-cta-bg);
428
444
  --sp-component-button-cta-bg-hover: var(--sp-button-cta-bghover);
@@ -493,6 +509,9 @@
493
509
  --sp-component-badge-info-text: var(--sp-badge-info-text);
494
510
  --sp-component-badge-ghost-bg: var(--sp-button-ghost-bg);
495
511
  --sp-component-badge-ghost-text: var(--sp-button-ghost-text);
512
+ --sp-component-badge-outline-bg: transparent;
513
+ --sp-component-badge-outline-text: var(--sp-badge-neutral-text);
514
+ --sp-component-badge-outline-border: var(--sp-button-secondary-border);
496
515
  --sp-component-badge-accent-bg: var(--sp-button-accent-bg);
497
516
  --sp-component-badge-accent-text: var(--sp-button-accent-text);
498
517
  --sp-component-badge-cta-bg: var(--sp-button-cta-bg);
@@ -543,6 +562,9 @@
543
562
  --sp-component-iconbox-accent-text: var(--sp-button-accent-text);
544
563
  --sp-component-iconbox-cta-bg: var(--sp-button-cta-bg);
545
564
  --sp-component-iconbox-cta-text: var(--sp-button-cta-text);
565
+ --sp-component-iconbox-outline-bg: transparent;
566
+ --sp-component-iconbox-outline-text: var(--sp-badge-neutral-text);
567
+ --sp-component-iconbox-outline-border: var(--sp-button-secondary-border);
546
568
 
547
569
  /* testimonial roles */
548
570
  --sp-component-testimonial-bg: var(--sp-surface-card);
@@ -664,6 +686,15 @@
664
686
  --sp-component-spinner-border-width: var(--sp-border-width-thick);
665
687
  --sp-component-spinner-track-color: var(--sp-color-neutral-200);
666
688
  --sp-component-spinner-arc-color: var(--sp-button-primary-bg);
689
+ --sp-component-spinner-primary-arc: var(--sp-button-primary-bg);
690
+ --sp-component-spinner-secondary-arc: var(--sp-button-secondary-border);
691
+ --sp-component-spinner-success-arc: var(--sp-color-success-500);
692
+ --sp-component-spinner-warning-arc: var(--sp-color-warning-500);
693
+ --sp-component-spinner-danger-arc: var(--sp-color-error-500);
694
+ --sp-component-spinner-info-arc: var(--sp-color-info-500);
695
+ --sp-component-spinner-neutral-arc: var(--sp-color-neutral-500);
696
+ --sp-component-spinner-accent-arc: var(--sp-button-accent-bg);
697
+ --sp-component-spinner-cta-arc: var(--sp-button-cta-bg);
667
698
  --sp-component-spinner-duration: var(--sp-duration-long);
668
699
  --sp-component-spinner-easing: var(--sp-easing-linear);
669
700
  }
@@ -724,6 +755,15 @@
724
755
 
725
756
  --sp-component-spinner-track-color: var(--sp-color-neutral-700);
726
757
  --sp-component-spinner-arc-color: var(--sp-color-brand-400);
758
+ --sp-component-spinner-primary-arc: var(--sp-color-brand-400);
759
+ --sp-component-spinner-secondary-arc: var(--sp-color-neutral-400);
760
+ --sp-component-spinner-success-arc: var(--sp-color-success-400);
761
+ --sp-component-spinner-warning-arc: var(--sp-color-warning-400);
762
+ --sp-component-spinner-danger-arc: var(--sp-color-error-400);
763
+ --sp-component-spinner-info-arc: var(--sp-color-info-400);
764
+ --sp-component-spinner-neutral-arc: var(--sp-color-neutral-400);
765
+ --sp-component-spinner-accent-arc: var(--sp-color-brand-400);
766
+ --sp-component-spinner-cta-arc: var(--sp-color-brand-400);
727
767
  }
728
768
 
729
769
  /* BUTTONS -------------------------------------------------------------- */
@@ -910,6 +950,12 @@
910
950
  color: var(--sp-component-button-danger-text-disabled);
911
951
  }
912
952
 
953
+ .sp-btn--danger:focus-visible,
954
+ .sp-btn--danger.sp-btn--focus,
955
+ .sp-btn--danger.is-focus {
956
+ box-shadow: 0 0 0 calc(var(--sp-focus-ring-width) + var(--sp-component-border-width)) var(--sp-component-button-danger-focus-visible);
957
+ }
958
+
913
959
  /* success */
914
960
  .sp-btn--success {
915
961
  background-color: var(--sp-component-button-success-bg);
@@ -935,6 +981,12 @@
935
981
  color: var(--sp-component-button-success-text-disabled);
936
982
  }
937
983
 
984
+ .sp-btn--success:focus-visible,
985
+ .sp-btn--success.sp-btn--focus,
986
+ .sp-btn--success.is-focus {
987
+ box-shadow: 0 0 0 calc(var(--sp-focus-ring-width) + var(--sp-component-border-width)) var(--sp-component-button-success-focus-visible);
988
+ }
989
+
938
990
  /* cta */
939
991
  .sp-btn--cta {
940
992
  background-color: var(--sp-component-button-cta-bg);
@@ -1351,6 +1403,12 @@
1351
1403
  background-color: var(--sp-component-badge-ghost-bg-hover);
1352
1404
  }
1353
1405
 
1406
+ .sp-badge--outline {
1407
+ background-color: var(--sp-component-badge-outline-bg);
1408
+ color: var(--sp-component-badge-outline-text);
1409
+ border-color: var(--sp-component-badge-outline-border);
1410
+ }
1411
+
1354
1412
  .sp-badge--accent {
1355
1413
  background-color: var(--sp-component-badge-accent-bg);
1356
1414
  color: var(--sp-component-badge-accent-text);
@@ -1548,6 +1606,12 @@
1548
1606
  color: var(--sp-component-iconbox-cta-text);
1549
1607
  }
1550
1608
 
1609
+ .sp-iconbox--outline {
1610
+ background-color: var(--sp-component-iconbox-outline-bg);
1611
+ color: var(--sp-component-iconbox-outline-text);
1612
+ border-color: var(--sp-component-iconbox-outline-border);
1613
+ }
1614
+
1551
1615
  .sp-iconbox--interactive {
1552
1616
  cursor: pointer;
1553
1617
  }
@@ -2316,6 +2380,52 @@
2316
2380
  border: var(--sp-component-spinner-border-width) solid var(--sp-component-spinner-track-color);
2317
2381
  border-top-color: var(--sp-component-spinner-arc-color);
2318
2382
  animation: sp-spin var(--sp-component-spinner-duration) var(--sp-component-spinner-easing) infinite;
2383
+ transition: opacity var(--sp-duration-fast) var(--sp-easing-out);
2384
+ }
2385
+
2386
+ .sp-spinner--primary {
2387
+ border-top-color: var(--sp-component-spinner-primary-arc);
2388
+ }
2389
+
2390
+ .sp-spinner--secondary {
2391
+ border-top-color: var(--sp-component-spinner-secondary-arc);
2392
+ }
2393
+
2394
+ .sp-spinner--success {
2395
+ border-top-color: var(--sp-component-spinner-success-arc);
2396
+ }
2397
+
2398
+ .sp-spinner--warning {
2399
+ border-top-color: var(--sp-component-spinner-warning-arc);
2400
+ }
2401
+
2402
+ .sp-spinner--danger {
2403
+ border-top-color: var(--sp-component-spinner-danger-arc);
2404
+ }
2405
+
2406
+ .sp-spinner--info {
2407
+ border-top-color: var(--sp-component-spinner-info-arc);
2408
+ }
2409
+
2410
+ .sp-spinner--neutral {
2411
+ border-top-color: var(--sp-component-spinner-neutral-arc);
2412
+ }
2413
+
2414
+ .sp-spinner--accent {
2415
+ border-top-color: var(--sp-component-spinner-accent-arc);
2416
+ }
2417
+
2418
+ .sp-spinner--cta {
2419
+ border-top-color: var(--sp-component-spinner-cta-arc);
2420
+ }
2421
+
2422
+ .sp-spinner--disabled {
2423
+ opacity: var(--sp-opacity-disabled);
2424
+ }
2425
+
2426
+ .sp-spinner--loading,
2427
+ .sp-spinner[aria-busy="true"] {
2428
+ opacity: var(--sp-opacity-active);
2319
2429
  }
2320
2430
 
2321
2431
  .sp-spinner--sm {
package/dist/index.cjs CHANGED
@@ -255,6 +255,7 @@ var BADGE_VARIANTS = {
255
255
  neutral: true,
256
256
  info: true,
257
257
  ghost: true,
258
+ outline: true,
258
259
  accent: true,
259
260
  cta: true
260
261
  };
@@ -296,6 +297,7 @@ function getBadgeClasses(opts = {}) {
296
297
  neutral: "sp-badge--neutral",
297
298
  info: "sp-badge--info",
298
299
  ghost: "sp-badge--ghost",
300
+ outline: "sp-badge--outline",
299
301
  accent: "sp-badge--accent",
300
302
  cta: "sp-badge--cta"
301
303
  };
@@ -331,7 +333,8 @@ var ICONBOX_VARIANTS = {
331
333
  neutral: true,
332
334
  ghost: true,
333
335
  accent: true,
334
- cta: true
336
+ cta: true,
337
+ outline: true
335
338
  };
336
339
  var ICONBOX_SIZES = {
337
340
  sm: true,
@@ -373,7 +376,8 @@ function getIconBoxClasses(opts = {}) {
373
376
  neutral: "sp-iconbox--neutral",
374
377
  ghost: "sp-iconbox--ghost",
375
378
  accent: "sp-iconbox--accent",
376
- cta: "sp-iconbox--cta"
379
+ cta: "sp-iconbox--cta",
380
+ outline: "sp-iconbox--outline"
377
381
  };
378
382
  const variantClass = variantMap[variant];
379
383
  const sizeMap = {
@@ -709,20 +713,48 @@ function getTagClasses(opts = {}) {
709
713
  }
710
714
 
711
715
  // src/recipes/spinner.ts
716
+ var SPINNER_VARIANTS = {
717
+ primary: true,
718
+ secondary: true,
719
+ success: true,
720
+ warning: true,
721
+ danger: true,
722
+ info: true,
723
+ neutral: true,
724
+ accent: true,
725
+ cta: true
726
+ };
712
727
  var SPINNER_SIZES = {
713
728
  sm: true,
714
729
  md: true,
715
730
  lg: true
716
731
  };
717
732
  function getSpinnerClasses(opts = {}) {
718
- const { size: sizeInput } = opts;
733
+ const {
734
+ variant: variantInput,
735
+ size: sizeInput,
736
+ disabled = false,
737
+ loading = false
738
+ } = opts;
739
+ const variant = variantInput && resolveOption({
740
+ name: "spinner variant",
741
+ value: variantInput,
742
+ allowed: SPINNER_VARIANTS,
743
+ fallback: "primary"
744
+ });
719
745
  const size = resolveOption({
720
746
  name: "spinner size",
721
747
  value: sizeInput,
722
748
  allowed: SPINNER_SIZES,
723
749
  fallback: "md"
724
750
  });
725
- return cx("sp-spinner", `sp-spinner--${size}`);
751
+ return cx(
752
+ "sp-spinner",
753
+ variant && `sp-spinner--${variant}`,
754
+ `sp-spinner--${size}`,
755
+ disabled && "sp-spinner--disabled",
756
+ loading && "sp-spinner--loading"
757
+ );
726
758
  }
727
759
 
728
760
  exports.getAlertClasses = getAlertClasses;