@phcdevworks/spectre-ui 2.2.0 → 2.4.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,62 @@ reflects package releases published to npm.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.4.0] - 2026-06-23
10
+
11
+ Release Title: App Shell Hardening
12
+
13
+ Contract change type: additive
14
+
15
+ ### Added
16
+
17
+ - **Stack `align` option**: Added an `align` option (`center` `stretch`) to
18
+ `getStackClasses`, mapping to `align-items` via the new
19
+ `.sp-stack--align-stretch` modifier class. Defaults to `center` to preserve
20
+ `.sp-hstack`'s existing hardcoded behavior. Fixes a downstream gap where
21
+ `.sp-hstack` could not stretch a docked `SpSidebar` to match a taller main
22
+ content column.
23
+ - **Sidebar toggle recipe**: Added `getSidebarToggleClasses`, wrapping a new
24
+ `.sp-sidebar-toggle` component class with an explicit
25
+ `--sp-component-sidebar-toggle-z-index` (`--sp-z-index-modal`) above
26
+ `--sp-component-sidebar-backdrop-z-index`, so a consumer-rendered toggle
27
+ button stays clickable above the backdrop once the sidebar is open.
28
+
29
+ ## [2.3.0] - 2026-06-19
30
+
31
+ Release Title: App Shell Recipe Expansion
32
+
33
+ Contract change type: additive
34
+
35
+ ### Added
36
+
37
+ - **Stack `basis` option**: Added a `basis` option (`sidebar`) to
38
+ `getStackClasses`, mapping a flex child to a fixed width via the new
39
+ `--sp-layout-sidebar-width` token (`@phcdevworks/spectre-tokens@3.1.0`),
40
+ distinct from the default `flex: 1` auto-sizing behavior.
41
+ - **Container `maxWidth` option**: Added a `maxWidth` option (`prose`) to
42
+ `getContainerClasses`, mapping to the new
43
+ `--sp-layout-container-max-width-prose` token, distinct from the existing
44
+ page-level `--sp-layout-container-max-width`.
45
+ - **Sidebar recipe**: Added `getSidebarClasses`, `getSidebarLinkClasses`, and
46
+ `getSidebarBackdropClasses`, wrapping new `.sp-sidebar` / `.sp-sidebar__link`
47
+ / `.sp-sidebar-backdrop` component classes in `src/styles/components.css`.
48
+ Reuses the existing `component.nav` token roles (bg/text/link/border) as
49
+ the vertical counterpart to `SpNav`'s top-bar pattern; sidebar width comes
50
+ from the same `--sp-layout-sidebar-width` token used by Stack's `basis`
51
+ option. Below `breakpoints.md`, the sidebar is an off-canvas drawer
52
+ (`transform: translateX(-100%)`) with a backdrop, toggled via a
53
+ `data-sidebar-open="true"` attribute contract — this is the first recipe
54
+ family with an interactive-state CSS contract. This package owns the CSS
55
+ reaction only; toggle behavior, click handling, and state management
56
+ belong to the consuming adapter.
57
+ - **Footer recipe**: Added `getFooterClasses`, wrapping a new `.sp-footer`
58
+ component class in `src/styles/components.css`, modeled on `SpNav`'s
59
+ `bordered`/`fullWidth` option shape (no `sticky`, per the deferred-unless-
60
+ needed decision in `TODO.md`).
61
+
62
+ This is Phase 4d in `TODO.md` — real downstream need surfaced in
63
+ `docs-phcdevworks-com`'s app shell (top bar + sidebar + main content).
64
+
9
65
  ## [2.2.0] - 2026-06-18
10
66
 
11
67
  Release Title: Grid Recipe Expansion
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  | Project team | `project-design` |
8
8
  | Repository role | Spectre L2 CSS, Tailwind, and recipe contract |
9
9
  | Package/artifact | `@phcdevworks/spectre-ui` |
10
- | Current version/status | 2.0.0 |
10
+ | Current version/status | 2.4.0 |
11
11
 
