aporia 0.2.1 → 0.2.6

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/README.md CHANGED
@@ -8,6 +8,18 @@ A collection of production-quality React components for building configurators -
8
8
  npm install aporia
9
9
  ```
10
10
 
11
+ Import **`aporia/styles.css` once** in your app entry (for example `main.tsx`). If you use Tailwind or another global reset, import Aporia’s stylesheet **after** those layers so Aporia’s opinionated panel / popover typography and control chrome win predictable conflicts.
12
+
13
+ ## Maintainers: package demo + consumer sandbox
14
+
15
+ If you keep a sibling app (for example `../aporia-test`) next to this repo:
16
+
17
+ 1. **Canonical UI (this repo)** — `npm run dev` starts the **Vite demo** (`--mode demo`, default port **5178**). That is the reference layout: white page shell (`var(--color-page)`), `Panel` uses the default **360px** max width from `--aporia-panel-max-width`.
18
+ 2. **NPM-style consumer** — in the sibling app, depend on `file:../aporia`, run `npm run dev` there (this workspace uses port **5173** for the consumer). After library changes, run `npm run sync-lib` inside the consumer to rebuild `aporia` and refresh the `file:` install.
19
+ 3. **From the parent folder** — with both repos under the same parent directory, `npm run dev:package` and `npm run dev:consumer` run each app; `npm run sync:consumer` rebuilds the library and reinstalls into the consumer.
20
+
21
+ Publishing for end users: bump `version`, run `npm run build`, then `npm publish`. Consumers should use a normal semver range (for example `^0.2.6`) and `import 'aporia/styles.css'`. The `prepublishOnly` script runs `build` automatically on publish. Until you publish, sibling apps can use `file:../aporia` and `npm run sync-lib` after edits — **no publish required** for local reflection.
22
+
11
23
  ## Usage
12
24
 
13
25
  The usual setup is a **Panel** with one or more **Category** blocks, each containing rows (`SliderRow`, `ColorRow`, etc.). The package default export is `Panel`, so you can import it as the default or by name.
@@ -46,13 +58,13 @@ function App() {
46
58
  }
47
59
  ```
48
60
 
49
- You can also compose rows without `Panel` / `Category` if you only need individual controls.
61
+ Use `Panel` (and usually `Category`) for any real configurator surface so spacing, typography, and the black card shell stay consistent. Rows still work as leaf nodes, but they are designed to live inside that shell.
50
62
 
51
63
  ## Components
52
64
 
53
65
  ### Panel
54
66
 
55
- Rounded shell for a configurator: drop in `Category` sections and row components as children.
67
+ Rounded shell for a configurator: drop in `Category` sections and row components as children. By default the panel is **`width: 100%`** with **`max-width: min(360px, 100%)`** via the `:root` token **`--aporia-panel-max-width`** (override on `html` or a wrapper if you need a wider shell).
56
68
 
57
69
  ### Category
58
70
 
@@ -133,7 +145,9 @@ const stops = parseGradient(css) // Parse CSS gradient
133
145
 
134
146
  ## Theming
135
147
 
136
- Aporia includes built-in dark and light themes. Wrap your app with `ThemeProvider`:
148
+ Aporia ships a **dark** visual system: the **Panel** surface is always `#000000` with light text, and `color-scheme: dark` is scoped to the panel and floating pickers only so your host page (light theme, marketing site, etc.) is not forced into document-wide dark UA chrome. CSS variables remain on `:root` so portaled popovers still inherit them.
149
+
150
+ `ThemeProvider` is for **React context** (`useTheme`) only; it does not mutate `<html>` or set global `data-theme`.
137
151
 
138
152
  ```tsx
139
153
  import { ThemeProvider, useTheme } from 'aporia'
@@ -146,15 +160,13 @@ function App() {
146
160
  )
147
161
  }
148
162
 
149
- // Access theme in components
150
- function ThemeToggle() {
151
- const { theme, toggleTheme } = useTheme()
152
- return <button onClick={toggleTheme}>{theme}</button>
163
+ // `theme` is `'dark'` today; useful for future light mode without changing call sites.
164
+ function ThemeLabel() {
165
+ const { theme } = useTheme()
166
+ return <span>{theme}</span>
153
167
  }
154
168
  ```
155
169
 
156
- Keyboard shortcut: `Shift+T` toggles the theme.
157
-
158
170
  ## Peer Dependencies
159
171
 
