@oxyhq/bloom 0.35.8 → 0.37.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/lib/commonjs/frosted-icon-button/FrostedIconButton.js +148 -0
- package/lib/commonjs/frosted-icon-button/FrostedIconButton.js.map +1 -0
- package/lib/commonjs/frosted-icon-button/FrostedIconButton.web.js +166 -0
- package/lib/commonjs/frosted-icon-button/FrostedIconButton.web.js.map +1 -0
- package/lib/commonjs/frosted-icon-button/index.js +13 -0
- package/lib/commonjs/frosted-icon-button/index.js.map +1 -0
- package/lib/commonjs/frosted-icon-button/index.web.js +13 -0
- package/lib/commonjs/frosted-icon-button/index.web.js.map +1 -0
- package/lib/commonjs/frosted-icon-button/shared.js +112 -0
- package/lib/commonjs/frosted-icon-button/shared.js.map +1 -0
- package/lib/commonjs/frosted-icon-button/types.js +6 -0
- package/lib/commonjs/frosted-icon-button/types.js.map +1 -0
- package/lib/commonjs/index.js +10 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +46 -37
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/theme/build-theme.js +31 -43
- package/lib/commonjs/theme/build-theme.js.map +1 -1
- package/lib/commonjs/theme/color-engine/cam16.js +161 -0
- package/lib/commonjs/theme/color-engine/cam16.js.map +1 -0
- package/lib/commonjs/theme/color-engine/color-role.js +183 -0
- package/lib/commonjs/theme/color-engine/color-role.js.map +1 -0
- package/lib/commonjs/theme/color-engine/color-roles.js +416 -0
- package/lib/commonjs/theme/color-engine/color-roles.js.map +1 -0
- package/lib/commonjs/theme/color-engine/color-utils.js +157 -0
- package/lib/commonjs/theme/color-engine/color-utils.js.map +1 -0
- package/lib/commonjs/theme/color-engine/contrast-curve.js +32 -0
- package/lib/commonjs/theme/color-engine/contrast-curve.js.map +1 -0
- package/lib/commonjs/theme/color-engine/contrast.js +70 -0
- package/lib/commonjs/theme/color-engine/contrast.js.map +1 -0
- package/lib/commonjs/theme/color-engine/dislike-analyzer.js +26 -0
- package/lib/commonjs/theme/color-engine/dislike-analyzer.js.map +1 -0
- package/lib/commonjs/theme/color-engine/dynamic-scheme.js +70 -0
- package/lib/commonjs/theme/color-engine/dynamic-scheme.js.map +1 -0
- package/lib/commonjs/theme/color-engine/hct-solver.js +236 -0
- package/lib/commonjs/theme/color-engine/hct-solver.js.map +1 -0
- package/lib/commonjs/theme/color-engine/hct.js +67 -0
- package/lib/commonjs/theme/color-engine/hct.js.map +1 -0
- package/lib/commonjs/theme/color-engine/index.js +173 -0
- package/lib/commonjs/theme/color-engine/index.js.map +1 -0
- package/lib/commonjs/theme/color-engine/lab-point-provider.js +32 -0
- package/lib/commonjs/theme/color-engine/lab-point-provider.js.map +1 -0
- package/lib/commonjs/theme/color-engine/math-utils.js +77 -0
- package/lib/commonjs/theme/color-engine/math-utils.js.map +1 -0
- package/lib/commonjs/theme/color-engine/quantizer-map.js +24 -0
- package/lib/commonjs/theme/color-engine/quantizer-map.js.map +1 -0
- package/lib/commonjs/theme/color-engine/quantizer-wu.js +266 -0
- package/lib/commonjs/theme/color-engine/quantizer-wu.js.map +1 -0
- package/lib/commonjs/theme/color-engine/scheme-variants.js +102 -0
- package/lib/commonjs/theme/color-engine/scheme-variants.js.map +1 -0
- package/lib/commonjs/theme/color-engine/score.js +80 -0
- package/lib/commonjs/theme/color-engine/score.js.map +1 -0
- package/lib/commonjs/theme/color-engine/seed-from-image.js +73 -0
- package/lib/commonjs/theme/color-engine/seed-from-image.js.map +1 -0
- package/lib/commonjs/theme/color-engine/tonal-palette.js +95 -0
- package/lib/commonjs/theme/color-engine/tonal-palette.js.map +1 -0
- package/lib/commonjs/theme/color-engine/tone-delta-pair.js +26 -0
- package/lib/commonjs/theme/color-engine/tone-delta-pair.js.map +1 -0
- package/lib/commonjs/theme/color-engine/variant.js +26 -0
- package/lib/commonjs/theme/color-engine/variant.js.map +1 -0
- package/lib/commonjs/theme/color-engine/viewing-conditions.js +61 -0
- package/lib/commonjs/theme/color-engine/viewing-conditions.js.map +1 -0
- package/lib/commonjs/theme/color-presets.js +44 -572
- package/lib/commonjs/theme/color-presets.js.map +1 -1
- package/lib/commonjs/theme/preset-vars.js +64 -47
- package/lib/commonjs/theme/preset-vars.js.map +1 -1
- package/lib/module/frosted-icon-button/FrostedIconButton.js +143 -0
- package/lib/module/frosted-icon-button/FrostedIconButton.js.map +1 -0
- package/lib/module/frosted-icon-button/FrostedIconButton.web.js +161 -0
- package/lib/module/frosted-icon-button/FrostedIconButton.web.js.map +1 -0
- package/lib/module/frosted-icon-button/index.js +4 -0
- package/lib/module/frosted-icon-button/index.js.map +1 -0
- package/lib/module/frosted-icon-button/index.web.js +11 -0
- package/lib/module/frosted-icon-button/index.web.js.map +1 -0
- package/lib/module/frosted-icon-button/shared.js +107 -0
- package/lib/module/frosted-icon-button/shared.js.map +1 -0
- package/lib/module/frosted-icon-button/types.js +4 -0
- package/lib/module/frosted-icon-button/types.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +1 -0
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/theme/build-theme.js +32 -44
- package/lib/module/theme/build-theme.js.map +1 -1
- package/lib/module/theme/color-engine/cam16.js +155 -0
- package/lib/module/theme/color-engine/cam16.js.map +1 -0
- package/lib/module/theme/color-engine/color-role.js +177 -0
- package/lib/module/theme/color-engine/color-role.js.map +1 -0
- package/lib/module/theme/color-engine/color-roles.js +411 -0
- package/lib/module/theme/color-engine/color-roles.js.map +1 -0
- package/lib/module/theme/color-engine/color-utils.js +135 -0
- package/lib/module/theme/color-engine/color-utils.js.map +1 -0
- package/lib/module/theme/color-engine/contrast-curve.js +26 -0
- package/lib/module/theme/color-engine/contrast-curve.js.map +1 -0
- package/lib/module/theme/color-engine/contrast.js +61 -0
- package/lib/module/theme/color-engine/contrast.js.map +1 -0
- package/lib/module/theme/color-engine/dislike-analyzer.js +20 -0
- package/lib/module/theme/color-engine/dislike-analyzer.js.map +1 -0
- package/lib/module/theme/color-engine/dynamic-scheme.js +65 -0
- package/lib/module/theme/color-engine/dynamic-scheme.js.map +1 -0
- package/lib/module/theme/color-engine/hct-solver.js +231 -0
- package/lib/module/theme/color-engine/hct-solver.js.map +1 -0
- package/lib/module/theme/color-engine/hct.js +61 -0
- package/lib/module/theme/color-engine/hct.js.map +1 -0
- package/lib/module/theme/color-engine/index.js +57 -0
- package/lib/module/theme/color-engine/index.js.map +1 -0
- package/lib/module/theme/color-engine/lab-point-provider.js +25 -0
- package/lib/module/theme/color-engine/lab-point-provider.js.map +1 -0
- package/lib/module/theme/color-engine/math-utils.js +65 -0
- package/lib/module/theme/color-engine/math-utils.js.map +1 -0
- package/lib/module/theme/color-engine/quantizer-map.js +20 -0
- package/lib/module/theme/color-engine/quantizer-map.js.map +1 -0
- package/lib/module/theme/color-engine/quantizer-wu.js +261 -0
- package/lib/module/theme/color-engine/quantizer-wu.js.map +1 -0
- package/lib/module/theme/color-engine/scheme-variants.js +93 -0
- package/lib/module/theme/color-engine/scheme-variants.js.map +1 -0
- package/lib/module/theme/color-engine/score.js +75 -0
- package/lib/module/theme/color-engine/score.js.map +1 -0
- package/lib/module/theme/color-engine/seed-from-image.js +67 -0
- package/lib/module/theme/color-engine/seed-from-image.js.map +1 -0
- package/lib/module/theme/color-engine/tonal-palette.js +89 -0
- package/lib/module/theme/color-engine/tonal-palette.js.map +1 -0
- package/lib/module/theme/color-engine/tone-delta-pair.js +21 -0
- package/lib/module/theme/color-engine/tone-delta-pair.js.map +1 -0
- package/lib/module/theme/color-engine/variant.js +22 -0
- package/lib/module/theme/color-engine/variant.js.map +1 -0
- package/lib/module/theme/color-engine/viewing-conditions.js +55 -0
- package/lib/module/theme/color-engine/viewing-conditions.js.map +1 -0
- package/lib/module/theme/color-presets.js +44 -572
- package/lib/module/theme/color-presets.js.map +1 -1
- package/lib/module/theme/preset-vars.js +64 -46
- package/lib/module/theme/preset-vars.js.map +1 -1
- package/lib/typescript/commonjs/frosted-icon-button/FrostedIconButton.d.ts +5 -0
- package/lib/typescript/commonjs/frosted-icon-button/FrostedIconButton.d.ts.map +1 -0
- package/lib/typescript/commonjs/frosted-icon-button/FrostedIconButton.web.d.ts +5 -0
- package/lib/typescript/commonjs/frosted-icon-button/FrostedIconButton.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/frosted-icon-button/index.d.ts +3 -0
- package/lib/typescript/commonjs/frosted-icon-button/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/frosted-icon-button/index.web.d.ts +3 -0
- package/lib/typescript/commonjs/frosted-icon-button/index.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/frosted-icon-button/shared.d.ts +60 -0
- package/lib/typescript/commonjs/frosted-icon-button/shared.d.ts.map +1 -0
- package/lib/typescript/commonjs/frosted-icon-button/types.d.ts +69 -0
- package/lib/typescript/commonjs/frosted-icon-button/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +2 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +2 -0
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/build-theme.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/color-engine/cam16.d.ts +26 -0
- package/lib/typescript/commonjs/theme/color-engine/cam16.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/color-role.d.ts +36 -0
- package/lib/typescript/commonjs/theme/color-engine/color-role.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/color-roles.d.ts +58 -0
- package/lib/typescript/commonjs/theme/color-engine/color-roles.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/color-utils.d.ts +32 -0
- package/lib/typescript/commonjs/theme/color-engine/color-utils.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/contrast-curve.d.ts +9 -0
- package/lib/typescript/commonjs/theme/color-engine/contrast-curve.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/contrast.d.ts +12 -0
- package/lib/typescript/commonjs/theme/color-engine/contrast.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/dislike-analyzer.d.ts +11 -0
- package/lib/typescript/commonjs/theme/color-engine/dislike-analyzer.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/dynamic-scheme.d.ts +47 -0
- package/lib/typescript/commonjs/theme/color-engine/dynamic-scheme.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/hct-solver.d.ts +6 -0
- package/lib/typescript/commonjs/theme/color-engine/hct-solver.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/hct.d.ts +20 -0
- package/lib/typescript/commonjs/theme/color-engine/hct.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/index.d.ts +44 -0
- package/lib/typescript/commonjs/theme/color-engine/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/lab-point-provider.d.ts +6 -0
- package/lib/typescript/commonjs/theme/color-engine/lab-point-provider.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/math-utils.d.ts +46 -0
- package/lib/typescript/commonjs/theme/color-engine/math-utils.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/quantizer-map.d.ts +3 -0
- package/lib/typescript/commonjs/theme/color-engine/quantizer-map.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/quantizer-wu.d.ts +3 -0
- package/lib/typescript/commonjs/theme/color-engine/quantizer-wu.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/scheme-variants.d.ts +23 -0
- package/lib/typescript/commonjs/theme/color-engine/scheme-variants.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/score.d.ts +8 -0
- package/lib/typescript/commonjs/theme/color-engine/score.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/seed-from-image.d.ts +13 -0
- package/lib/typescript/commonjs/theme/color-engine/seed-from-image.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/tonal-palette.d.ts +24 -0
- package/lib/typescript/commonjs/theme/color-engine/tonal-palette.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/tone-delta-pair.d.ts +19 -0
- package/lib/typescript/commonjs/theme/color-engine/tone-delta-pair.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/variant.d.ts +19 -0
- package/lib/typescript/commonjs/theme/color-engine/variant.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-engine/viewing-conditions.d.ts +18 -0
- package/lib/typescript/commonjs/theme/color-engine/viewing-conditions.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/color-presets.d.ts +25 -20
- package/lib/typescript/commonjs/theme/color-presets.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/preset-vars.d.ts +16 -12
- package/lib/typescript/commonjs/theme/preset-vars.d.ts.map +1 -1
- package/lib/typescript/module/frosted-icon-button/FrostedIconButton.d.ts +5 -0
- package/lib/typescript/module/frosted-icon-button/FrostedIconButton.d.ts.map +1 -0
- package/lib/typescript/module/frosted-icon-button/FrostedIconButton.web.d.ts +5 -0
- package/lib/typescript/module/frosted-icon-button/FrostedIconButton.web.d.ts.map +1 -0
- package/lib/typescript/module/frosted-icon-button/index.d.ts +3 -0
- package/lib/typescript/module/frosted-icon-button/index.d.ts.map +1 -0
- package/lib/typescript/module/frosted-icon-button/index.web.d.ts +3 -0
- package/lib/typescript/module/frosted-icon-button/index.web.d.ts.map +1 -0
- package/lib/typescript/module/frosted-icon-button/shared.d.ts +60 -0
- package/lib/typescript/module/frosted-icon-button/shared.d.ts.map +1 -0
- package/lib/typescript/module/frosted-icon-button/types.d.ts +69 -0
- package/lib/typescript/module/frosted-icon-button/types.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +2 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +2 -0
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/lib/typescript/module/theme/build-theme.d.ts.map +1 -1
- package/lib/typescript/module/theme/color-engine/cam16.d.ts +26 -0
- package/lib/typescript/module/theme/color-engine/cam16.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/color-role.d.ts +36 -0
- package/lib/typescript/module/theme/color-engine/color-role.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/color-roles.d.ts +58 -0
- package/lib/typescript/module/theme/color-engine/color-roles.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/color-utils.d.ts +32 -0
- package/lib/typescript/module/theme/color-engine/color-utils.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/contrast-curve.d.ts +9 -0
- package/lib/typescript/module/theme/color-engine/contrast-curve.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/contrast.d.ts +12 -0
- package/lib/typescript/module/theme/color-engine/contrast.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/dislike-analyzer.d.ts +11 -0
- package/lib/typescript/module/theme/color-engine/dislike-analyzer.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/dynamic-scheme.d.ts +47 -0
- package/lib/typescript/module/theme/color-engine/dynamic-scheme.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/hct-solver.d.ts +6 -0
- package/lib/typescript/module/theme/color-engine/hct-solver.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/hct.d.ts +20 -0
- package/lib/typescript/module/theme/color-engine/hct.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/index.d.ts +44 -0
- package/lib/typescript/module/theme/color-engine/index.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/lab-point-provider.d.ts +6 -0
- package/lib/typescript/module/theme/color-engine/lab-point-provider.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/math-utils.d.ts +46 -0
- package/lib/typescript/module/theme/color-engine/math-utils.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/quantizer-map.d.ts +3 -0
- package/lib/typescript/module/theme/color-engine/quantizer-map.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/quantizer-wu.d.ts +3 -0
- package/lib/typescript/module/theme/color-engine/quantizer-wu.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/scheme-variants.d.ts +23 -0
- package/lib/typescript/module/theme/color-engine/scheme-variants.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/score.d.ts +8 -0
- package/lib/typescript/module/theme/color-engine/score.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/seed-from-image.d.ts +13 -0
- package/lib/typescript/module/theme/color-engine/seed-from-image.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/tonal-palette.d.ts +24 -0
- package/lib/typescript/module/theme/color-engine/tonal-palette.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/tone-delta-pair.d.ts +19 -0
- package/lib/typescript/module/theme/color-engine/tone-delta-pair.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/variant.d.ts +19 -0
- package/lib/typescript/module/theme/color-engine/variant.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-engine/viewing-conditions.d.ts +18 -0
- package/lib/typescript/module/theme/color-engine/viewing-conditions.d.ts.map +1 -0
- package/lib/typescript/module/theme/color-presets.d.ts +25 -20
- package/lib/typescript/module/theme/color-presets.d.ts.map +1 -1
- package/lib/typescript/module/theme/preset-vars.d.ts +16 -12
- package/lib/typescript/module/theme/preset-vars.d.ts.map +1 -1
- package/package.json +17 -1
- package/src/__tests__/BloomThemeProvider.test.tsx +9 -4
- package/src/__tests__/FrostedIconButton.test.tsx +137 -0
- package/src/__tests__/FrostedIconButton.web.test.tsx +246 -0
- package/src/__tests__/native-root-vars.test.ts +8 -4
- package/src/__tests__/preset-vars.test.ts +66 -37
- package/src/__tests__/theme.test.ts +20 -12
- package/src/frosted-icon-button/FrostedIconButton.stories.tsx +65 -0
- package/src/frosted-icon-button/FrostedIconButton.tsx +156 -0
- package/src/frosted-icon-button/FrostedIconButton.web.tsx +207 -0
- package/src/frosted-icon-button/index.ts +2 -0
- package/src/frosted-icon-button/index.web.ts +9 -0
- package/src/frosted-icon-button/shared.ts +118 -0
- package/src/frosted-icon-button/types.ts +81 -0
- package/src/index.ts +2 -0
- package/src/index.web.ts +2 -0
- package/src/theme/__tests__/__fixtures__/golden-resolved-tokens.json +509 -509
- package/src/theme/__tests__/__snapshots__/visual-gallery.test.tsx.snap +613 -613
- package/src/theme/__tests__/color-engine-golden.test.ts +81 -0
- package/src/theme/__tests__/fixtures/color-engine-golden.json +1 -0
- package/src/theme/__tests__/golden-tokens.test.ts +6 -7
- package/src/theme/__tests__/runtime-contract.test.ts +8 -8
- package/src/theme/__tests__/theme-colors-parity.test.ts +44 -88
- package/src/theme/build-theme.ts +32 -44
- package/src/theme/color-engine/cam16.ts +179 -0
- package/src/theme/color-engine/color-role.ts +217 -0
- package/src/theme/color-engine/color-roles.ts +435 -0
- package/src/theme/color-engine/color-utils.ts +157 -0
- package/src/theme/color-engine/contrast-curve.ts +25 -0
- package/src/theme/color-engine/contrast.ts +59 -0
- package/src/theme/color-engine/dislike-analyzer.ts +19 -0
- package/src/theme/color-engine/dynamic-scheme.ts +90 -0
- package/src/theme/color-engine/hct-solver.ts +263 -0
- package/src/theme/color-engine/hct.ts +69 -0
- package/src/theme/color-engine/index.ts +73 -0
- package/src/theme/color-engine/lab-point-provider.ts +26 -0
- package/src/theme/color-engine/math-utils.ts +69 -0
- package/src/theme/color-engine/quantizer-map.ts +17 -0
- package/src/theme/color-engine/quantizer-wu.ts +320 -0
- package/src/theme/color-engine/scheme-variants.ts +113 -0
- package/src/theme/color-engine/score.ts +82 -0
- package/src/theme/color-engine/seed-from-image.ts +64 -0
- package/src/theme/color-engine/tonal-palette.ts +94 -0
- package/src/theme/color-engine/tone-delta-pair.ts +21 -0
- package/src/theme/color-engine/variant.ts +18 -0
- package/src/theme/color-engine/viewing-conditions.ts +77 -0
- package/src/theme/color-presets.ts +54 -631
- package/src/theme/preset-vars.ts +65 -49
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { act } from 'react';
|
|
7
|
+
import { createRoot, type Root } from 'react-dom/client';
|
|
8
|
+
import { getByRole, fireEvent } from '@testing-library/dom';
|
|
9
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
10
|
+
import '@testing-library/jest-dom';
|
|
11
|
+
|
|
12
|
+
import { BloomThemeProvider } from '../theme/BloomThemeProvider';
|
|
13
|
+
import { FrostedIconButton } from '../frosted-icon-button/FrostedIconButton.web';
|
|
14
|
+
import { parseRgb } from '../theme/color-utils';
|
|
15
|
+
|
|
16
|
+
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
17
|
+
|
|
18
|
+
let container: HTMLDivElement;
|
|
19
|
+
let root: Root;
|
|
20
|
+
|
|
21
|
+
function mount(ui: React.ReactElement, mode: 'light' | 'dark' = 'dark'): HTMLElement {
|
|
22
|
+
act(() => {
|
|
23
|
+
root.render(
|
|
24
|
+
<BloomThemeProvider mode={mode} colorPreset="blue">
|
|
25
|
+
{ui}
|
|
26
|
+
</BloomThemeProvider>,
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
return container;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
container = document.createElement('div');
|
|
34
|
+
document.body.appendChild(container);
|
|
35
|
+
root = createRoot(container);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
act(() => root.unmount());
|
|
40
|
+
container.remove();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Mention's measured dark page background — the "invisible circle" bug composited
|
|
44
|
+
// a near-black `card` fill onto this and vanished. The frosted chip must read.
|
|
45
|
+
const PAGE_DARK = { r: 11, g: 11, b: 15 };
|
|
46
|
+
|
|
47
|
+
/** Composite an `rgba(...)` surface over an opaque background (source-over). */
|
|
48
|
+
function compositeOver(surface: string, bg: { r: number; g: number; b: number }) {
|
|
49
|
+
const base = parseRgb(surface);
|
|
50
|
+
const m = /rgba?\(([^)]+)\)/i.exec(surface);
|
|
51
|
+
const rawAlpha = m?.[1]?.split(',')[3];
|
|
52
|
+
const a = rawAlpha === undefined ? 1 : Number(rawAlpha);
|
|
53
|
+
if (!base) throw new Error(`not an rgba string: ${surface}`);
|
|
54
|
+
return {
|
|
55
|
+
r: Math.round(bg.r * (1 - a) + base.r * a),
|
|
56
|
+
g: Math.round(bg.g * (1 - a) + base.g * a),
|
|
57
|
+
b: Math.round(bg.b * (1 - a) + base.b * a),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
describe('FrostedIconButton.web', () => {
|
|
62
|
+
it('renders a real <button> with the accessible label', () => {
|
|
63
|
+
const c = mount(<FrostedIconButton accessibilityLabel="Back" icon={<span>x</span>} />);
|
|
64
|
+
const btn = getByRole(c, 'button', { name: 'Back' });
|
|
65
|
+
expect(btn.tagName).toBe('BUTTON');
|
|
66
|
+
expect(btn).toHaveClass('bloom-frosted-icon-btn');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('self-injects its interaction stylesheet once', () => {
|
|
70
|
+
mount(<FrostedIconButton accessibilityLabel="Back" icon={<span>x</span>} />);
|
|
71
|
+
mount(<FrostedIconButton accessibilityLabel="Fwd" icon={<span>y</span>} />);
|
|
72
|
+
expect(document.querySelectorAll('#bloom-frosted-icon-button-web-css')).toHaveLength(1);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// THE bug this component exists to prevent: on a dark SOLID page, a `card`-fill
|
|
76
|
+
// circle composited to near-black and vanished. Prove the frosted surface is a
|
|
77
|
+
// LIGHT tint that reads as a distinct, clearly-lighter circle on rgb(11,11,15).
|
|
78
|
+
describe('reads on a solid dark background (nothing behind it)', () => {
|
|
79
|
+
it('uses a low-opacity LIGHT tint distinguishable from the page', () => {
|
|
80
|
+
const c = mount(<FrostedIconButton accessibilityLabel="Back" icon={<span>x</span>} />);
|
|
81
|
+
const btn = getByRole(c, 'button');
|
|
82
|
+
const surface = btn.style.backgroundColor;
|
|
83
|
+
|
|
84
|
+
// Not the page color, and a genuine translucent (rgba) surface.
|
|
85
|
+
expect(surface).not.toBe('rgb(11, 11, 15)');
|
|
86
|
+
expect(surface).toMatch(/^rgba\(/);
|
|
87
|
+
|
|
88
|
+
// The tint's BASE color is light (each channel high) — a light frost, not a
|
|
89
|
+
// dark card fill.
|
|
90
|
+
const base = parseRgb(surface);
|
|
91
|
+
if (!base) throw new Error('unreachable');
|
|
92
|
+
expect(base.r).toBeGreaterThanOrEqual(200);
|
|
93
|
+
expect(base.g).toBeGreaterThanOrEqual(200);
|
|
94
|
+
expect(base.b).toBeGreaterThanOrEqual(200);
|
|
95
|
+
|
|
96
|
+
// Composited over the page it is clearly LIGHTER than rgb(11,11,15).
|
|
97
|
+
const composite = compositeOver(surface, PAGE_DARK);
|
|
98
|
+
expect(composite.r - PAGE_DARK.r).toBeGreaterThanOrEqual(15);
|
|
99
|
+
expect(composite.g - PAGE_DARK.g).toBeGreaterThanOrEqual(15);
|
|
100
|
+
expect(composite.b - PAGE_DARK.b).toBeGreaterThanOrEqual(15);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('renders a hairline ring and a shadow that survive dark mode', () => {
|
|
104
|
+
const c = mount(<FrostedIconButton accessibilityLabel="Back" icon={<span>x</span>} />);
|
|
105
|
+
const btn = getByRole(c, 'button');
|
|
106
|
+
// Ring: a translucent border COLOR is present inline (per-instance token).
|
|
107
|
+
expect(btn.style.borderColor).toMatch(/^rgba\(/);
|
|
108
|
+
// The hairline border-style/width live in the self-injected stylesheet.
|
|
109
|
+
const css = document.getElementById('bloom-frosted-icon-button-web-css')?.textContent ?? '';
|
|
110
|
+
expect(css).toMatch(/border-style:\s*solid/);
|
|
111
|
+
expect(css).toMatch(/border-width:\s*1px/);
|
|
112
|
+
// Shadow is NOT killed in dark mode.
|
|
113
|
+
expect(btn.style.boxShadow).not.toBe('');
|
|
114
|
+
expect(btn.style.boxShadow).toMatch(/rgba?\(/);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// jsdom's CSS engine drops `backdrop-filter`, so assert it via SSR style
|
|
118
|
+
// serialization — which emits EVERY style key exactly as the browser receives
|
|
119
|
+
// it (react-dom writes the same inline style on a real DOM <button>).
|
|
120
|
+
it('emits a real CSS backdrop-filter blur when frosted (and drops it when active)', () => {
|
|
121
|
+
const frosted = renderToStaticMarkup(
|
|
122
|
+
<BloomThemeProvider mode="dark" colorPreset="blue">
|
|
123
|
+
<FrostedIconButton accessibilityLabel="Back" icon={<span>x</span>} />
|
|
124
|
+
</BloomThemeProvider>,
|
|
125
|
+
);
|
|
126
|
+
expect(frosted).toMatch(/backdrop-filter:\s*blur\(\d+px\)/);
|
|
127
|
+
|
|
128
|
+
const active = renderToStaticMarkup(
|
|
129
|
+
<BloomThemeProvider mode="dark" colorPreset="blue">
|
|
130
|
+
<FrostedIconButton accessibilityLabel="Back" active icon={<span>x</span>} />
|
|
131
|
+
</BloomThemeProvider>,
|
|
132
|
+
);
|
|
133
|
+
// Active is solid: no blur (either absent or explicitly "none").
|
|
134
|
+
expect(active).not.toMatch(/backdrop-filter:\s*blur\(/);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('active (solid "on") state', () => {
|
|
139
|
+
it('fills opaque (no rgba alpha), drops the blur, and sets aria-pressed', () => {
|
|
140
|
+
const c = mount(<FrostedIconButton accessibilityLabel="Mute" active icon={<span>x</span>} />);
|
|
141
|
+
const btn = getByRole(c, 'button');
|
|
142
|
+
// Opaque solid fill — an `rgb(...)` (not translucent `rgba`).
|
|
143
|
+
expect(btn.style.backgroundColor).toMatch(/^rgb\(/);
|
|
144
|
+
expect(btn.style.backgroundColor).not.toMatch(/^rgba\(/);
|
|
145
|
+
expect(btn).toHaveAttribute('aria-pressed', 'true');
|
|
146
|
+
expect(btn).toHaveAttribute('data-active', 'true');
|
|
147
|
+
const bf = btn.style.getPropertyValue('backdrop-filter');
|
|
148
|
+
expect(bf === '' || bf === 'none').toBe(true);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('reports aria-pressed=false when not active', () => {
|
|
152
|
+
const c = mount(<FrostedIconButton accessibilityLabel="Mute" icon={<span>x</span>} />);
|
|
153
|
+
expect(getByRole(c, 'button')).toHaveAttribute('aria-pressed', 'false');
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('icon color', () => {
|
|
158
|
+
it('injects the theme icon color as a fallback fill on a bare icon', () => {
|
|
159
|
+
const c = mount(
|
|
160
|
+
<FrostedIconButton accessibilityLabel="Back" icon={<svg data-testid="ic" />} />,
|
|
161
|
+
);
|
|
162
|
+
const icon = c.querySelector('[data-testid="ic"]');
|
|
163
|
+
expect(icon?.getAttribute('fill')).toMatch(/^rgb/);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('never overrides an explicit fill on the icon', () => {
|
|
167
|
+
const c = mount(
|
|
168
|
+
<FrostedIconButton
|
|
169
|
+
accessibilityLabel="Back"
|
|
170
|
+
icon={<svg data-testid="ic" fill="rgb(1, 2, 3)" />}
|
|
171
|
+
/>,
|
|
172
|
+
);
|
|
173
|
+
expect(c.querySelector('[data-testid="ic"]')?.getAttribute('fill')).toBe('rgb(1, 2, 3)');
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('light theme', () => {
|
|
178
|
+
it('uses a subtle DARK tint (base near-black) distinguishable from a white page', () => {
|
|
179
|
+
const c = mount(
|
|
180
|
+
<FrostedIconButton accessibilityLabel="Back" icon={<span>x</span>} />,
|
|
181
|
+
'light',
|
|
182
|
+
);
|
|
183
|
+
const surface = getByRole(c, 'button').style.backgroundColor;
|
|
184
|
+
expect(surface).toMatch(/^rgba\(/);
|
|
185
|
+
const base = parseRgb(surface);
|
|
186
|
+
if (!base) throw new Error('unreachable');
|
|
187
|
+
expect(base.r).toBeLessThanOrEqual(60);
|
|
188
|
+
expect(base.g).toBeLessThanOrEqual(60);
|
|
189
|
+
expect(base.b).toBeLessThanOrEqual(60);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('behavior', () => {
|
|
194
|
+
it('fires both onClick and onPress on click', () => {
|
|
195
|
+
const onClick = jest.fn();
|
|
196
|
+
const onPress = jest.fn();
|
|
197
|
+
const c = mount(
|
|
198
|
+
<FrostedIconButton
|
|
199
|
+
accessibilityLabel="Back"
|
|
200
|
+
onClick={onClick}
|
|
201
|
+
onPress={onPress}
|
|
202
|
+
icon={<span>x</span>}
|
|
203
|
+
/>,
|
|
204
|
+
);
|
|
205
|
+
act(() => {
|
|
206
|
+
fireEvent.click(getByRole(c, 'button'));
|
|
207
|
+
});
|
|
208
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
209
|
+
expect(onPress).toHaveBeenCalledTimes(1);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('does not fire handlers when disabled', () => {
|
|
213
|
+
const onPress = jest.fn();
|
|
214
|
+
const c = mount(
|
|
215
|
+
<FrostedIconButton accessibilityLabel="Back" disabled onPress={onPress} icon={<span>x</span>} />,
|
|
216
|
+
);
|
|
217
|
+
const btn = getByRole(c, 'button');
|
|
218
|
+
expect(btn).toBeDisabled();
|
|
219
|
+
act(() => {
|
|
220
|
+
fireEvent.click(btn);
|
|
221
|
+
});
|
|
222
|
+
expect(onPress).not.toHaveBeenCalled();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('applies preset sizes (md = 36px, sm = 32px)', () => {
|
|
226
|
+
const c = mount(<FrostedIconButton accessibilityLabel="Back" size="md" icon={<span>x</span>} />);
|
|
227
|
+
expect(getByRole(c, 'button').style.width).toBe('36px');
|
|
228
|
+
const c2 = mount(<FrostedIconButton accessibilityLabel="Back" size="sm" icon={<span>x</span>} />);
|
|
229
|
+
expect(getByRole(c2, 'button').style.width).toBe('32px');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('does not crash on an array-form style prop (StyleProp flattening)', () => {
|
|
233
|
+
const showBorder = false;
|
|
234
|
+
const c = mount(
|
|
235
|
+
<FrostedIconButton
|
|
236
|
+
accessibilityLabel="Back"
|
|
237
|
+
icon={<span>x</span>}
|
|
238
|
+
style={[{ marginTop: 8 }, showBorder && { borderColor: 'rgb(9, 9, 9)' }]}
|
|
239
|
+
/>,
|
|
240
|
+
);
|
|
241
|
+
const btn = getByRole(c, 'button');
|
|
242
|
+
expect(btn.style.marginTop).toBe('8px');
|
|
243
|
+
expect(btn.getAttribute('style') ?? '').not.toMatch(/(^|;)\s*0\s*:/);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
});
|
|
@@ -53,12 +53,16 @@ describe('applyNativeRootVars (native variant)', () => {
|
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
// Keys are bare (no `--`), and the value is the single canonical sRGB
|
|
56
|
-
// `rgb(...)` color from `getResolvedTokens` (
|
|
57
|
-
// sRGB is the space react-native-css's `color-mix` resolver registers,
|
|
58
|
-
// alpha utilities (`bg-primary/10`) resolve on native.
|
|
56
|
+
// `rgb(...)` color from `getResolvedTokens` (now engine-derived from the blue
|
|
57
|
+
// seed). sRGB is the space react-native-css's `color-mix` resolver registers,
|
|
58
|
+
// so alpha utilities (`bg-primary/10`) resolve on native.
|
|
59
|
+
const { getResolvedTokens } = require('../theme/token-registry') as {
|
|
60
|
+
getResolvedTokens: (preset: string, mode: 'light' | 'dark') => Record<string, string>;
|
|
61
|
+
};
|
|
62
|
+
const expectedPrimary = getResolvedTokens('blue', 'light')['--primary'];
|
|
59
63
|
expect(sets).toHaveProperty('primary');
|
|
60
64
|
expect(sets).not.toHaveProperty('--primary');
|
|
61
|
-
expect(sets.primary).toEqual([[
|
|
65
|
+
expect(sets.primary).toEqual([[expectedPrimary]]);
|
|
62
66
|
|
|
63
67
|
// The legacy resolved-color companion universe is gone — the base token IS
|
|
64
68
|
// rgb, so no `color-primary` companion is written.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { APP_COLOR_NAMES
|
|
1
|
+
import { APP_COLOR_NAMES } from '../theme/color-presets';
|
|
2
2
|
import { getPresetVars } from '../theme/preset-vars';
|
|
3
3
|
import { getResolvedTokens } from '../theme/token-registry';
|
|
4
4
|
|
|
@@ -20,70 +20,99 @@ const EXTENDED_KEYS = [
|
|
|
20
20
|
'--sidebar-ring',
|
|
21
21
|
] as const;
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
const BASE_KEYS = [
|
|
24
|
+
'--background',
|
|
25
|
+
'--foreground',
|
|
26
|
+
'--surface',
|
|
27
|
+
'--surface-foreground',
|
|
28
|
+
'--popover',
|
|
29
|
+
'--popover-foreground',
|
|
30
|
+
'--primary',
|
|
31
|
+
'--primary-foreground',
|
|
32
|
+
'--secondary',
|
|
33
|
+
'--secondary-foreground',
|
|
34
|
+
'--muted',
|
|
35
|
+
'--muted-foreground',
|
|
36
|
+
'--accent',
|
|
37
|
+
'--accent-foreground',
|
|
38
|
+
'--destructive',
|
|
39
|
+
'--border',
|
|
40
|
+
'--input',
|
|
41
|
+
'--ring',
|
|
42
|
+
'--sidebar',
|
|
43
|
+
] as const;
|
|
44
|
+
|
|
45
|
+
describe('getPresetVars (engine-backed)', () => {
|
|
46
|
+
it('emits every base + extended token as a full rgb() color for all presets and modes', () => {
|
|
25
47
|
for (const name of APP_COLOR_NAMES) {
|
|
26
48
|
for (const mode of ['light', 'dark'] as const) {
|
|
27
49
|
const vars = getPresetVars(name, mode);
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
for (const key of Object.keys(base)) {
|
|
31
|
-
expect(vars[key]).toBe(base[key]);
|
|
32
|
-
}
|
|
33
|
-
for (const key of EXTENDED_KEYS) {
|
|
50
|
+
for (const key of [...BASE_KEYS, ...EXTENDED_KEYS]) {
|
|
34
51
|
expect(vars[key]).toBeDefined();
|
|
52
|
+
// The engine emits `rgb(r g b)` directly — never a bare HSL triple.
|
|
53
|
+
expect(vars[key]).toMatch(/^rgb\(\d+ \d+ \d+\)$/);
|
|
35
54
|
}
|
|
36
55
|
}
|
|
37
56
|
}
|
|
38
57
|
});
|
|
39
58
|
|
|
40
|
-
it('
|
|
59
|
+
it('drives sidebar-primary / ring from the engine primary role', () => {
|
|
41
60
|
const vars = getPresetVars('blue', 'light');
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
61
|
+
expect(vars['--sidebar-primary']).toBe(vars['--primary']);
|
|
62
|
+
expect(vars['--ring']).toBe(vars['--primary']);
|
|
63
|
+
expect(vars['--chart-1']).toBe(vars['--primary']);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('makes secondary a real contrast color, not a mirror of primary (the fix)', () => {
|
|
67
|
+
for (const name of APP_COLOR_NAMES) {
|
|
68
|
+
for (const mode of ['light', 'dark'] as const) {
|
|
69
|
+
const vars = getPresetVars(name, mode);
|
|
70
|
+
expect(vars['--secondary']).not.toBe(vars['--primary']);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
47
73
|
});
|
|
48
74
|
|
|
49
|
-
it('
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
75
|
+
it('makes card the lightest surface in light mode, darkest in dark (the fix)', () => {
|
|
76
|
+
const channels = (rgb: string): number[] =>
|
|
77
|
+
(rgb.match(/\d+/g) ?? []).map(Number);
|
|
78
|
+
const sum = (rgb: string): number => channels(rgb).reduce((a, b) => a + b, 0);
|
|
79
|
+
|
|
80
|
+
for (const name of APP_COLOR_NAMES) {
|
|
81
|
+
const light = getPresetVars(name, 'light');
|
|
82
|
+
// card (surfaceContainerLowest) must be lighter than background in light.
|
|
83
|
+
expect(sum(light['--card'] ?? '')).toBeGreaterThan(sum(light['--background'] ?? ''));
|
|
84
|
+
|
|
85
|
+
const dark = getPresetVars(name, 'dark');
|
|
86
|
+
// card must be darker than (or equal to) background in dark — correct inversion.
|
|
87
|
+
expect(sum(dark['--card'] ?? '')).toBeLessThanOrEqual(sum(dark['--background'] ?? ''));
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('carries no legacy resolved-color companion universe', () => {
|
|
53
92
|
const vars = getPresetVars('green', 'dark');
|
|
54
|
-
expect(vars['--primary']).toBe(APP_COLOR_PRESETS.green.dark['--primary']);
|
|
55
|
-
expect(/^-?\d[\d.]*\s+[\d.]+%/.test(vars['--primary'] ?? '')).toBe(true);
|
|
56
93
|
const legacyPrefix = `--${'color'}-`;
|
|
57
94
|
expect(Object.keys(vars).some((k) => k.startsWith(legacyPrefix))).toBe(false);
|
|
58
95
|
});
|
|
59
96
|
});
|
|
60
97
|
|
|
61
98
|
describe('getResolvedTokens (single canonical rgb pipeline)', () => {
|
|
62
|
-
it('
|
|
99
|
+
it('passes the engine rgb() values through unchanged', () => {
|
|
63
100
|
const resolved = getResolvedTokens('green', 'dark');
|
|
64
|
-
const
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
expect(
|
|
101
|
+
const raw = getPresetVars('green', 'dark');
|
|
102
|
+
// The engine already emits rgb(); getResolvedTokens must not re-touch it.
|
|
103
|
+
expect(resolved['--primary']).toBe(raw['--primary']);
|
|
104
|
+
expect(resolved['--card']).toBe(raw['--card']);
|
|
68
105
|
expect(resolved['--primary']).toMatch(/^rgb\(\d+ \d+ \d+\)$/);
|
|
69
106
|
expect(resolved['--card']).toMatch(/^rgb\(\d+ \d+ \d+\)$/);
|
|
70
|
-
|
|
71
|
-
expect(resolved
|
|
72
|
-
expect(primaryTriple).toMatch(/%/);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('resolves --primary to rgb for the blue preset (matches web reference)', () => {
|
|
76
|
-
// `blue` preset light `--primary` is `205 87% 53%` → rgb(31 153 239).
|
|
77
|
-
const resolved = getResolvedTokens('blue', 'light');
|
|
78
|
-
expect(resolved['--primary']).toBe('rgb(31 153 239)');
|
|
107
|
+
const legacyPrefix = `--${'color'}-`;
|
|
108
|
+
expect(Object.keys(resolved).some((k) => k.startsWith(legacyPrefix))).toBe(false);
|
|
79
109
|
});
|
|
80
110
|
|
|
81
111
|
it('produces a full CSS color for every base preset token of every preset/mode', () => {
|
|
82
112
|
for (const name of APP_COLOR_NAMES) {
|
|
83
113
|
for (const mode of ['light', 'dark'] as const) {
|
|
84
|
-
const base = mode === 'light' ? APP_COLOR_PRESETS[name].light : APP_COLOR_PRESETS[name].dark;
|
|
85
114
|
const resolved = getResolvedTokens(name, mode);
|
|
86
|
-
for (const key of
|
|
115
|
+
for (const key of BASE_KEYS) {
|
|
87
116
|
const value = resolved[key] ?? '';
|
|
88
117
|
// A bare triple (`185 50% 5%`) is INVALID CSS — the incident. Every
|
|
89
118
|
// base token must come out as a complete color the browser parses.
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
hexToAppColorName,
|
|
6
6
|
} from '../theme/color-presets';
|
|
7
7
|
import { buildTheme } from '../theme/build-theme';
|
|
8
|
+
import { getPresetVars } from '../theme/preset-vars';
|
|
8
9
|
import { THEME_GRADIENTS } from '../theme/gradients';
|
|
9
10
|
import type { Theme, ThemeColors, ThemeMode } from '../theme/types';
|
|
10
11
|
|
|
@@ -18,7 +19,9 @@ describe('Theme system', () => {
|
|
|
18
19
|
}
|
|
19
20
|
});
|
|
20
21
|
|
|
21
|
-
it('each preset
|
|
22
|
+
it('each preset generates both light and dark canonical tokens from its seed', () => {
|
|
23
|
+
// The palette is now engine-derived from the seed; every preset must
|
|
24
|
+
// produce a full canonical token set in both modes via `getPresetVars`.
|
|
22
25
|
const requiredVars = [
|
|
23
26
|
'--background',
|
|
24
27
|
'--foreground',
|
|
@@ -32,31 +35,36 @@ describe('Theme system', () => {
|
|
|
32
35
|
];
|
|
33
36
|
|
|
34
37
|
for (const name of APP_COLOR_NAMES) {
|
|
35
|
-
const
|
|
38
|
+
const light = getPresetVars(name, 'light');
|
|
39
|
+
const dark = getPresetVars(name, 'dark');
|
|
36
40
|
for (const v of requiredVars) {
|
|
37
|
-
expect(
|
|
38
|
-
expect(
|
|
41
|
+
expect(light[v]).toBeDefined();
|
|
42
|
+
expect(dark[v]).toBeDefined();
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
});
|
|
42
46
|
|
|
43
47
|
it('light and dark variants have different values', () => {
|
|
44
48
|
for (const name of APP_COLOR_NAMES) {
|
|
45
|
-
const preset = APP_COLOR_PRESETS[name]!;
|
|
46
49
|
// Background should differ between light and dark
|
|
47
|
-
expect(
|
|
50
|
+
expect(getPresetVars(name, 'light')['--background']).not.toBe(
|
|
51
|
+
getPresetVars(name, 'dark')['--background'],
|
|
52
|
+
);
|
|
48
53
|
}
|
|
49
54
|
});
|
|
50
55
|
});
|
|
51
56
|
|
|
52
57
|
describe('buildTheme primaryForeground', () => {
|
|
53
58
|
it('resolves a readable foreground for the primary fill per preset', () => {
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
// Every preset's primary foreground is the engine's `on-primary` role,
|
|
60
|
+
// surfaced as `--primary-foreground` — a legible on-primary contrast color.
|
|
61
|
+
for (const name of APP_COLOR_NAMES) {
|
|
62
|
+
for (const mode of ['light', 'dark'] as const) {
|
|
63
|
+
expect(buildTheme(name, mode).colors.primaryForeground).toBe(
|
|
64
|
+
getPresetVars(name, mode)['--primary-foreground'],
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
60
68
|
});
|
|
61
69
|
|
|
62
70
|
it('sets a primaryForeground for every preset in both modes', () => {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, ImageBackground } from 'react-native';
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
4
|
+
|
|
5
|
+
import { FrostedIconButton } from './FrostedIconButton';
|
|
6
|
+
import * as Icons from '../icons';
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof FrostedIconButton> = {
|
|
9
|
+
title: 'Components/FrostedIconButton',
|
|
10
|
+
component: FrostedIconButton,
|
|
11
|
+
args: {
|
|
12
|
+
accessibilityLabel: 'Back',
|
|
13
|
+
icon: <Icons.ChevronLeft_Stroke2_Corner0_Rounded size="md" />,
|
|
14
|
+
onPress: () => {},
|
|
15
|
+
},
|
|
16
|
+
argTypes: {
|
|
17
|
+
size: { control: 'select', options: ['sm', 'md'] },
|
|
18
|
+
active: { control: 'boolean' },
|
|
19
|
+
disabled: { control: 'boolean' },
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default meta;
|
|
24
|
+
|
|
25
|
+
type Story = StoryObj<typeof FrostedIconButton>;
|
|
26
|
+
|
|
27
|
+
/** The frosted chip on a SOLID dark background — must read as a distinct button. */
|
|
28
|
+
export const OnSolidDark: Story = {
|
|
29
|
+
render: (args) => (
|
|
30
|
+
<View style={{ padding: 40, backgroundColor: 'rgb(11, 11, 15)' }}>
|
|
31
|
+
<FrostedIconButton {...args} />
|
|
32
|
+
</View>
|
|
33
|
+
),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** The solid "on" state for toggles (no blur, brand fill). */
|
|
37
|
+
export const Active: Story = {
|
|
38
|
+
render: (args) => (
|
|
39
|
+
<View style={{ padding: 40, backgroundColor: 'rgb(11, 11, 15)' }}>
|
|
40
|
+
<FrostedIconButton {...args} active />
|
|
41
|
+
</View>
|
|
42
|
+
),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Frosting over an image (the reason it exists) — blur shows the banner behind. */
|
|
46
|
+
export const OverImage: Story = {
|
|
47
|
+
render: (args) => (
|
|
48
|
+
<ImageBackground
|
|
49
|
+
source={{ uri: 'https://picsum.photos/600/300' }}
|
|
50
|
+
style={{ width: 320, height: 180, padding: 16, justifyContent: 'flex-start' }}
|
|
51
|
+
>
|
|
52
|
+
<FrostedIconButton {...args} />
|
|
53
|
+
</ImageBackground>
|
|
54
|
+
),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Both sizes side by side. */
|
|
58
|
+
export const Sizes: Story = {
|
|
59
|
+
render: (args) => (
|
|
60
|
+
<View style={{ flexDirection: 'row', gap: 12, padding: 40, backgroundColor: 'rgb(11, 11, 15)' }}>
|
|
61
|
+
<FrostedIconButton {...args} size="sm" />
|
|
62
|
+
<FrostedIconButton {...args} size="md" />
|
|
63
|
+
</View>
|
|
64
|
+
),
|
|
65
|
+
};
|