12
12
  ## Standard Workflow
13
13
 
@@ -274,10 +274,12 @@ All options are optional and fall back to sensible defaults.
274
274
  | Tooltip | `getTooltipClasses` | placement: `top` `bottom` `left` `right` | — | `visible` |
275
275
  | Dropdown | `getDropdownClasses` | menu placement: `bottom-start` `bottom-end` `top-start` `top-end` | — | `fullWidth`, item: `active` `disabled` |
276
276
  | Modal | `getModalClasses` | — | — | `open` `fullWidth` |
277
- | Container | `getContainerClasses` | | — | — |
278
- | Stack | `getStackClasses` | direction: `vertical` `horizontal` | — | — |
277
+ | Container | `getContainerClasses` | maxWidth: `prose` | — | — |
278
+ | Stack | `getStackClasses` | direction: `vertical` `horizontal`, basis: `sidebar`, align: `center` `stretch` | — | — |
279
279
  | Section | `getSectionClasses` | — | — | — |
280
280
  | Grid | `getGridClasses` | columns: `1` `2` `3` `4` `6` `12` | gap: `sm` `md` `lg` | — |
281
+ | Sidebar | `getSidebarClasses` | — | — | `bordered` |
282
+ | Footer | `getFooterClasses` | — | — | `bordered` `fullWidth` |
281
283
 
282
284
  Each recipe family also exports sub-element helpers for its structural parts
283
285
  (labels, wrappers, sub-containers, text elements). See the full list below.
@@ -304,6 +306,7 @@ Root recipe functions:
304
306
  - `getCardClasses`
305
307
  - `getContainerClasses`
306
308
  - `getDropdownClasses`
309
+ - `getFooterClasses`
307
310
  - `getGridClasses`
308
311
  - `getIconBoxClasses`
309
312
  - `getInputClasses`
@@ -312,6 +315,7 @@ Root recipe functions:
312
315
  - `getPricingCardClasses`
313
316
  - `getRatingClasses`
314
317
  - `getSectionClasses`
318
+ - `getSidebarClasses`
315
319
  - `getSpinnerClasses`
316
320
  - `getStackClasses`
317
321
  - `getTagClasses`
@@ -337,6 +341,9 @@ Root recipe helper functions:
337
341
  - `getRatingStarClasses`
338
342
  - `getRatingStarsClasses`
339
343
  - `getRatingTextClasses`
344
+ - `getSidebarBackdropClasses`
345
+ - `getSidebarLinkClasses`
346
+ - `getSidebarToggleClasses`
340
347
  - `getTestimonialAuthorClasses`
341
348
  - `getTestimonialAuthorInfoClasses`
342
349
  - `getTestimonialAuthorNameClasses`
@@ -378,6 +385,34 @@ README documentation omits manifest-declared exports, when export snapshots
378
385
  drift, when Tailwind artifacts drift, or when CSS contract coverage no longer
379
386
  matches the declared surface.
380
387
 
388
+ ### Sidebar interactive-state contract
389
+
390
+ `getSidebarClasses` is the first recipe family with an interactive-state CSS
391
+ contract. Below `breakpoints.md` (768px), `.sp-sidebar` renders off-canvas
392
+ (`transform: translateX(-100%)`). This package owns only the CSS reaction to
393
+ that state — it does not own toggle behavior, click handlers, or open/closed
394
+ state management.
395
+
396
+ Consumers (typically a framework adapter) toggle the sidebar by setting a
397
+ `data-sidebar-open="true"` attribute on an ancestor element wrapping
398
+ `.sp-sidebar` and `.sp-sidebar-backdrop` (from `getSidebarBackdropClasses`):
399
+
400
+ - `[data-sidebar-open="true"] .sp-sidebar` slides the sidebar into view
401
+ (`transform: translateX(0)`).
402
+ - `[data-sidebar-open="true"] .sp-sidebar-backdrop` shows the backdrop
403
+ overlay (`display: block`).
404
+ - Above `breakpoints.md`, the sidebar docks inline and the backdrop is
405
+ always hidden, regardless of the `data-sidebar-open` value.
406
+
407
+ A consumer-rendered toggle button that opens/closes the sidebar must carry
408
+ `getSidebarToggleClasses()`. `.sp-sidebar-toggle` stacks above
409
+ `.sp-sidebar-backdrop` (`--sp-component-sidebar-toggle-z-index`, above
410
+ `--sp-component-sidebar-backdrop-z-index`) so the backdrop never intercepts
411
+ clicks meant for the toggle once the sidebar is open.
412
+
413
+ Adapters own the hamburger/toggle control, click handling, and SSR-safe
414
+ initial closed state.
415
+
381
416
  ## Downstream boundaries
