aporia 0.2.1 → 0.2.5

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` capped at **360px** wide like `src/App.css`.
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.5`) 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,7 +58,7 @@ 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
 
@@ -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"}
@@ -1,9 +1,98 @@
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
+ /* Ticks (base color × layer opacity ≈ dark appearance) */
47
+ --slider-tick: rgba(255, 255, 255, 0.32);
48
+ --slider-tick-layer-opacity: 0.3;
49
+
50
+ /* Thumb opacity when row not hovered */
51
+ --slider-thumb-idle-opacity: 0;
52
+
53
+ /* Value field selection (same in both themes per spec) */
54
+ --slider-value-selection-bg: #ffffff;
55
+ --slider-value-selection-fg: #000000;
56
+
57
+ /*
58
+ * Toggle inner thumb: must match how the *row* reads, but stay opaque — the ON
59
+ * track is solid white; semi-transparent --slider-surface would wash out and
60
+ * hide the thumb. These ≈ 5% / 8% white on black, same as the row visually.
61
+ */
62
+ --toggle-thumb-row-bg: #0d0d0d;
63
+ --toggle-thumb-row-bg-hover: #141414;
64
+
65
+ /*
66
+ * Swatch popover: opaque fill so the panel reads on #000 (rows use translucent rgba).
67
+ * #0a0a0a matches Figma design spec.
68
+ */
69
+ --swatch-popover-surface: #141414;
70
+ --swatch-popover-bg: #0a0a0a;
71
+ --swatch-popover-rim: rgba(255, 255, 255, 0.12);
72
+
73
+ /* Shift+D debug overlays */
74
+ --slider-debug-label-tint: rgba(255, 0, 160, 0.18);
75
+ --slider-debug-value-tint: rgba(255, 200, 0, 0.2);
76
+ --slider-debug-dom-label: rgba(0, 255, 140, 0.9);
77
+ --slider-debug-dom-value: rgba(80, 180, 255, 0.95);
78
+ --slider-debug-dom-shadow: rgba(0, 0, 0, 0.35);
79
+ }
1
80
  /* Shell for grouped controls: solid black card, generous radius, inset padding. */
2
81
 
