@oxy.so/bloom 4.27.0 → 4.29.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/docs/carousel.mdx +2 -0
- package/docs/design-tokens.mdx +15 -4
- package/docs/glass.mdx +1 -1
- package/lib/commonjs/carousel/Carousel.js +41 -25
- package/lib/commonjs/carousel/Carousel.js.map +1 -1
- package/lib/commonjs/design-tokens/tokens.json +50 -48
- package/lib/commonjs/theme/color-presets.js +64 -4
- package/lib/commonjs/theme/color-presets.js.map +1 -1
- package/lib/commonjs/theme/preset-vars.js +19 -1
- package/lib/commonjs/theme/preset-vars.js.map +1 -1
- package/lib/module/carousel/Carousel.js +42 -26
- package/lib/module/carousel/Carousel.js.map +1 -1
- package/lib/module/design-tokens/tokens.json +50 -48
- package/lib/module/theme/color-presets.js +64 -4
- package/lib/module/theme/color-presets.js.map +1 -1
- package/lib/module/theme/preset-vars.js +20 -2
- package/lib/module/theme/preset-vars.js.map +1 -1
- package/lib/typescript/commonjs/carousel/Carousel.d.ts +1 -1
- package/lib/typescript/commonjs/carousel/Carousel.d.ts.map +1 -1
- package/lib/typescript/commonjs/carousel/types.d.ts +13 -0
- package/lib/typescript/commonjs/carousel/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/color-presets.d.ts +57 -2
- package/lib/typescript/commonjs/theme/color-presets.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/preset-vars.d.ts.map +1 -1
- package/lib/typescript/module/carousel/Carousel.d.ts +1 -1
- package/lib/typescript/module/carousel/Carousel.d.ts.map +1 -1
- package/lib/typescript/module/carousel/types.d.ts +13 -0
- package/lib/typescript/module/carousel/types.d.ts.map +1 -1
- package/lib/typescript/module/theme/color-presets.d.ts +57 -2
- package/lib/typescript/module/theme/color-presets.d.ts.map +1 -1
- package/lib/typescript/module/theme/preset-vars.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/carousel/Carousel.tsx +19 -8
- package/src/carousel/types.ts +13 -0
- package/src/design-tokens/tokens.json +50 -48
- package/src/theme/color-presets.ts +67 -2
- package/src/theme/preset-vars.ts +22 -1
- package/src/__tests__/support/adoption-matrix.ts +0 -703
- package/src/__tests__/support/collision-fixture-barrel.ts +0 -20
- package/src/__tests__/support/commerce-harness.tsx +0 -97
- package/src/__tests__/support/composite.ts +0 -68
- package/src/__tests__/support/constructed-style-sheets.ts +0 -68
- package/src/__tests__/support/press-host.ts +0 -30
- package/src/__tests__/support/rendered-style.ts +0 -99
- package/src/__tests__/support/unread-hook-fixture.ts +0 -33
- package/src/__tests__/support/worklet-capture-fixture.tsx +0 -28
- package/src/theme/__tests__/__fixtures__/golden-resolved-tokens.json +0 -9346
- package/src/theme/__tests__/__fixtures__/tonal-glass-primary-failures.json +0 -69
- package/src/theme/__tests__/fixtures/color-engine-golden.json +0 -1
|
@@ -40,6 +40,7 @@ import type { CarouselItemProps, CarouselProps } from './types';
|
|
|
40
40
|
* prev/next buttons above and a position indicator below.
|
|
41
41
|
*
|
|
42
42
|
* column gap 16
|
|
43
|
+
* header optional leading content of the arrows row (a title)
|
|
43
44
|
* arrows right-aligned row, gap 8, small secondary icon buttons
|
|
44
45
|
* (Bloom's pill `Button`, sized to a 32px square icon button)
|
|
45
46
|
* track native horizontal scrolling that SNAPS to each slide — CSS
|
|
@@ -105,6 +106,7 @@ const DOT = '[data-bloom-carousel-dot]';
|
|
|
105
106
|
const BLOOM_CAROUSEL_CSS = `
|
|
106
107
|
${TRACK} {
|
|
107
108
|
scroll-snap-type: x mandatory;
|
|
109
|
+
scroll-padding-inline: var(--bloom-carousel-inset, 0px);
|
|
108
110
|
overscroll-behavior-x: contain;
|
|
109
111
|
scrollbar-width: none;
|
|
110
112
|
outline: none;
|
|
@@ -266,10 +268,12 @@ interface SlideOffset {
|
|
|
266
268
|
const CarouselComponent = function Carousel({
|
|
267
269
|
children,
|
|
268
270
|
accessibilityLabel,
|
|
271
|
+
header,
|
|
269
272
|
showArrows = true,
|
|
270
273
|
showDots = true,
|
|
271
274
|
align = 'start',
|
|
272
275
|
gap = 16,
|
|
276
|
+
inset = 0,
|
|
273
277
|
onIndexChange,
|
|
274
278
|
previousLabel = 'Previous slide',
|
|
275
279
|
nextLabel = 'Next slide',
|
|
@@ -337,11 +341,11 @@ const CarouselComponent = function Carousel({
|
|
|
337
341
|
|
|
338
342
|
const targetFor = useCallback(
|
|
339
343
|
(item: SlideOffset) => {
|
|
340
|
-
const raw = align === 'center' ? item.x - (trackWidth - item.width) / 2 : item.x;
|
|
344
|
+
const raw = align === 'center' ? item.x - (trackWidth - item.width) / 2 : item.x - inset;
|
|
341
345
|
const max = Math.max(0, scroll.current.contentWidth - trackWidth);
|
|
342
346
|
return Math.min(Math.max(0, raw), max);
|
|
343
347
|
},
|
|
344
|
-
[align, trackWidth],
|
|
348
|
+
[align, inset, trackWidth],
|
|
345
349
|
);
|
|
346
350
|
|
|
347
351
|
const recomputeSnaps = useCallback(() => {
|
|
@@ -392,6 +396,8 @@ const CarouselComponent = function Carousel({
|
|
|
392
396
|
width: '100%',
|
|
393
397
|
// The `:focus-visible` ring colour, read by the adopted sheet.
|
|
394
398
|
'--bloom-carousel-ring': paint.ring,
|
|
399
|
+
// Where a start-aligned slide snaps to on web, matching `inset` on native.
|
|
400
|
+
'--bloom-carousel-inset': `${inset}px`,
|
|
395
401
|
};
|
|
396
402
|
|
|
397
403
|
return (
|
|
@@ -402,10 +408,15 @@ const CarouselComponent = function Carousel({
|
|
|
402
408
|
style={[{ width: '100%', flexDirection: 'column', gap: 16 }, style]}
|
|
403
409
|
testID={testID}
|
|
404
410
|
>
|
|
405
|
-
{showArrows && count > 0 ? (
|
|
406
|
-
<View style={{ flexDirection: 'row', alignItems: 'center',
|
|
407
|
-
<
|
|
408
|
-
|
|
411
|
+
{header != null || (showArrows && count > 0) ? (
|
|
412
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: inset }}>
|
|
413
|
+
<View style={{ flex: 1, minWidth: 0 }}>{header}</View>
|
|
414
|
+
{showArrows && count > 0 ? (
|
|
415
|
+
<>
|
|
416
|
+
<Button size="sm" icon={RiArrowLeftSLine} accessibilityLabel={previousLabel} disabled={atStart} onPress={() => scrollToIndex(active - 1)} appearance="subtle" tone="neutral" />
|
|
417
|
+
<Button size="sm" icon={RiArrowRightSLine} accessibilityLabel={nextLabel} disabled={atEnd} onPress={() => scrollToIndex(active + 1)} appearance="subtle" tone="neutral" />
|
|
418
|
+
</>
|
|
419
|
+
) : null}
|
|
409
420
|
</View>
|
|
410
421
|
) : null}
|
|
411
422
|
|
|
@@ -426,7 +437,7 @@ const CarouselComponent = function Carousel({
|
|
|
426
437
|
decelerationRate={IS_WEB ? undefined : 'fast'}
|
|
427
438
|
disableIntervalMomentum
|
|
428
439
|
style={trackStyle}
|
|
429
|
-
contentContainerStyle={{ gap }}
|
|
440
|
+
contentContainerStyle={{ gap, paddingHorizontal: inset }}
|
|
430
441
|
>
|
|
431
442
|
{slides.map((child, index) => (
|
|
432
443
|
<CarouselItemIndexContext.Provider key={child.key ?? index} value={index}>
|
|
@@ -437,7 +448,7 @@ const CarouselComponent = function Carousel({
|
|
|
437
448
|
</CarouselContext.Provider>
|
|
438
449
|
|
|
439
450
|
{showDots && count > 1 ? (
|
|
440
|
-
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 6 }}>
|
|
451
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 6, paddingHorizontal: inset }}>
|
|
441
452
|
{Array.from({ length: count }, (_, index) => (
|
|
442
453
|
<CarouselDot
|
|
443
454
|
key={index}
|
package/src/carousel/types.ts
CHANGED
|
@@ -9,6 +9,12 @@ export interface CarouselProps {
|
|
|
9
9
|
* `aria-label`). The region announces itself as a "carousel".
|
|
10
10
|
*/
|
|
11
11
|
accessibilityLabel: string;
|
|
12
|
+
/**
|
|
13
|
+
* Leading content of the row above the track — typically the section title
|
|
14
|
+
* and a "See all" link. It shares the row with the arrows (header at the
|
|
15
|
+
* start, arrows at the end), so a titled carousel costs one row, not two.
|
|
16
|
+
*/
|
|
17
|
+
header?: ReactNode;
|
|
12
18
|
/** Previous / next buttons above the track. Defaults to `true`. */
|
|
13
19
|
showArrows?: boolean;
|
|
14
20
|
/** Position indicator below the track. Defaults to `true`. */
|
|
@@ -17,6 +23,13 @@ export interface CarouselProps {
|
|
|
17
23
|
align?: 'start' | 'center';
|
|
18
24
|
/** Gap between slides, in px. Defaults to `16`. */
|
|
19
25
|
gap?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Horizontal inset, in px, of the header row, the dots and the first and
|
|
28
|
+
* last slide. Slides still scroll under it to the carousel's edges (it is
|
|
29
|
+
* padding INSIDE the track, not a margin around it), and a snapped slide
|
|
30
|
+
* rests at the inset, not flush. Defaults to `0`.
|
|
31
|
+
*/
|
|
32
|
+
inset?: number;
|
|
20
33
|
/** Called when the slide in view changes. */
|
|
21
34
|
onIndexChange?: (index: number) => void;
|
|
22
35
|
/** Accessible name of the previous button. Defaults to `'Previous slide'`. */
|
|
@@ -4445,64 +4445,66 @@
|
|
|
4445
4445
|
}
|
|
4446
4446
|
},
|
|
4447
4447
|
"oxy": {
|
|
4448
|
-
"$description": "Oxy
|
|
4448
|
+
"$description": "Oxy orchid with a magenta support and a terracotta action, reserved for the Oxy account identity.",
|
|
4449
4449
|
"$extensions": {
|
|
4450
4450
|
"so.oxy.bloom": {
|
|
4451
4451
|
"seed": "#c46ede",
|
|
4452
4452
|
"variant": "vivid",
|
|
4453
4453
|
"displayName": "Oxy",
|
|
4454
4454
|
"family": "brand",
|
|
4455
|
-
"description": "Oxy
|
|
4456
|
-
"pairing": "
|
|
4455
|
+
"description": "Oxy orchid with a magenta support and a terracotta action, reserved for the Oxy account identity.",
|
|
4456
|
+
"pairing": "curated",
|
|
4457
4457
|
"featured": false,
|
|
4458
|
+
"secondarySeed": "#8f3987",
|
|
4459
|
+
"tertiarySeed": "#a0392b",
|
|
4458
4460
|
"gate": "handle"
|
|
4459
4461
|
}
|
|
4460
4462
|
},
|
|
4461
4463
|
"light": {
|
|
4462
4464
|
"background": {
|
|
4463
|
-
"$value": "#
|
|
4465
|
+
"$value": "#f7ebf0"
|
|
4464
4466
|
},
|
|
4465
4467
|
"foreground": {
|
|
4466
4468
|
"$value": "#2a1132"
|
|
4467
4469
|
},
|
|
4468
4470
|
"surface": {
|
|
4469
|
-
"$value": "#
|
|
4471
|
+
"$value": "#f1dde9"
|
|
4470
4472
|
},
|
|
4471
4473
|
"surface-foreground": {
|
|
4472
4474
|
"$value": "#2a1132"
|
|
4473
4475
|
},
|
|
4474
4476
|
"popover": {
|
|
4475
|
-
"$value": "#
|
|
4477
|
+
"$value": "#edcee4"
|
|
4476
4478
|
},
|
|
4477
4479
|
"popover-foreground": {
|
|
4478
4480
|
"$value": "#2a1132"
|
|
4479
4481
|
},
|
|
4480
4482
|
"primary": {
|
|
4481
|
-
"$value": "#
|
|
4483
|
+
"$value": "#9422ae"
|
|
4482
4484
|
},
|
|
4483
4485
|
"primary-foreground": {
|
|
4484
|
-
"$value": "#
|
|
4486
|
+
"$value": "#ffeefc"
|
|
4485
4487
|
},
|
|
4486
4488
|
"secondary": {
|
|
4487
|
-
"$value": "#
|
|
4489
|
+
"$value": "#8f3987"
|
|
4488
4490
|
},
|
|
4489
4491
|
"secondary-foreground": {
|
|
4490
|
-
"$value": "#
|
|
4492
|
+
"$value": "#ffeef8"
|
|
4491
4493
|
},
|
|
4492
4494
|
"tertiary": {
|
|
4493
|
-
"$value": "#
|
|
4495
|
+
"$value": "#a0392b"
|
|
4494
4496
|
},
|
|
4495
4497
|
"tertiary-foreground": {
|
|
4496
|
-
"$value": "#
|
|
4498
|
+
"$value": "#ffefed"
|
|
4497
4499
|
},
|
|
4498
4500
|
"muted": {
|
|
4499
|
-
"$value": "#
|
|
4501
|
+
"$value": "#e7c1dd"
|
|
4500
4502
|
},
|
|
4501
4503
|
"muted-foreground": {
|
|
4502
4504
|
"$value": "#593c60"
|
|
4503
4505
|
},
|
|
4504
4506
|
"accent": {
|
|
4505
|
-
"$value": "#
|
|
4507
|
+
"$value": "#e7c1dd"
|
|
4506
4508
|
},
|
|
4507
4509
|
"accent-foreground": {
|
|
4508
4510
|
"$value": "#593c60"
|
|
@@ -4517,13 +4519,13 @@
|
|
|
4517
4519
|
"$value": "#c9a5cf"
|
|
4518
4520
|
},
|
|
4519
4521
|
"ring": {
|
|
4520
|
-
"$value": "#
|
|
4522
|
+
"$value": "#9422ae"
|
|
4521
4523
|
},
|
|
4522
4524
|
"sidebar": {
|
|
4523
|
-
"$value": "#
|
|
4525
|
+
"$value": "#f1dde9"
|
|
4524
4526
|
},
|
|
4525
4527
|
"card": {
|
|
4526
|
-
"$value": "#
|
|
4528
|
+
"$value": "#fff7f9"
|
|
4527
4529
|
},
|
|
4528
4530
|
"card-foreground": {
|
|
4529
4531
|
"$value": "#2a1132"
|
|
@@ -4550,22 +4552,22 @@
|
|
|
4550
4552
|
"$value": "#2a1132"
|
|
4551
4553
|
},
|
|
4552
4554
|
"sidebar-primary": {
|
|
4553
|
-
"$value": "#
|
|
4555
|
+
"$value": "#9422ae"
|
|
4554
4556
|
},
|
|
4555
4557
|
"sidebar-primary-foreground": {
|
|
4556
|
-
"$value": "#
|
|
4558
|
+
"$value": "#ffeefc"
|
|
4557
4559
|
},
|
|
4558
4560
|
"sidebar-accent": {
|
|
4559
|
-
"$value": "#
|
|
4561
|
+
"$value": "#ffbdf1"
|
|
4560
4562
|
},
|
|
4561
4563
|
"sidebar-accent-foreground": {
|
|
4562
|
-
"$value": "#
|
|
4564
|
+
"$value": "#772371"
|
|
4563
4565
|
},
|
|
4564
4566
|
"sidebar-border": {
|
|
4565
4567
|
"$value": "#c9a5cf"
|
|
4566
4568
|
},
|
|
4567
4569
|
"sidebar-ring": {
|
|
4568
|
-
"$value": "#
|
|
4570
|
+
"$value": "#9422ae"
|
|
4569
4571
|
},
|
|
4570
4572
|
"primary-text": {
|
|
4571
4573
|
"$value": "#9c00c8"
|
|
@@ -4574,16 +4576,16 @@
|
|
|
4574
4576
|
"$value": "#d556ff21"
|
|
4575
4577
|
},
|
|
4576
4578
|
"secondary-text": {
|
|
4577
|
-
"$value": "#
|
|
4579
|
+
"$value": "#96388e"
|
|
4578
4580
|
},
|
|
4579
4581
|
"secondary-subtle": {
|
|
4580
|
-
"$value": "#
|
|
4582
|
+
"$value": "#ff05f821"
|
|
4581
4583
|
},
|
|
4582
4584
|
"tertiary-text": {
|
|
4583
|
-
"$value": "#
|
|
4585
|
+
"$value": "#a7392a"
|
|
4584
4586
|
},
|
|
4585
4587
|
"tertiary-subtle": {
|
|
4586
|
-
"$value": "#
|
|
4588
|
+
"$value": "#ff554021"
|
|
4587
4589
|
},
|
|
4588
4590
|
"chart-1-active": {
|
|
4589
4591
|
"$value": "#893ca2"
|
|
@@ -4675,49 +4677,49 @@
|
|
|
4675
4677
|
},
|
|
4676
4678
|
"dark": {
|
|
4677
4679
|
"background": {
|
|
4678
|
-
"$value": "#
|
|
4680
|
+
"$value": "#1f031d"
|
|
4679
4681
|
},
|
|
4680
4682
|
"foreground": {
|
|
4681
4683
|
"$value": "#fee7ff"
|
|
4682
4684
|
},
|
|
4683
4685
|
"surface": {
|
|
4684
|
-
"$value": "#
|
|
4686
|
+
"$value": "#2f0f2c"
|
|
4685
4687
|
},
|
|
4686
4688
|
"surface-foreground": {
|
|
4687
4689
|
"$value": "#fee7ff"
|
|
4688
4690
|
},
|
|
4689
4691
|
"popover": {
|
|
4690
|
-
"$value": "#
|
|
4692
|
+
"$value": "#381835"
|
|
4691
4693
|
},
|
|
4692
4694
|
"popover-foreground": {
|
|
4693
4695
|
"$value": "#fee7ff"
|
|
4694
4696
|
},
|
|
4695
4697
|
"primary": {
|
|
4696
|
-
"$value": "#
|
|
4698
|
+
"$value": "#ec86ff"
|
|
4697
4699
|
},
|
|
4698
4700
|
"primary-foreground": {
|
|
4699
4701
|
"$value": "#000000"
|
|
4700
4702
|
},
|
|
4701
4703
|
"secondary": {
|
|
4702
|
-
"$value": "#
|
|
4704
|
+
"$value": "#fd99ee"
|
|
4703
4705
|
},
|
|
4704
4706
|
"secondary-foreground": {
|
|
4705
|
-
"$value": "#
|
|
4707
|
+
"$value": "#651161"
|
|
4706
4708
|
},
|
|
4707
4709
|
"tertiary": {
|
|
4708
|
-
"$value": "#
|
|
4710
|
+
"$value": "#ff9381"
|
|
4709
4711
|
},
|
|
4710
4712
|
"tertiary-foreground": {
|
|
4711
|
-
"$value": "#
|
|
4713
|
+
"$value": "#640d05"
|
|
4712
4714
|
},
|
|
4713
4715
|
"muted": {
|
|
4714
|
-
"$value": "#
|
|
4716
|
+
"$value": "#42203d"
|
|
4715
4717
|
},
|
|
4716
4718
|
"muted-foreground": {
|
|
4717
4719
|
"$value": "#e0bae6"
|
|
4718
4720
|
},
|
|
4719
4721
|
"accent": {
|
|
4720
|
-
"$value": "#
|
|
4722
|
+
"$value": "#42203d"
|
|
4721
4723
|
},
|
|
4722
4724
|
"accent-foreground": {
|
|
4723
4725
|
"$value": "#e0bae6"
|
|
@@ -4732,13 +4734,13 @@
|
|
|
4732
4734
|
"$value": "#66486d"
|
|
4733
4735
|
},
|
|
4734
4736
|
"ring": {
|
|
4735
|
-
"$value": "#
|
|
4737
|
+
"$value": "#ec86ff"
|
|
4736
4738
|
},
|
|
4737
4739
|
"sidebar": {
|
|
4738
|
-
"$value": "#
|
|
4740
|
+
"$value": "#2f0f2c"
|
|
4739
4741
|
},
|
|
4740
4742
|
"card": {
|
|
4741
|
-
"$value": "#
|
|
4743
|
+
"$value": "#4b2947"
|
|
4742
4744
|
},
|
|
4743
4745
|
"card-foreground": {
|
|
4744
4746
|
"$value": "#fee7ff"
|
|
@@ -4765,22 +4767,22 @@
|
|
|
4765
4767
|
"$value": "#fee7ff"
|
|
4766
4768
|
},
|
|
4767
4769
|
"sidebar-primary": {
|
|
4768
|
-
"$value": "#
|
|
4770
|
+
"$value": "#ec86ff"
|
|
4769
4771
|
},
|
|
4770
4772
|
"sidebar-primary-foreground": {
|
|
4771
|
-
"$value": "#
|
|
4773
|
+
"$value": "#530065"
|
|
4772
4774
|
},
|
|
4773
4775
|
"sidebar-accent": {
|
|
4774
|
-
"$value": "#
|
|
4776
|
+
"$value": "#762371"
|
|
4775
4777
|
},
|
|
4776
4778
|
"sidebar-accent-foreground": {
|
|
4777
|
-
"$value": "#
|
|
4779
|
+
"$value": "#ffbbf1"
|
|
4778
4780
|
},
|
|
4779
4781
|
"sidebar-border": {
|
|
4780
4782
|
"$value": "#66486d"
|
|
4781
4783
|
},
|
|
4782
4784
|
"sidebar-ring": {
|
|
4783
|
-
"$value": "#
|
|
4785
|
+
"$value": "#ec86ff"
|
|
4784
4786
|
},
|
|
4785
4787
|
"primary-text": {
|
|
4786
4788
|
"$value": "#f0b0ff"
|
|
@@ -4789,16 +4791,16 @@
|
|
|
4789
4791
|
"$value": "#d556ff3d"
|
|
4790
4792
|
},
|
|
4791
4793
|
"secondary-text": {
|
|
4792
|
-
"$value": "#
|
|
4794
|
+
"$value": "#ffabf0"
|
|
4793
4795
|
},
|
|
4794
4796
|
"secondary-subtle": {
|
|
4795
|
-
"$value": "#
|
|
4797
|
+
"$value": "#ff05f83d"
|
|
4796
4798
|
},
|
|
4797
4799
|
"tertiary-text": {
|
|
4798
|
-
"$value": "#
|
|
4800
|
+
"$value": "#ffb4a8"
|
|
4799
4801
|
},
|
|
4800
4802
|
"tertiary-subtle": {
|
|
4801
|
-
"$value": "#
|
|
4803
|
+
"$value": "#ff55403d"
|
|
4802
4804
|
},
|
|
4803
4805
|
"chart-1-active": {
|
|
4804
4806
|
"$value": "#f0b0ff"
|
|
@@ -51,6 +51,7 @@ interface ColorPresetSource {
|
|
|
51
51
|
/** Force a white primary label even when preserving the seed would choose black. */
|
|
52
52
|
label?: 'white';
|
|
53
53
|
gate?: ColorPresetGate;
|
|
54
|
+
tokens?: PresetTokenOverrides;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
/**
|
|
@@ -157,11 +158,63 @@ const COLOR_PRESET_SOURCES = [
|
|
|
157
158
|
name: 'oxy',
|
|
158
159
|
displayName: 'Oxy',
|
|
159
160
|
family: 'brand',
|
|
160
|
-
description: 'Oxy
|
|
161
|
+
description: 'Oxy orchid with a magenta support and a terracotta action, reserved for the Oxy account identity.',
|
|
161
162
|
hex: '#c46ede',
|
|
163
|
+
secondaryHex: '#8f3987',
|
|
164
|
+
tertiaryHex: '#a0392b',
|
|
162
165
|
variant: 'vivid',
|
|
163
|
-
pairing: '
|
|
166
|
+
pairing: 'curated',
|
|
164
167
|
gate: 'handle',
|
|
168
|
+
// Hand-authored from the Oxy logo's own Material 3 scheme (plum page, pink
|
|
169
|
+
// surfaces, orchid/magenta/terracotta trio). The accent fills and their
|
|
170
|
+
// labels are that scheme's roles verbatim. The surface ladder keeps each
|
|
171
|
+
// role's hue and chroma (capped at Bloom's 28) but sits on Bloom's
|
|
172
|
+
// four-tone ramp, since the scheme's own two-tone steps fall below what the
|
|
173
|
+
// eye separates (`SURFACE_RAMP`, color-policy.ts). Text, borders and status
|
|
174
|
+
// stay derived: the policy is what holds them at AA on that deeper ladder.
|
|
175
|
+
tokens: {
|
|
176
|
+
light: {
|
|
177
|
+
'--background': '#f7ebf0',
|
|
178
|
+
'--surface': '#f1dde9',
|
|
179
|
+
'--popover': '#edcee4',
|
|
180
|
+
'--card': '#fff7f9',
|
|
181
|
+
'--muted': '#e7c1dd',
|
|
182
|
+
'--accent': '#e7c1dd',
|
|
183
|
+
'--primary': '#9422ae',
|
|
184
|
+
'--primary-foreground': '#ffeefc',
|
|
185
|
+
'--ring': '#9422ae',
|
|
186
|
+
'--secondary': '#8f3987',
|
|
187
|
+
'--secondary-foreground': '#ffeef8',
|
|
188
|
+
'--tertiary': '#a0392b',
|
|
189
|
+
'--tertiary-foreground': '#ffefed',
|
|
190
|
+
'--sidebar': '#f1dde9',
|
|
191
|
+
'--sidebar-primary': '#9422ae',
|
|
192
|
+
'--sidebar-primary-foreground': '#ffeefc',
|
|
193
|
+
'--sidebar-accent': '#ffbdf1',
|
|
194
|
+
'--sidebar-accent-foreground': '#772371',
|
|
195
|
+
'--sidebar-ring': '#9422ae',
|
|
196
|
+
},
|
|
197
|
+
dark: {
|
|
198
|
+
'--background': '#1f031d',
|
|
199
|
+
'--surface': '#2f0f2c',
|
|
200
|
+
'--popover': '#381835',
|
|
201
|
+
'--card': '#4b2947',
|
|
202
|
+
'--muted': '#42203d',
|
|
203
|
+
'--accent': '#42203d',
|
|
204
|
+
'--primary': '#ec86ff',
|
|
205
|
+
'--ring': '#ec86ff',
|
|
206
|
+
'--secondary': '#fd99ee',
|
|
207
|
+
'--secondary-foreground': '#651161',
|
|
208
|
+
'--tertiary': '#ff9381',
|
|
209
|
+
'--tertiary-foreground': '#640d05',
|
|
210
|
+
'--sidebar': '#2f0f2c',
|
|
211
|
+
'--sidebar-primary': '#ec86ff',
|
|
212
|
+
'--sidebar-primary-foreground': '#530065',
|
|
213
|
+
'--sidebar-accent': '#762371',
|
|
214
|
+
'--sidebar-accent-foreground': '#ffbbf1',
|
|
215
|
+
'--sidebar-ring': '#ec86ff',
|
|
216
|
+
},
|
|
217
|
+
},
|
|
165
218
|
},
|
|
166
219
|
{
|
|
167
220
|
name: 'faircoin',
|
|
@@ -782,6 +835,8 @@ export interface AppColorPreset {
|
|
|
782
835
|
tertiaryHex?: string;
|
|
783
836
|
label?: 'white';
|
|
784
837
|
gate?: ColorPresetGate;
|
|
838
|
+
/** Authored per-mode token values that win over the engine's derivation. */
|
|
839
|
+
tokens?: PresetTokenOverrides;
|
|
785
840
|
}
|
|
786
841
|
|
|
787
842
|
/** Enriched registry entry for a picker or colour playground. */
|
|
@@ -815,6 +870,7 @@ export const APP_COLOR_PRESETS: Record<AppColorName, AppColorPreset> =
|
|
|
815
870
|
...(preset.tertiaryHex !== undefined ? { tertiaryHex: preset.tertiaryHex } : {}),
|
|
816
871
|
...(preset.label !== undefined ? { label: preset.label } : {}),
|
|
817
872
|
...(preset.gate !== undefined ? { gate: preset.gate } : {}),
|
|
873
|
+
...(preset.tokens !== undefined ? { tokens: preset.tokens } : {}),
|
|
818
874
|
};
|
|
819
875
|
return presets;
|
|
820
876
|
}, {} as Record<AppColorName, AppColorPreset>);
|
|
@@ -857,6 +913,15 @@ export const COLOR_PRESET_GROUPS: Readonly<Record<ColorPresetFamily, ColorPreset
|
|
|
857
913
|
*/
|
|
858
914
|
export type PresetTokens = Record<string, string>;
|
|
859
915
|
|
|
916
|
+
/**
|
|
917
|
+
* A hand-authored brand scheme, per mode, as `#rrggbb` keyed by canonical token.
|
|
918
|
+
* Only for a brand that owns a drawn palette; every other preset stays derived.
|
|
919
|
+
*/
|
|
920
|
+
export interface PresetTokenOverrides {
|
|
921
|
+
light?: PresetTokens;
|
|
922
|
+
dark?: PresetTokens;
|
|
923
|
+
}
|
|
924
|
+
|
|
860
925
|
const namesGatedBy = (gate: ColorPresetGate | undefined): readonly AppColorName[] =>
|
|
861
926
|
COLOR_PRESET_REGISTRY.filter((preset) => preset.gate === gate).map(({ name }) => name);
|
|
862
927
|
|
package/src/theme/preset-vars.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { APP_COLOR_PRESETS, type AppColorName, type PresetTokens } from './color-presets';
|
|
2
|
-
import { generateRoleColors, type RoleColors } from './color-engine';
|
|
2
|
+
import { argbFromHex, generateRoleColors, type RoleColors } from './color-engine';
|
|
3
3
|
import { buildPolicyTokens } from './color-policy';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -112,9 +112,30 @@ export function getPresetVars(
|
|
|
112
112
|
secondary: accents?.secondaryHex ?? preset.secondaryHex,
|
|
113
113
|
tertiary: accents?.tertiaryHex ?? preset.tertiaryHex,
|
|
114
114
|
}),
|
|
115
|
+
...authoredTokens(preset.tokens?.[mode], accents),
|
|
115
116
|
};
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
/**
|
|
120
|
+
* A preset's hand-authored values, as the same `rgb(r g b)` form the engine
|
|
121
|
+
* emits. An app-wide accent override still wins over the authored accent it
|
|
122
|
+
* replaces, so the `secondary*`/`tertiary*` family is dropped when pinned.
|
|
123
|
+
*/
|
|
124
|
+
function authoredTokens(
|
|
125
|
+
authored: PresetTokens | undefined,
|
|
126
|
+
accents: ExplicitAccents | undefined,
|
|
127
|
+
): PresetTokens {
|
|
128
|
+
if (authored === undefined) return {};
|
|
129
|
+
const out: PresetTokens = {};
|
|
130
|
+
for (const [token, hex] of Object.entries(authored)) {
|
|
131
|
+
if (accents?.secondaryHex !== undefined && token.startsWith('--secondary')) continue;
|
|
132
|
+
if (accents?.tertiaryHex !== undefined && token.startsWith('--tertiary')) continue;
|
|
133
|
+
const argb = argbFromHex(hex);
|
|
134
|
+
out[token] = `rgb(${(argb >> 16) & 255} ${(argb >> 8) & 255} ${argb & 255})`;
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
|
|
118
139
|
/**
|
|
119
140
|
* Apply a preset's extended vars to `document.documentElement`. Web-only no-op
|
|
120
141
|
* on native. `BloomThemeProvider` already writes the base preset vars on web;
|