382
417
 
383
418
  Downstream packages should never redefine locally:
@@ -473,7 +508,7 @@ npm run ci:verify
473
508
  ```
474
509
 
475
510
  This project requires Node.js `^22.13.0 || >=24.0.0` and npm `>=10.0.0`. The
476
- checked-in package manager is `npm@11.16.0`.
511
+ checked-in package manager is `npm@11.17.0`.
477
512
 
478
513
  ### Common commands
479
514
 
package/dist/base.css CHANGED
@@ -5,6 +5,10 @@
5
5
  --sp-surface-overlay: rgba(0, 0, 0, 0.6);
6
6
  --sp-surface-subtle: #eef1f6;
7
7
  --sp-surface-hero: linear-gradient(135deg, #5b6ee1 0%, #6f3fd7 100%);
8
+ --sp-surface-hover: #eef1f6;
9
+ --sp-surface-selected: #f0f9ff;
10
+ --sp-surface-active: #d9dfeb;
11
+ --sp-surface-divider: #d9dfeb;
8
12
  --sp-text-on-page-default: #141b24;
9
13
  --sp-text-on-page-muted: #4b576a;
10
14
  --sp-text-on-page-subtle: #657287;
@@ -72,6 +76,10 @@
72
76
  --sp-dropdown-item-hover: #eef1f6;
73
77
  --sp-dropdown-item-active: #f0f9ff;
74
78
  --sp-dropdown-item-text: #141b24;
79
+ --sp-link-default: #1f57db;
80
+ --sp-link-hover: #1946b4;
81
+ --sp-link-active: #173b8f;
82
+ --sp-link-visited: #5d28b8;
75
83
  --sp-color-brand-50: #eef4ff;
76
84
  --sp-color-brand-100: #d9e7ff;
77
85
  --sp-color-brand-200: #b9d2ff;
@@ -184,6 +192,8 @@
184
192
  --sp-layout-container-padding-inline-md: 1.5rem;
185
193
  --sp-layout-container-padding-inline-lg: 2rem;
186
194
  --sp-layout-container-max-width: 72rem;
195
+ --sp-layout-container-max-width-prose: 65ch;
196
+ --sp-layout-sidebar-width: 16rem;
187
197
  --sp-border-width-none: 0;
188
198
  --sp-border-width-base: 1px;
189
199
  --sp-border-width-thick: 2px;
@@ -424,6 +434,10 @@
424
434
  --sp-surface-overlay: rgba(0, 0, 0, 0.6);
425
435
  --sp-surface-subtle: #222b38;
426
436
  --sp-surface-hero: linear-gradient(135deg, #5d28b8 0%, #401f75 100%);
437
+ --sp-surface-hover: #374253;
438
+ --sp-surface-selected: #082f49;
439
+ --sp-surface-active: #4b576a;
440
+ --sp-surface-divider: #374253;
427
441
  --sp-text-on-page-default: #f7f8fb;
428
442
  --sp-text-on-page-muted: #b7c1d4;
429
443
  --sp-text-on-page-subtle: #8a96ad;
@@ -491,6 +505,10 @@
491
505
  --sp-dropdown-item-hover: #374253;
492
506
  --sp-dropdown-item-active: #082f49;
493
507
  --sp-dropdown-item-text: #eef1f6;
508
+ --sp-link-default: #1f57db;
509
+ --sp-link-hover: #1946b4;
510
+ --sp-link-active: #173b8f;
511
+ --sp-link-visited: #5d28b8;
494
512
  }
495
513
  @layer base {
496
514
 
@@ -5,6 +5,10 @@
5
5
  --sp-surface-overlay: rgba(0, 0, 0, 0.6);
6
6
  --sp-surface-subtle: #eef1f6;
7
7
  --sp-surface-hero: linear-gradient(135deg, #5b6ee1 0%, #6f3fd7 100%);
8
+ --sp-surface-hover: #eef1f6;
9
+ --sp-surface-selected: #f0f9ff;
10
+ --sp-surface-active: #d9dfeb;
11
+ --sp-surface-divider: #d9dfeb;
8
12
  --sp-text-on-page-default: #141b24;
9
13
  --sp-text-on-page-muted: #4b576a;
10
14
  --sp-text-on-page-subtle: #657287;
@@ -72,6 +76,10 @@
72
76
  --sp-dropdown-item-hover: #eef1f6;
73
77
  --sp-dropdown-item-active: #f0f9ff;
74
78
  --sp-dropdown-item-text: #141b24;
79
+ --sp-link-default: #1f57db;
80
+ --sp-link-hover: #1946b4;
81
+ --sp-link-active: #173b8f;
82
+ --sp-link-visited: #5d28b8;
75
83
  --sp-color-brand-50: #eef4ff;
76
84
  --sp-color-brand-100: #d9e7ff;
77
85
  --sp-color-brand-200: #b9d2ff;
@@ -184,6 +192,8 @@
184
192
  --sp-layout-container-padding-inline-md: 1.5rem;
185
193
  --sp-layout-container-padding-inline-lg: 2rem;
186
194
  --sp-layout-container-max-width: 72rem;
195
+ --sp-layout-container-max-width-prose: 65ch;
196
+ --sp-layout-sidebar-width: 16rem;
187
197
  --sp-border-width-none: 0;
188
198
  --sp-border-width-base: 1px;
189
199
  --sp-border-width-thick: 2px;
@@ -424,6 +434,10 @@
424
434
  --sp-surface-overlay: rgba(0, 0, 0, 0.6);
425
435
  --sp-surface-subtle: #222b38;
426
436
  --sp-surface-hero: linear-gradient(135deg, #5d28b8 0%, #401f75 100%);
437
+ --sp-surface-hover: #374253;
438
+ --sp-surface-selected: #082f49;
439
+ --sp-surface-active: #4b576a;
440
+ --sp-surface-divider: #374253;
427
441
  --sp-text-on-page-default: #f7f8fb;
428
442
  --sp-text-on-page-muted: #b7c1d4;
429
443
  --sp-text-on-page-subtle: #8a96ad;
@@ -491,6 +505,10 @@
491
505
  --sp-dropdown-item-hover: #374253;
492
506
  --sp-dropdown-item-active: #082f49;
493
507
  --sp-dropdown-item-text: #eef1f6;
508
+ --sp-link-default: #1f57db;
509
+ --sp-link-hover: #1946b4;
510
+ --sp-link-active: #173b8f;
511
+ --sp-link-visited: #5d28b8;
494
512
  }
495
513
  @layer components {
496
514
 
@@ -809,6 +827,24 @@
809
827
  --sp-component-nav-link-active: var(--sp-nav-link-active);
810
828
  --sp-component-nav-border: var(--sp-nav-border);
811
829
 
830
+ /* sidebar roles (reuses nav roles - vertical counterpart to the top-bar nav) */
831
+ --sp-component-sidebar-bg: var(--sp-nav-bg);
832
+ --sp-component-sidebar-text: var(--sp-nav-text);
833
+ --sp-component-sidebar-link: var(--sp-nav-link);
834
+ --sp-component-sidebar-link-hover: var(--sp-nav-link-hover);
835
+ --sp-component-sidebar-link-active: var(--sp-nav-link-active);
836
+ --sp-component-sidebar-border: var(--sp-nav-border);
837
+ --sp-component-sidebar-width: var(--sp-layout-sidebar-width);
838
+ --sp-component-sidebar-z-index: var(--sp-z-index-fixed);
839
+ --sp-component-sidebar-backdrop: var(--sp-surface-overlay);
840
+ --sp-component-sidebar-backdrop-z-index: var(--sp-z-index-overlay);
841
+ --sp-component-sidebar-toggle-z-index: var(--sp-z-index-modal);
842
+
843
+ /* footer roles (reuses nav roles - bottom-bar counterpart to the top-bar nav) */
844
+ --sp-component-footer-bg: var(--sp-nav-bg);
845
+ --sp-component-footer-text: var(--sp-nav-text);
846
+ --sp-component-footer-border: var(--sp-nav-border);
847
+
812
848
  /* toast roles */
813
849
  --sp-component-toast-radius: var(--sp-radius-lg);
814
850
  --sp-component-toast-padding-x: var(--sp-space-16);
@@ -2722,6 +2758,125 @@
2722
2758
  opacity: var(--sp-opacity-disabled);
2723
2759
  }
2724
2760
 
2761
+ /* SIDEBAR -------------------------------------------------------------- */
2762
+ .sp-sidebar {
2763
+ display: flex;
2764
+ flex-direction: column;
2765
+ gap: var(--sp-space-16);
2766
+ width: var(--sp-component-sidebar-width);
2767
+ padding: var(--sp-space-16);
2768
+ background-color: var(--sp-component-sidebar-bg);
2769
+ color: var(--sp-component-sidebar-text);
2770
+ font-family: var(--sp-font-family-sans);
2771
+ position: fixed;
2772
+ top: 0;
2773
+ left: 0;
2774
+ height: 100%;
2775
+ transform: translateX(-100%);
2776
+ z-index: var(--sp-component-sidebar-z-index);
2777
+ transition:
2778
+ background-color var(--sp-duration-fast) var(--sp-easing-out),
2779
+ color var(--sp-duration-fast) var(--sp-easing-out),
2780
+ border-color var(--sp-duration-fast) var(--sp-easing-out),
2781
+ transform var(--sp-duration-fast) var(--sp-easing-out);
2782
+ }
2783
+
2784
+ .sp-sidebar--bordered {
2785
+ border-right: var(--sp-component-border-width) solid var(--sp-component-sidebar-border);
2786
+ }
2787
+
2788
+ .sp-sidebar__link {
2789
+ color: var(--sp-component-sidebar-link);
2790
+ font-size: var(--sp-font-sm-size);
2791
+ line-height: var(--sp-font-sm-line-height);
2792
+ font-weight: var(--sp-font-sm-weight);
2793
+ text-decoration: none;
2794
+ transition: color var(--sp-duration-fast) var(--sp-easing-out);
2795
+ }
2796
+
2797
+ .sp-sidebar__link:hover,
2798
+ .sp-sidebar__link--hover,
2799
+ .sp-sidebar__link.is-hover {
2800
+ color: var(--sp-component-sidebar-link-hover);
2801
+ }
2802
+
2803
+ .sp-sidebar__link:focus-visible,
2804
+ .sp-sidebar__link--focus,
2805
+ .sp-sidebar__link.is-focus {
2806
+ outline: none;
2807
+ box-shadow: 0 0 0 calc(var(--sp-focus-ring-width) + var(--sp-component-border-width)) var(--sp-color-focus-primary);
2808
+ }
2809
+
2810
+ .sp-sidebar__link--active,
2811
+ .sp-sidebar__link.is-active {
2812
+ color: var(--sp-component-sidebar-link-active);
2813
+ }
2814
+
2815
+ .sp-sidebar__link--disabled,
2816
+ .sp-sidebar__link[aria-disabled="true"] {
2817
+ pointer-events: none;
2818
+ opacity: var(--sp-opacity-disabled);
2819
+ }
2820
+
2821
+ .sp-sidebar-backdrop {
2822
+ display: none;
2823
+ position: fixed;
2824
+ inset: 0;
2825
+ background-color: var(--sp-component-sidebar-backdrop);
2826
+ z-index: var(--sp-component-sidebar-backdrop-z-index);
2827
+ }
2828
+
2829
+ .sp-sidebar-toggle {
2830
+ position: relative;
2831
+ z-index: var(--sp-component-sidebar-toggle-z-index);
2832
+ }
2833
+
2834
+ [data-sidebar-open="true"] .sp-sidebar {
2835
+ transform: translateX(0);
2836
+ }
2837
+
2838
+ [data-sidebar-open="true"] .sp-sidebar-backdrop {
2839
+ display: block;
2840
+ }
2841
+
2842
+ /* Above breakpoints.md, the sidebar docks inline instead of as an off-canvas
2843
+ drawer - see Grid's @media literal convention for why this is a literal. */
2844
+ @media (min-width: 768px) {
2845
+ .sp-sidebar {
2846
+ position: static;
2847
+ height: auto;
2848
+ transform: none;
2849
+ }
2850
+
2851
+ .sp-sidebar-backdrop {
2852
+ display: none;
2853
+ }
2854
+ }
2855
+
2856
+ /* FOOTER ----------------------------------------------------------------- */
2857
+ .sp-footer {
2858
+ display: flex;
2859
+ align-items: center;
2860
+ justify-content: space-between;
2861
+ gap: var(--sp-space-16);
2862
+ padding: var(--sp-space-12) var(--sp-space-16);
2863
+ background-color: var(--sp-component-footer-bg);
2864
+ color: var(--sp-component-footer-text);
2865
+ font-family: var(--sp-font-family-sans);
2866
+ transition:
2867
+ background-color var(--sp-duration-fast) var(--sp-easing-out),
2868
+ color var(--sp-duration-fast) var(--sp-easing-out),
2869
+ border-color var(--sp-duration-fast) var(--sp-easing-out);
2870
+ }
2871
+
2872
+ .sp-footer--bordered {
2873
+ border-top: var(--sp-component-border-width) solid var(--sp-component-footer-border);
2874
+ }
2875
+
2876
+ .sp-footer--full {
2877
+ width: 100%;
2878
+ }
2879
+
2725
2880
  /* TOASTS ----------------------------------------------------------------- */
2726
2881
  .sp-toast {
2727
2882
  display: flex;
package/dist/index.cjs CHANGED
@@ -788,6 +788,39 @@ function getNavLinkClasses(opts = {}) {
788
788
  );
789
789
  }
790
790
 
791
+ // src/recipes/sidebar.ts
792
+ function getSidebarClasses(opts = {}) {
793
+ const { bordered = false } = opts;
794
+ return cx("sp-sidebar", bordered && "sp-sidebar--bordered");
795
+ }
796
+ function getSidebarLinkClasses(opts = {}) {
797
+ const {
798
+ active = false,
799
+ disabled = false,
800
+ hovered = false,
801
+ focused = false
802
+ } = opts;
803
+ return cx(
804
+ "sp-sidebar__link",
805
+ active && "sp-sidebar__link--active",
806
+ disabled && "sp-sidebar__link--disabled",
807
+ hovered && "sp-sidebar__link--hover is-hover",
808
+ focused && "sp-sidebar__link--focus is-focus"
809
+ );
810
+ }
811
+ function getSidebarBackdropClasses() {
812
+ return cx("sp-sidebar-backdrop");
813
+ }
814
+ function getSidebarToggleClasses() {
815
+ return cx("sp-sidebar-toggle");
816
+ }
817
+
818
+ // src/recipes/footer.ts
819
+ function getFooterClasses(opts = {}) {
820
+ const { bordered = false, fullWidth = false } = opts;
821
+ return cx("sp-footer", bordered && "sp-footer--bordered", fullWidth && "sp-footer--full");
822
+ }
823
+
791
824
  // src/recipes/toast.ts
792
825
  var TOAST_VARIANTS = {
793
826
  info: true,
@@ -899,8 +932,19 @@ function getModalClasses(opts = {}) {
899
932
  }
900
933
 
901
934
  // src/recipes/container.ts
902
- function getContainerClasses(_opts = {}) {
903
- return "sp-container";
935
+ var CONTAINER_MAX_WIDTHS = {
936
+ none: true,
937
+ prose: true
938
+ };
939
+ function getContainerClasses(opts = {}) {
940
+ const { maxWidth: maxWidthInput } = opts;
941
+ const maxWidth = resolveOption({
942
+ name: "container maxWidth",
943
+ value: maxWidthInput,
944
+ allowed: CONTAINER_MAX_WIDTHS,
945
+ fallback: "none"
946
+ });
947
+ return cx("sp-container", maxWidth !== "none" && `sp-container--max-width-${maxWidth}`);
904
948
  }
905
949
 
906
950
  // src/recipes/stack.ts
@@ -908,15 +952,39 @@ var STACK_DIRECTIONS = {
908
952
  vertical: true,
909
953
  horizontal: true
910
954
  };
955
+ var STACK_BASES = {
956
+ none: true,
957
+ sidebar: true
958
+ };
959
+ var STACK_ALIGNS = {
960
+ center: true,
961
+ stretch: true
962
+ };
911
963
  function getStackClasses(opts = {}) {
912
- const { direction: directionInput } = opts;
964
+ const { direction: directionInput, basis: basisInput, align: alignInput } = opts;
913
965
  const direction = resolveOption({
914
966
  name: "stack direction",
915
967
  value: directionInput,
916
968
  allowed: STACK_DIRECTIONS,
917
969
  fallback: "vertical"
918
970
  });
919
- return direction === "horizontal" ? "sp-hstack" : "sp-stack";
971
+ const basis = resolveOption({
972
+ name: "stack basis",
973
+ value: basisInput,
974
+ allowed: STACK_BASES,
975
+ fallback: "none"
976
+ });
977
+ const align = resolveOption({
978
+ name: "stack align",
979
+ value: alignInput,
980
+ allowed: STACK_ALIGNS,
981
+ fallback: "center"
982
+ });
983
+ return cx(
984
+ direction === "horizontal" ? "sp-hstack" : "sp-stack",
985
+ basis !== "none" && `sp-stack--basis-${basis}`,
986
+ align !== "center" && `sp-stack--align-${align}`
987
+ );
920
988
  }
921
989
 
922
990
  // src/recipes/section.ts
@@ -964,6 +1032,7 @@ exports.getContainerClasses = getContainerClasses;
964
1032
  exports.getDropdownClasses = getDropdownClasses;
965
1033
  exports.getDropdownItemClasses = getDropdownItemClasses;
966
1034
  exports.getDropdownMenuClasses = getDropdownMenuClasses;
1035
+ exports.getFooterClasses = getFooterClasses;
967
1036
  exports.getGridClasses = getGridClasses;
968
1037
  exports.getIconBoxClasses = getIconBoxClasses;
969
1038
  exports.getInputClasses = getInputClasses;
@@ -986,6 +1055,10 @@ exports.getRatingStarClasses = getRatingStarClasses;
986
1055
  exports.getRatingStarsClasses = getRatingStarsClasses;
987
1056
  exports.getRatingTextClasses = getRatingTextClasses;
988
1057
  exports.getSectionClasses = getSectionClasses;
1058
+ exports.getSidebarBackdropClasses = getSidebarBackdropClasses;
1059
+ exports.getSidebarClasses = getSidebarClasses;
1060
+ exports.getSidebarLinkClasses = getSidebarLinkClasses;
1061
+ exports.getSidebarToggleClasses = getSidebarToggleClasses;
989
1062
  exports.getSpinnerClasses = getSpinnerClasses;
990
1063
  exports.getStackClasses = getStackClasses;
991
1064
  exports.getTagClasses = getTagClasses;