3
82
  .aporiaPanel {
4
83
  box-sizing: border-box;
5
84
  width: 100%;
6
85
  min-width: 0;
86
+ /* Hug content: avoid stretching to fill a flex/grid parent’s unused vertical space. */
87
+ flex: 0 0 auto;
88
+ height: auto;
89
+ min-height: 0;
90
+ max-height: none;
91
+ align-self: stretch;
92
+ /* Typography + metrics: see `aporia-surfaces.css` (imported after components). */
93
+ /* Always read as the black configurator card, independent of host `color` / light UI. */
94
+ color: var(--slider-text);
95
+ color-scheme: dark;
7
96
  background-color: #000000;
8
97
  border-radius: 24px;
9
98
  padding: 16px;
@@ -15,6 +104,7 @@
15
104
  0 6px 14px 0 rgba(0, 0, 0, 0.09);
16
105
  display: flex;
17
106
  flex-direction: column;
107
+ gap: 8px;
18
108
  align-items: stretch;
19
109
  justify-content: flex-start;
20
110
  align-content: flex-start;
@@ -22,7 +112,8 @@
22
112
  .category {
23
113
  display: flex;
24
114
  flex-direction: column;
25
- gap: 10px;
115
+ /* Keep in sync with CATEGORY_STACK_GAP_PX in Category.tsx (motion cancels this when collapsed). */
116
+ gap: 8px;
26
117
  min-width: 0;
27
118
  }
28
119
 
@@ -134,6 +225,7 @@
134
225
  margin: 0;
135
226
  flex: 1;
136
227
  min-width: 0;
228
+ font-family: var(--aporia-font-family);
137
229
  font-size: 12px;
138
230
  font-weight: 500;
139
231
  letter-spacing: 0.08em;
@@ -145,15 +237,17 @@
145
237
 
146
238
  .categoryBody {
147
239
  min-width: 0;
148
- padding-bottom: 4px;
149
240
  }
150
241
 
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;
242
+ /* Padding lives inside the height animation so it never springs on its own (8px + Panel gap = 16px between categories). */
243
+ .categoryBodyInner {
244
+ min-width: 0;
245
+ padding-bottom: 8px;
246
+ }
247
+
248
+ /* Last category among siblings: no extra bottom inset (panel padding handles the card edge). */
249
+ .category:not(:has(~ .category)) .categoryBodyInner {
250
+ padding-bottom: 0;
157
251
  }
158
252
 
159
253
  .category[data-disabled="true"] .categoryBody {
@@ -380,6 +474,7 @@
380
474
  position: absolute;
381
475
  inset: 0;
382
476
  right: auto;
477
+ z-index: 0;
383
478
  background: var(--slider-track);
384
479
  border-radius: 12px;
385
480
  pointer-events: none;
@@ -562,6 +657,8 @@
562
657
  flex-direction: column;
563
658
  width: 190px;
564
659
  box-sizing: border-box;
660
+ color-scheme: dark;
661
+ font-family: var(--aporia-font-family, inherit);
565
662
  background: rgba(10, 10, 10, 0.8);
566
663
  backdrop-filter: blur(20px);
567
664
  border: 0.5px solid rgba(255, 255, 255, 0.06);
@@ -655,10 +752,6 @@
655
752
  }
656
753
 
657
754
  /* 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
755
  .colorPickerHex .valueInputField {
663
756
  width: 72px;
664
757
  }
@@ -667,7 +760,6 @@
667
760
  display: flex;
668
761
  align-items: center;
669
762
  gap: 10px;
670
- font-family: 'SF Compact Rounded', system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
671
763
  font-size: 14px;
672
764
  line-height: normal;
673
765
  letter-spacing: 0.28px;
@@ -727,7 +819,6 @@
727
819
  border-radius: 9px;
728
820
  background: transparent;
729
821
  color: rgba(255, 255, 255, 0.7);
730
- font-family: 'SF Compact Rounded', system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
731
822
  font-size: 14px;
732
823
  line-height: normal;
733
824
  letter-spacing: 0.28px;
@@ -772,6 +863,8 @@
772
863
  .swatchPopoverPopup {
773
864
  position: relative;
774
865
  z-index: 1;
866
+ color-scheme: dark;
867
+ font-family: var(--aporia-font-family, inherit);
775
868
  min-width: 190px;
776
869
  max-width: min(320px, calc(100vw - 24px));
777
870
  padding: 0;
@@ -1043,9 +1136,14 @@
1043
1136
  -moz-user-select: none;
1044
1137
  -webkit-user-drag: none;
1045
1138
  width: 100%;
1046
- font: inherit;
1139
+ font-family: var(--aporia-font-family);
1140
+ font-size: var(--aporia-ui-font-size);
1141
+ font-weight: 400;
1142
+ font-style: normal;
1143
+ line-height: var(--aporia-ui-line-height);
1144
+ letter-spacing: normal;
1047
1145
  text-align: left;
1048
- color: inherit;
1146
+ color: var(--slider-text-muted);
1049
1147
  }
1050
1148
 
1051
1149
  .toggleCard:hover {
@@ -1135,11 +1233,263 @@
1135
1233
  .toggleCard[data-on='true'] .toggleSwitch__thumb {
1136
1234
  transform: translateX(6px);
1137
1235
  }
1236
+ /* Trigger: same geometry as SliderRow; card fill matches .sliderCard (--slider-surface). */
1237
+
1238
+ .selectRowWrap {
1239
+ width: 100%;
1240
+ max-width: 100%;
1241
+ min-width: 0;
1242
+ position: relative;
1243
+ overflow: visible;
1244
+ }
1245
+
1246
+ .selectRowWrap[data-disabled="true"] {
1247
+ opacity: 0.45;
1248
+ }
1249
+
1250
+ .selectRowWrap[data-disabled="true"] .selectCard {
1251
+ pointer-events: none;
1252
+ }
1253
+
1254
+ .selectCard {
1255
+ position: relative;
1256
+ width: 100%;
1257
+ height: 37px;
1258
+ margin: 0;
1259
+ border: none;
1260
+ border-radius: 12px;
1261
+ background: var(--slider-surface);
1262
+ box-shadow: inset 0 0 0 0.5px var(--slider-rim);
1263
+ overflow: hidden;
1264
+ transition: background-color 0.2s ease-out;
1265
+ cursor: pointer;
1266
+ display: flex;
1267
+ align-items: center;
1268
+ padding: 10px 12px;
1269
+ box-sizing: border-box;
1270
+ font-family: var(--aporia-font-family);
1271
+ font-size: var(--aporia-ui-font-size);
1272
+ font-weight: 400;
1273
+ font-style: normal;
1274
+ line-height: var(--aporia-ui-line-height);
1275
+ letter-spacing: normal;
1276
+ text-align: left;
1277
+ color: var(--slider-text-muted);
1278
+ user-select: none;
1279
+ -webkit-user-select: none;
1280
+ -moz-user-select: none;
1281
+ }
1282
+
1283
+ .selectCard:hover:not(:disabled) {
1284
+ background: var(--slider-surface-active);
1285
+ }
1286
+
1287
+ .selectCard:focus {
1288
+ outline: none;
1289
+ }
1290
+
1291
+ .selectCard:focus-visible {
1292
+ outline: 2px solid var(--slider-text-muted);
1293
+ outline-offset: 2px;
1294
+ }
1295
+
1296
+ .selectCard[data-popup-open="true"] {
1297
+ background: var(--slider-surface-active);
1298
+ }
1299
+
1300
+ .selectCard:active:not(:disabled) {
1301
+ background: var(--slider-surface-active);
1302
+ }
1303
+
1304
+ .selectLabel {
1305
+ position: relative;
1306
+ font-size: 14px;
1307
+ font-weight: 400;
1308
+ color: var(--slider-text-muted);
1309
+ letter-spacing: 0.28px;
1310
+ flex: 1;
1311
+ min-width: 0;
1312
+ z-index: 2;
1313
+ pointer-events: none;
1314
+ transition: color 0.2s ease-out;
1315
+ }
1316
+
1317
+ .selectCard:hover:not(:disabled) .selectLabel,
1318
+ .selectCard[data-popup-open="true"] .selectLabel {
1319
+ color: var(--slider-text);
1320
+ }
1321
+
1322
+ .selectValueZone {
1323
+ position: absolute;
1324
+ top: 0;
1325
+ right: 0;
1326
+ height: 100%;
1327
+ z-index: 3;
1328
+ display: flex;
1329
+ align-items: center;
1330
+ justify-content: flex-end;
1331
+ gap: 6px;
1332
+ padding-right: 12px;
1333
+ min-width: 72px;
1334
+ max-width: 50%;
1335
+ }
1336
+
1337
+ .selectValue {
1338
+ font-size: 14px;
1339
+ font-weight: 400;
1340
+ letter-spacing: 0.28px;
1341
+ color: var(--slider-text);
1342
+ white-space: nowrap;
1343
+ overflow: hidden;
1344
+ text-overflow: ellipsis;
1345
+ min-width: 0;
1346
+ }
1347
+
1348
+ .selectValue[data-placeholder] {
1349
+ color: var(--slider-text-muted);
1350
+ }
1351
+
1352
+ .selectIcon {
1353
+ display: flex;
1354
+ flex-shrink: 0;
1355
+ color: var(--slider-text-muted);
1356
+ }
1357
+
1358
+ .selectCard:hover:not(:disabled) .selectIcon,
1359
+ .selectCard[data-popup-open="true"] .selectIcon {
1360
+ color: var(--slider-text-muted);
1361
+ }
1362
+
1363
+ .selectChevronSvg {
1364
+ display: block;
1365
+ transition: transform 0.2s ease-out;
1366
+ }
1367
+
1368
+ /* Flip with React `menuOpen` (onOpenChange) so it tracks open immediately, not only after Base UI’s popup-open paint. */
1369
+ .selectCard[data-menu-open="true"] .selectChevronSvg {
1370
+ transform: rotate(180deg);
1371
+ }
1372
+
1373
+ .selectPortal {
1374
+ position: relative;
1375
+ z-index: 200000;
1376
+ isolation: isolate;
1377
+ }
1378
+
1379
+ .selectPositioner {
1380
+ z-index: 0;
1381
+ box-sizing: border-box;
1382
+ /* At least as wide as the row — set inline on portal as --select-anchor-width */
1383
+ min-width: var(--select-anchor-width, 0px);
1384
+ }
1385
+
1386
+ /* Base UI scroll nudge controls (▲ / ▼) — hide even if present (siblings of popup in the portal). */
1387
+ .selectPortal [data-direction="up"],
1388
+ .selectPortal [data-direction="down"],
1389
+ .selectPositioner [data-direction="up"],
1390
+ .selectPositioner [data-direction="down"] {
1391
+ display: none !important;
1392
+ }
1393
+
1394
+ .selectPopup {
1395
+ box-sizing: border-box;
1396
+ width: 100%;
1397
+ min-width: 0;
1398
+ color-scheme: dark;
1399
+ font-family: var(--aporia-font-family, inherit);
1400
+ max-width: min(320px, calc(100vw - 24px));
1401
+ padding: 4px;
1402
+ border-radius: 12px;
1403
+ background: var(--swatch-popover-bg);
1404
+ box-shadow: inset 0 0 0 0.5px var(--swatch-popover-rim);
1405
+ color: var(--slider-text);
1406
+ outline: none;
1407
+ /* Popover-style motion; anchor to trailing edge (align end / SwatchPopover). */
1408
+ transform-origin: top right;
1409
+ opacity: 1;
1410
+ transform: translateY(0) scale(1);
1411
+ transition:
1412
+ opacity 0.2s ease-out,
1413
+ transform 0.22s cubic-bezier(0.16, 1, 0.3, 1);
1414
+ }
1415
+
1416
+ .selectPopup[data-starting-style],
1417
+ .selectPopup[data-ending-style] {
1418
+ opacity: 0;
1419
+ transform: translateY(-6px) scale(0.97);
1420
+ }
1421
+
1422
+ .selectList {
1423
+ display: flex;
1424
+ flex-direction: column;
1425
+ gap: 0;
1426
+ max-height: min(280px, 45vh);
1427
+ overflow-y: auto;
1428
+ padding: 0;
1429
+ min-width: 0;
1430
+ }
1431
+
1432
+ .selectItem {
1433
+ position: relative;
1434
+ margin: 0;
1435
+ /* Match SliderRow card: 10px vertical padding + ~17px line = 37px (line-height caps text box). */
1436
+ height: 37px;
1437
+ box-sizing: border-box;
1438
+ padding: 10px 8px;
1439
+ border: none;
1440
+ border-radius: 8px;
1441
+ background: transparent;
1442
+ font-family: var(--aporia-font-family);
1443
+ font-size: var(--aporia-ui-font-size);
1444
+ font-weight: 400;
1445
+ font-style: normal;
1446
+ line-height: 17px;
1447
+ letter-spacing: 0.02em;
1448
+ color: rgba(255, 255, 255, 0.7);
1449
+ cursor: pointer;
1450
+ text-align: left;
1451
+ display: flex;
1452
+ align-items: center;
1453
+ width: 100%;
1454
+ transition:
1455
+ background-color 0.15s ease-out,
1456
+ color 0.15s ease-out;
1457
+ }
1458
+
1459
+ /* Selected / highlighted visuals: see `aporia-surfaces.css` (host `[role=option]` overrides). */
1460
+
1461
+ .selectItem[data-disabled] {
1462
+ opacity: 0.45;
1463
+ cursor: default;
1464
+ }
1465
+
1466
+ .selectItemText {
1467
+ flex: 1;
1468
+ min-width: 0;
1469
+ }
1470
+
1471
+ @media (prefers-reduced-motion: reduce) {
1472
+ .selectPopup {
1473
+ transition-duration: 0.1s;
1474
+ transition-property: opacity;
1475
+ }
1476
+
1477
+ .selectPopup[data-starting-style],
1478
+ .selectPopup[data-ending-style] {
1479
+ transform: none;
1480
+ }
1481
+
1482
+ .selectChevronSvg {
1483
+ transition: none;
1484
+ }
1485
+ }
1138
1486
  /* Container - exact Figma specs */
1139
1487
  .gradientPicker {
1140
1488
  display: flex;
1141
1489
  flex-direction: column;
1142
1490
  width: 190px;
1491
+ color-scheme: dark;
1492
+ font-family: var(--aporia-font-family, inherit);
1143
1493
  background: #0a0a0a;
1144
1494
  border: 1px solid rgba(255, 255, 255, 0.06);
1145
1495
  border-radius: 12px;
@@ -1354,3 +1704,57 @@
1354
1704
  outline: 2px solid var(--slider-text-muted);
1355
1705
  outline-offset: 2px;
1356
1706
  }
1707
+ /**
1708
+ * Host isolation: consumer apps often scale `rem`, reset `button`, or style generic
1709
+ * `[role="option"]` / `[type="range"]`. These rules sit after component CSS and restore
1710
+ * Aporia’s intended metrics on the panel and on portaled surfaces (not under `.aporiaPanel`).
1711
+ */
1712
+
1713
+ .aporiaPanel,
1714
+ .swatchPopoverPopup,
1715
+ .selectPopup,
1716
+ .gradientPicker,
1717
+ .colorPicker {
1718
+ font-family: var(--aporia-font-family);
1719
+ font-size: var(--aporia-ui-font-size);
1720
+ font-weight: 400;
1721
+ font-style: normal;
1722
+ line-height: var(--aporia-ui-line-height);
1723
+ letter-spacing: normal;
1724
+ -webkit-font-smoothing: antialiased;
1725
+ -moz-osx-font-smoothing: grayscale;
1726
+ font-synthesis: none;
1727
+ text-rendering: optimizeLegibility;
1728
+ }
1729
+
1730
+ /*
1731
+ * Do not style `input[type=range]` pseudo-elements here. WebKit paints the range control
1732
+ * above sibling divs (`.sliderTrack`, thumb); forcing `-webkit-appearance`/thumb sizing
1733
+ * hid the custom progress fill and card chrome in real apps (Tailwind + host resets).
1734
+ */
1735
+
1736
+ /* Swatch triggers keep inline `background` / gradients vs global `button { background: none }`. */
1737
+ .aporiaPanel button.gradientSwatchBtn,
1738
+ .aporiaPanel button.colorSwatchBtn {
1739
+ appearance: none;
1740
+ -webkit-appearance: none;
1741
+ margin: 0;
1742
+ box-sizing: border-box;
1743
+ border-style: solid;
1744
+ border-width: 0;
1745
+ }
1746
+
1747
+ /*
1748
+ * Select listbox: duplicate Base UI’s selected / highlighted visuals with higher
1749
+ * specificity than generic `[role="option"]` rules from host design systems.
1750
+ */
1751
+ .selectPopup .selectList > .selectItem[role='option'][data-highlighted] {
1752
+ background-color: #141414 !important;
1753
+ color: #ffffff !important;
1754
+ }
1755
+
1756
+ .selectPopup .selectList > .selectItem[role='option'][data-selected]:not([data-highlighted]),
1757
+ .selectPopup .selectList > .selectItem[role='option'][aria-selected='true']:not([data-highlighted]) {
1758
+ background-color: transparent !important;
1759
+ color: #ffffff !important;
1760
+ }
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"}