160
172
  Aporia requires these peer dependencies:
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../src/ThemeProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,OAAO,CAAA;AAEd,MAAM,MAAM,KAAK,GAAG,MAAM,CAAA;AAE1B,KAAK,iBAAiB,GAAG;IACvB,KAAK,EAAE,KAAK,CAAA;CACb,CAAA;AAID,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,2CAYlE;AAED,wBAAgB,QAAQ,sBAMvB"}
1
+ {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../src/ThemeProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEjE,MAAM,MAAM,KAAK,GAAG,MAAM,CAAA;AAE1B,KAAK,iBAAiB,GAAG;IACvB,KAAK,EAAE,KAAK,CAAA;CACb,CAAA;AAID,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,2CAQlE;AAED,wBAAgB,QAAQ,sBAMvB"}
@@ -1 +1 @@
1
- {"version":3,"file":"Category.d.ts","sourceRoot":"","sources":["../../src/components/Category.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEpE,OAAO,gBAAgB,CAAA;AAEvB,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,SAAS,CAAA;IAChB,QAAQ,EAAE,SAAS,CAAA;IACnB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,YAAY,EAAE,OAAO,KAAK,IAAI,CAAA;IAClD,yDAAyD;IACzD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,0DAA0D;IAC1D,iBAAiB,CAAC,EAAE,CAAC,aAAa,EAAE,OAAO,KAAK,IAAI,CAAA;IACpD,iDAAiD;IACjD,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,QAAQ,EACR,QAAgB,EAChB,gBAAgB,EAChB,SAAS,EAAE,aAAa,EACxB,iBAAiB,EACjB,gBAAwB,EACxB,SAAS,GACV,EAAE,aAAa,2CAmFf"}
1
+ {"version":3,"file":"Category.d.ts","sourceRoot":"","sources":["../../src/components/Category.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAA;AAGd,OAAO,gBAAgB,CAAA;AAavB,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,SAAS,CAAA;IAChB,QAAQ,EAAE,SAAS,CAAA;IACnB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,YAAY,EAAE,OAAO,KAAK,IAAI,CAAA;IAClD,yDAAyD;IACzD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,0DAA0D;IAC1D,iBAAiB,CAAC,EAAE,CAAC,aAAa,EAAE,OAAO,KAAK,IAAI,CAAA;IACpD,iDAAiD;IACjD,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,QAAQ,EACR,QAAgB,EAChB,gBAAgB,EAChB,SAAS,EAAE,aAAa,EACxB,iBAAiB,EACjB,gBAAwB,EACxB,SAAS,GACV,EAAE,aAAa,2CAqJf"}
@@ -0,0 +1,17 @@
1
+ export type SelectRowOption = {
2
+ value: string;
3
+ label: string;
4
+ };
5
+ export type SelectRowProps = {
6
+ label: string;
7
+ options: readonly SelectRowOption[];
8
+ /** Controlled value; `null` shows the placeholder. */
9
+ value: string | null;
10
+ onChange: (value: string) => void;
11
+ placeholder?: string;
12
+ disabled?: boolean;
13
+ /** Optional form field name (hidden input). */
14
+ name?: string;
15
+ };
16
+ export declare function SelectRow({ label, options, value, onChange, placeholder, disabled: disabledProp, name, }: SelectRowProps): import("react/jsx-runtime").JSX.Element;
17
+ //# sourceMappingURL=SelectRow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SelectRow.d.ts","sourceRoot":"","sources":["../../src/components/SelectRow.tsx"],"names":[],"mappings":"AAGA,OAAO,iBAAiB,CAAA;AAExB,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,SAAS,eAAe,EAAE,CAAA;IACnC,sDAAsD;IACtD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,OAAO,EACP,KAAK,EACL,QAAQ,EACR,WAAuB,EACvB,QAAQ,EAAE,YAAY,EACtB,IAAI,GACL,EAAE,cAAc,2CA8FhB"}
@@ -14,8 +14,11 @@ type SwatchPopoverProps = {
14
14
  };
15
15
  /**
16
16
  * Base UI Popover anchored to a swatch: opens below, aligned to the inline end (bottom-trailing in LTR).
17
- * `modal={true}` is required for the internal backdrop (blocks clicks/scroll on the rest of the page).
18
- * `modal="trap-focus"` does not mount that layer, so controls underneath stayed clickable.
17
+ *
18
+ * **Default `modal={false}`** matches Base UI and keeps pointer events on the document so nested
19
+ * portaled pickers (e.g. gradient editor → stop color) still receive clicks. Use `modal={true}`
20
+ * only when you want scroll lock + outside pointer blocking for a single-layer popover.
21
+ * Use `modal="trap-focus"` for focus trapping without scroll lock.
19
22
  */
20
23
  export declare function SwatchPopover({ title, renderTrigger, children, modal, side, align, sideOffset, zIndex, }: SwatchPopoverProps): import("react/jsx-runtime").JSX.Element;
21
24
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"SwatchPopover.d.ts","sourceRoot":"","sources":["../../src/components/SwatchPopover.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,qBAAqB,CAAA;AAE5B,MAAM,MAAM,0BAA0B,GAAG,cAAc,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;AAEvE,KAAK,kBAAkB,GAAG;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,WAAW,CAAC,0BAA0B,CAAC,CAAA;IACtD,QAAQ,EAAE,SAAS,CAAA;IACnB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,IAAI,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IACvC,KAAK,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IACzC,UAAU,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;IACnD,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,aAAa,EACb,QAAQ,EACR,KAAY,EACZ,IAAe,EACf,KAAa,EACb,UAAc,EACd,MAAM,GACP,EAAE,kBAAkB,2CA2BpB"}
1
+ {"version":3,"file":"SwatchPopover.d.ts","sourceRoot":"","sources":["../../src/components/SwatchPopover.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,qBAAqB,CAAA;AAE5B,MAAM,MAAM,0BAA0B,GAAG,cAAc,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;AAEvE,KAAK,kBAAkB,GAAG;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,WAAW,CAAC,0BAA0B,CAAC,CAAA;IACtD,QAAQ,EAAE,SAAS,CAAA;IACnB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,IAAI,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IACvC,KAAK,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IACzC,UAAU,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;IACnD,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,aAAa,EACb,QAAQ,EACR,KAAa,EACb,IAAe,EACf,KAAa,EACb,UAAc,EACd,MAAM,GACP,EAAE,kBAAkB,2CA2BpB"}
@@ -1,9 +1,102 @@
1
+ /*
2
+ * Theme tokens — dark mode only.
3
+ */
4
+
5
+ /*
6
+ * Tokens live on :root so portaled popovers (mounted under <body>) still inherit variables.
7
+ * Do not set `color-scheme` here — that would affect the entire host document (scrollbars,
8
+ * native controls outside Aporia). `color-scheme: dark` is applied only on Panel and popups.
9
+ */
10
+ :root {
11
+ /* App shell */
12
+ --color-page: #ffffff;
13
+ --color-text: rgba(255, 255, 255, 0.87);
14
+ --color-muted: rgba(255, 255, 255, 0.45);
15
+
16
+ /* Slider card (full pill) */
17
+ --slider-surface: rgba(255, 255, 255, 0.05);
18
+ --slider-surface-active: rgba(255, 255, 255, 0.08);
19
+ --slider-rim: rgba(255, 255, 255, 0.06);
20
+
21
+ /* Filled track (left portion) */
22
+ --slider-track: rgba(255, 255, 255, 0.11);
23
+ --slider-track-active: rgba(255, 255, 255, 0.18);
24
+ /* ButtonRow / pointer-down on track-like controls */
25
+ --slider-track-pressed: rgba(255, 255, 255, 0.26);
26
+
27
+ /* Thumb */
28
+ --slider-thumb: rgba(255, 255, 255, 0.7);
29
+ --slider-thumb-dragging: #ffffff;
30
+
31
+ /* Typography */
32
+ --slider-text-muted: rgba(255, 255, 255, 0.7);
33
+ --slider-text: #ffffff;
34
+ /*
35
+ * UI font for Panel, rows, and portaled UI (popover content inherits from :root,
36
+ * not from Panel — without a default, `inherit` resolves to the browser body font).
37
+ * Override anytime: html { --aporia-font-family: 'Inter', system-ui, sans-serif; }
38
+ */
39
+ --aporia-font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
40
+ 'Helvetica Neue', Arial, sans-serif;
41
+
42
+ /* Fixed UI rhythm (px): not tied to host `html { font-size: … }` / rem scaling). */
43
+ --aporia-ui-font-size: 14px;
44
+ --aporia-ui-line-height: 1.25;
45
+
46
+ /* Default panel width cap (matches the package demo). Override per app if needed. */
47
+ --aporia-panel-max-width: 360px;
48
+
49
+ /* Ticks (base color × layer opacity ≈ dark appearance) */
50
+ --slider-tick: rgba(255, 255, 255, 0.32);
51
+ --slider-tick-layer-opacity: 0.3;
52
+
53
+ /* Thumb opacity when row not hovered */
54
+ --slider-thumb-idle-opacity: 0;
55
+
56
+ /* Value field selection (same in both themes per spec) */
57
+ --slider-value-selection-bg: #ffffff;
58
+ --slider-value-selection-fg: #000000;
59
+
60
+ /*
61
+ * Toggle inner thumb: must match how the *row* reads, but stay opaque — the ON
62
+ * track is solid white; semi-transparent --slider-surface would wash out and
63
+ * hide the thumb. These ≈ 5% / 8% white on black, same as the row visually.
64
+ */
65
+ --toggle-thumb-row-bg: #0d0d0d;
66
+ --toggle-thumb-row-bg-hover: #141414;
67
+
68
+ /*
69
+ * Swatch popover: opaque fill so the panel reads on #000 (rows use translucent rgba).
70
+ * #0a0a0a matches Figma design spec.
71
+ */
72
+ --swatch-popover-surface: #141414;
73
+ --swatch-popover-bg: #0a0a0a;
74
+ --swatch-popover-rim: rgba(255, 255, 255, 0.12);
75
+
76
+ /* Shift+D debug overlays */
77
+ --slider-debug-label-tint: rgba(255, 0, 160, 0.18);
78
+ --slider-debug-value-tint: rgba(255, 200, 0, 0.2);
79
+ --slider-debug-dom-label: rgba(0, 255, 140, 0.9);
80
+ --slider-debug-dom-value: rgba(80, 180, 255, 0.95);
81
+ --slider-debug-dom-shadow: rgba(0, 0, 0, 0.35);
82
+ }
1
83
  /* Shell for grouped controls: solid black card, generous radius, inset padding. */
2
84
 
3
85
  .aporiaPanel {
4
86
  box-sizing: border-box;
5
87
  width: 100%;
88
+ max-width: min(var(--aporia-panel-max-width), 100%);
6
89
  min-width: 0;
90
+ /* Hug content: avoid stretching to fill a flex/grid parent’s unused vertical space. */
91
+ flex: 0 0 auto;
92
+ height: auto;
93
+ min-height: 0;
94
+ max-height: none;
95
+ align-self: stretch;
96
+ /* Typography + metrics: see `aporia-surfaces.css` (imported after components). */
97
+ /* Always read as the black configurator card, independent of host `color` / light UI. */
98
+ color: var(--slider-text);
99
+ color-scheme: dark;
7
100
  background-color: #000000;
8
101
  border-radius: 24px;
9
102
  padding: 16px;
@@ -15,6 +108,7 @@
15
108
  0 6px 14px 0 rgba(0, 0, 0, 0.09);
16
109
  display: flex;
17
110
  flex-direction: column;
111
+ gap: 8px;
18
112
  align-items: stretch;
19
113
  justify-content: flex-start;
20
114
  align-content: flex-start;
@@ -22,7 +116,8 @@
22
116
  .category {
23
117
  display: flex;
24
118
  flex-direction: column;
25
- gap: 10px;
119
+ /* Keep in sync with CATEGORY_STACK_GAP_PX in Category.tsx (motion cancels this when collapsed). */
120
+ gap: 8px;
26
121
  min-width: 0;
27
122
  }
28
123
 
@@ -134,6 +229,7 @@
134
229
  margin: 0;
135
230
  flex: 1;
136
231
  min-width: 0;
232
+ font-family: var(--aporia-font-family);
137
233
  font-size: 12px;
138
234
  font-weight: 500;
139
235
  letter-spacing: 0.08em;
@@ -145,15 +241,17 @@
145
241
 
146
242
  .categoryBody {
147
243
  min-width: 0;
148
- padding-bottom: 4px;
149
244
  }
150
245
 
151
- /*
152
- * .sliderGroup sets display:flex; without this, that rule wins over the [hidden]
153
- * attribute and the category body stays visible when “collapsed”.
154
- */
155
- .categoryBody[hidden] {
156
- display: none !important;
246
+ /* Padding lives inside the height animation so it never springs on its own (8px + Panel gap = 16px between categories). */
247
+ .categoryBodyInner {
248
+ min-width: 0;
249
+ padding-bottom: 8px;
250
+ }
251
+
252
+ /* Last category among siblings: no extra bottom inset (panel padding handles the card edge). */
253
+ .category:not(:has(~ .category)) .categoryBodyInner {
254
+ padding-bottom: 0;
157
255
  }
158
256
 
159
257
  .category[data-disabled="true"] .categoryBody {
@@ -380,6 +478,7 @@
380
478
  position: absolute;
381
479
  inset: 0;
382
480
  right: auto;
481
+ z-index: 0;
383
482
  background: var(--slider-track);
384
483
  border-radius: 12px;
385
484
  pointer-events: none;
@@ -562,6 +661,8 @@
562
661
  flex-direction: column;
563
662
  width: 190px;
564
663
  box-sizing: border-box;
664
+ color-scheme: dark;
665
+ font-family: var(--aporia-font-family, inherit);
565
666
  background: rgba(10, 10, 10, 0.8);
566
667
  backdrop-filter: blur(20px);
567
668
  border: 0.5px solid rgba(255, 255, 255, 0.06);
@@ -655,10 +756,6 @@
655
756
  }
656
757
 
657
758
  /* Hex value - uses ValueInput component */
658
- .colorPickerHex {
659
- font-family: 'SF Compact Rounded', system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
660
- }
661
-
662
759
  .colorPickerHex .valueInputField {
663
760
  width: 72px;
664
761
  }
@@ -667,7 +764,6 @@
667
764
  display: flex;
668
765
  align-items: center;
669
766
  gap: 10px;
670
- font-family: 'SF Compact Rounded', system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
671
767
  font-size: 14px;
672
768
  line-height: normal;
673
769
  letter-spacing: 0.28px;
@@ -727,7 +823,6 @@
727
823
  border-radius: 9px;
728
824
  background: transparent;
729
825
  color: rgba(255, 255, 255, 0.7);
730
- font-family: 'SF Compact Rounded', system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
731
826
  font-size: 14px;
732
827
  line-height: normal;
733
828
  letter-spacing: 0.28px;
@@ -772,6 +867,8 @@
772
867
  .swatchPopoverPopup {
773
868
  position: relative;
774
869
  z-index: 1;
870
+ color-scheme: dark;
871
+ font-family: var(--aporia-font-family, inherit);
775
872
  min-width: 190px;
776
873
  max-width: min(320px, calc(100vw - 24px));
777
874
  padding: 0;
@@ -1043,9 +1140,14 @@
1043
1140
  -moz-user-select: none;
1044
1141
  -webkit-user-drag: none;
1045
1142
  width: 100%;
1046
- font: inherit;
1143
+ font-family: var(--aporia-font-family);
1144
+ font-size: var(--aporia-ui-font-size);
1145
+ font-weight: 400;
1146
+ font-style: normal;
1147
+ line-height: var(--aporia-ui-line-height);
1148
+ letter-spacing: normal;
1047
1149
  text-align: left;
1048
- color: inherit;
1150
+ color: var(--slider-text-muted);
1049
1151
  }
1050
1152
 
1051
1153
  .toggleCard:hover {
@@ -1135,11 +1237,263 @@
1135
1237
  .toggleCard[data-on='true'] .toggleSwitch__thumb {
1136
1238
  transform: translateX(6px);
1137
1239
  }
1240
+ /* Trigger: same geometry as SliderRow; card fill matches .sliderCard (--slider-surface). */
1241
+
1242
+ .selectRowWrap {
1243
+ width: 100%;
1244
+ max-width: 100%;
1245
+ min-width: 0;
1246
+ position: relative;
1247
+ overflow: visible;
1248
+ }
1249
+
1250
+ .selectRowWrap[data-disabled="true"] {
1251
+ opacity: 0.45;
1252
+ }
1253
+
1254
+ .selectRowWrap[data-disabled="true"] .selectCard {
1255
+ pointer-events: none;
1256
+ }
1257
+
1258
+ .selectCard {
1259
+ position: relative;
1260
+ width: 100%;
1261
+ height: 37px;
1262
+ margin: 0;
1263
+ border: none;
1264
+ border-radius: 12px;
1265
+ background: var(--slider-surface);
1266
+ box-shadow: inset 0 0 0 0.5px var(--slider-rim);
1267
+ overflow: hidden;
1268
+ transition: background-color 0.2s ease-out;
1269
+ cursor: pointer;
1270
+ display: flex;
1271
+ align-items: center;
1272
+ padding: 10px 12px;
1273
+ box-sizing: border-box;
1274
+ font-family: var(--aporia-font-family);
1275
+ font-size: var(--aporia-ui-font-size);
1276
+ font-weight: 400;
1277
+ font-style: normal;
1278
+ line-height: var(--aporia-ui-line-height);
1279
+ letter-spacing: normal;
1280
+ text-align: left;
1281
+ color: var(--slider-text-muted);
1282
+ user-select: none;
1283
+ -webkit-user-select: none;
1284
+ -moz-user-select: none;
1285
+ }
1286
+
1287
+ .selectCard:hover:not(:disabled) {
1288
+ background: var(--slider-surface-active);
1289
+ }
1290
+
1291
+ .selectCard:focus {
1292
+ outline: none;
1293
+ }
1294
+
1295
+ .selectCard:focus-visible {
1296
+ outline: 2px solid var(--slider-text-muted);
1297
+ outline-offset: 2px;
1298
+ }
1299
+
1300
+ .selectCard[data-popup-open="true"] {
1301
+ background: var(--slider-surface-active);
1302
+ }
1303
+
1304
+ .selectCard:active:not(:disabled) {
1305
+ background: var(--slider-surface-active);
1306
+ }
1307
+
1308
+ .selectLabel {
1309
+ position: relative;
1310
+ font-size: 14px;
1311
+ font-weight: 400;
1312
+ color: var(--slider-text-muted);
1313
+ letter-spacing: 0.28px;
1314
+ flex: 1;
1315
+ min-width: 0;
1316
+ z-index: 2;
1317
+ pointer-events: none;
1318
+ transition: color 0.2s ease-out;
1319
+ }
1320
+
1321
+ .selectCard:hover:not(:disabled) .selectLabel,
1322
+ .selectCard[data-popup-open="true"] .selectLabel {
1323
+ color: var(--slider-text);
1324
+ }
1325
+
1326
+ .selectValueZone {
1327
+ position: absolute;
1328
+ top: 0;
1329
+ right: 0;
1330
+ height: 100%;
1331
+ z-index: 3;
1332
+ display: flex;
1333
+ align-items: center;
1334
+ justify-content: flex-end;
1335
+ gap: 6px;
1336
+ padding-right: 12px;
1337
+ min-width: 72px;
1338
+ max-width: 50%;
1339
+ }
1340
+
1341
+ .selectValue {
1342
+ font-size: 14px;
1343
+ font-weight: 400;
1344
+ letter-spacing: 0.28px;
1345
+ color: var(--slider-text);
1346
+ white-space: nowrap;
1347
+ overflow: hidden;
1348
+ text-overflow: ellipsis;
1349
+ min-width: 0;
1350
+ }
1351
+
1352
+ .selectValue[data-placeholder] {
1353
+ color: var(--slider-text-muted);
1354
+ }
1355
+
1356
+ .selectIcon {
1357
+ display: flex;
1358
+ flex-shrink: 0;
1359
+ color: var(--slider-text-muted);
1360
+ }
1361
+
1362
+ .selectCard:hover:not(:disabled) .selectIcon,
1363
+ .selectCard[data-popup-open="true"] .selectIcon {
1364
+ color: var(--slider-text-muted);
1365
+ }
1366
+
1367
+ .selectChevronSvg {
1368
+ display: block;
1369
+ transition: transform 0.2s ease-out;
1370
+ }
1371
+
1372
+ /* Flip with React `menuOpen` (onOpenChange) so it tracks open immediately, not only after Base UI’s popup-open paint. */
1373
+ .selectCard[data-menu-open="true"] .selectChevronSvg {
1374
+ transform: rotate(180deg);
1375
+ }
1376
+
1377
+ .selectPortal {
1378
+ position: relative;
1379
+ z-index: 200000;
1380
+ isolation: isolate;
1381
+ }
1382
+
1383
+ .selectPositioner {
1384
+ z-index: 0;
1385
+ box-sizing: border-box;
1386
+ /* At least as wide as the row — set inline on portal as --select-anchor-width */
1387
+ min-width: var(--select-anchor-width, 0px);
1388
+ }
1389
+
1390
+ /* Base UI scroll nudge controls (▲ / ▼) — hide even if present (siblings of popup in the portal). */
1391
+ .selectPortal [data-direction="up"],
1392
+ .selectPortal [data-direction="down"],
1393
+ .selectPositioner [data-direction="up"],
1394
+ .selectPositioner [data-direction="down"] {
1395
+ display: none !important;
1396
+ }
1397
+
1398
+ .selectPopup {
1399
+ box-sizing: border-box;
1400
+ width: 100%;
1401
+ min-width: 0;
1402
+ color-scheme: dark;
1403
+ font-family: var(--aporia-font-family, inherit);
1404
+ max-width: min(320px, calc(100vw - 24px));
1405
+ padding: 4px;
1406
+ border-radius: 12px;
1407
+ background: var(--swatch-popover-bg);
1408
+ box-shadow: inset 0 0 0 0.5px var(--swatch-popover-rim);
1409
+ color: var(--slider-text);
1410
+ outline: none;
1411
+ /* Popover-style motion; anchor to trailing edge (align end / SwatchPopover). */
1412
+ transform-origin: top right;
1413
+ opacity: 1;
1414
+ transform: translateY(0) scale(1);
1415
+ transition:
1416
+ opacity 0.2s ease-out,
1417
+ transform 0.22s cubic-bezier(0.16, 1, 0.3, 1);
1418
+ }
1419
+
1420
+ .selectPopup[data-starting-style],
1421
+ .selectPopup[data-ending-style] {
1422
+ opacity: 0;
1423
+ transform: translateY(-6px) scale(0.97);
1424
+ }
1425
+
1426
+ .selectList {
1427
+ display: flex;
1428
+ flex-direction: column;
1429
+ gap: 0;
1430
+ max-height: min(280px, 45vh);
1431
+ overflow-y: auto;
1432
+ padding: 0;
1433
+ min-width: 0;
1434
+ }
1435
+
1436
+ .selectItem {
1437
+ position: relative;
1438
+ margin: 0;
1439
+ /* Match SliderRow card: 10px vertical padding + ~17px line = 37px (line-height caps text box). */
1440
+ height: 37px;
1441
+ box-sizing: border-box;
1442
+ padding: 10px 8px;
1443
+ border: none;
1444
+ border-radius: 8px;
1445
+ background: transparent;
1446
+ font-family: var(--aporia-font-family);
1447
+ font-size: var(--aporia-ui-font-size);
1448
+ font-weight: 400;
1449
+ font-style: normal;
1450
+ line-height: 17px;
1451
+ letter-spacing: 0.02em;
1452
+ color: rgba(255, 255, 255, 0.7);
1453
+ cursor: pointer;
1454
+ text-align: left;
1455
+ display: flex;
1456
+ align-items: center;
1457
+ width: 100%;
1458
+ transition:
1459
+ background-color 0.15s ease-out,
1460
+ color 0.15s ease-out;
1461
+ }
1462
+
1463
+ /* Selected / highlighted visuals: see `aporia-surfaces.css` (host `[role=option]` overrides). */
1464
+
1465
+ .selectItem[data-disabled] {
1466
+ opacity: 0.45;
1467
+ cursor: default;
1468
+ }
1469
+
1470
+ .selectItemText {
1471
+ flex: 1;
1472
+ min-width: 0;
1473
+ }
1474
+
1475
+ @media (prefers-reduced-motion: reduce) {
1476
+ .selectPopup {
1477
+ transition-duration: 0.1s;
1478
+ transition-property: opacity;
1479
+ }
1480
+
1481
+ .selectPopup[data-starting-style],
1482
+ .selectPopup[data-ending-style] {
1483
+ transform: none;
1484
+ }
1485
+
1486
+ .selectChevronSvg {
1487
+ transition: none;
1488
+ }
1489
+ }
1138
1490
  /* Container - exact Figma specs */
1139
1491
  .gradientPicker {
1140
1492
  display: flex;
1141
1493
  flex-direction: column;
1142
1494
  width: 190px;
1495
+ color-scheme: dark;
1496
+ font-family: var(--aporia-font-family, inherit);
1143
1497
  background: #0a0a0a;
1144
1498
  border: 1px solid rgba(255, 255, 255, 0.06);
1145
1499
  border-radius: 12px;
@@ -1354,3 +1708,57 @@
1354
1708
  outline: 2px solid var(--slider-text-muted);
1355
1709
  outline-offset: 2px;
1356
1710
  }
1711
+ /**
1712
+ * Host isolation: consumer apps often scale `rem`, reset `button`, or style generic
1713
+ * `[role="option"]` / `[type="range"]`. These rules sit after component CSS and restore
1714
+ * Aporia’s intended metrics on the panel and on portaled surfaces (not under `.aporiaPanel`).
1715
+ */
1716
+
1717
+ .aporiaPanel,
1718
+ .swatchPopoverPopup,
1719
+ .selectPopup,
1720
+ .gradientPicker,
1721
+ .colorPicker {
1722
+ font-family: var(--aporia-font-family);
1723
+ font-size: var(--aporia-ui-font-size);
1724
+ font-weight: 400;
1725
+ font-style: normal;
1726
+ line-height: var(--aporia-ui-line-height);
1727
+ letter-spacing: normal;
1728
+ -webkit-font-smoothing: antialiased;
1729
+ -moz-osx-font-smoothing: grayscale;
1730
+ font-synthesis: none;
1731
+ text-rendering: optimizeLegibility;
1732
+ }
1733
+
1734
+ /*
1735
+ * Do not style `input[type=range]` pseudo-elements here. WebKit paints the range control
1736
+ * above sibling divs (`.sliderTrack`, thumb); forcing `-webkit-appearance`/thumb sizing
1737
+ * hid the custom progress fill and card chrome in real apps (Tailwind + host resets).
1738
+ */
1739
+
1740
+ /* Swatch triggers keep inline `background` / gradients vs global `button { background: none }`. */
1741
+ .aporiaPanel button.gradientSwatchBtn,
1742
+ .aporiaPanel button.colorSwatchBtn {
1743
+ appearance: none;
1744
+ -webkit-appearance: none;
1745
+ margin: 0;
1746
+ box-sizing: border-box;
1747
+ border-style: solid;
1748
+ border-width: 0;
1749
+ }
1750
+
1751
+ /*
1752
+ * Select listbox: duplicate Base UI’s selected / highlighted visuals with higher
1753
+ * specificity than generic `[role="option"]` rules from host design systems.
1754
+ */
1755
+ .selectPopup .selectList > .selectItem[role='option'][data-highlighted] {
1756
+ background-color: #141414 !important;
1757
+ color: #ffffff !important;
1758
+ }
1759
+
1760
+ .selectPopup .selectList > .selectItem[role='option'][data-selected]:not([data-highlighted]),
1761
+ .selectPopup .selectList > .selectItem[role='option'][aria-selected='true']:not([data-highlighted]) {
1762
+ background-color: transparent !important;
1763
+ color: #ffffff !important;
1764
+ }
package/dist/index.d.ts CHANGED
@@ -11,6 +11,8 @@ export { ColorPicker } from './components/ColorPicker';
11
11
  export type { ColorPickerProps } from './components/ColorPicker';
12
12
  export { ToggleRow } from './components/ToggleRow';
13
13
  export type { ToggleRowProps } from './components/ToggleRow';
14
+ export { SelectRow } from './components/SelectRow';
15
+ export type { SelectRowProps, SelectRowOption } from './components/SelectRow';
14
16
  export { GradientRow } from './components/GradientRow';
15
17
  export type { GradientRowProps } from './components/GradientRow';
16
18
  export { GradientPicker, stopsToGradient, parseGradient, DEFAULT_GRADIENT_STOPS } from './components/GradientPicker';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,KAAK,IAAI,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC5D,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAEpD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAE1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAA;AAE1E,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAClD,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAE5D,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAE1D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,YAAY,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAEhE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAClD,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAE5D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,YAAY,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAEhE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AACpH,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AAEpF,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC1D,YAAY,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAA;AAG5E,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACzD,YAAY,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AAE5C,OAAO,EAAE,0BAA0B,EAAE,4BAA4B,EAAE,MAAM,iCAAiC,CAAA;AAG1G,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,OAAO,aAAa,CAAA;AAGpB,OAAO,EAAE,KAAK,EAAE,KAAK,IAAI,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC5D,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAEpD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAE1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAA;AAE1E,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAClD,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAE5D,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAE1D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,YAAY,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAEhE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAClD,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAE5D,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAClD,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAE7E,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,YAAY,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAEhE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AACpH,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AAEpF,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC1D,YAAY,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAA;AAG5E,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACzD,YAAY,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AAE5C,OAAO,EAAE,0BAA0B,EAAE,4BAA4B,EAAE,MAAM,iCAAiC,CAAA;AAG1G,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAE7D,0FAA0F;AAC1F,OAAO,uBAAuB,CAAA"}