diff --git a/.gitignore b/.gitignore index 3c45938..dd6e803 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ +dist/ *.log .DS_Store diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..921d9b3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,323 @@ +{ + "name": "@cocotte/site-themes", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@cocotte/site-themes", + "version": "0.1.0", + "dependencies": { + "@cocotte/ui-theme": "^1.5.2" + }, + "devDependencies": { + "@types/react": "^19.2.8", + "typescript": "^5.9.3" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@cocotte/ui-theme": { + "version": "1.5.2", + "resolved": "http://134.199.243.61:4873/@cocotte/ui-theme/-/ui-theme-1.5.2.tgz", + "integrity": "sha512-6lwkBH+02slFtoWRapoLT8fal1rFBfN0ufa/sccGrGJ7Y8ahD8mtWMNq0yCsYSxM+VneNi/NBXnDlOGDTyyLOQ==", + "dependencies": { + "@lilith/ui-design-tokens": "^1.2.1", + "@lilith/ui-styled-components": "^6.3.9" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0", + "styled-components": "^6.0.0" + } + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "http://134.199.243.61:4873/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "http://134.199.243.61:4873/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "http://134.199.243.61:4873/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@lilith/ui-design-tokens": { + "version": "1.2.1", + "resolved": "http://134.199.243.61:4873/@lilith/ui-design-tokens/-/ui-design-tokens-1.2.1.tgz", + "integrity": "sha512-eOVzcfEHXp/k3Kt5qR7oEw+sz8QRfFUf5ssnqfpo/St7W5mtdrgokBhXuhY8KWjtuvV9PnRFpHyDC2fGxGUI3Q==" + }, + "node_modules/@lilith/ui-styled-components": { + "version": "6.3.9", + "resolved": "http://134.199.243.61:4873/@lilith/ui-styled-components/-/ui-styled-components-6.3.9.tgz", + "integrity": "sha512-vwgr0RwOZlPKW5KhP+A20yIMKW0nCdlYxy6yzdBDmqFEuf18y71mY4hwTJnVd5J4oWKf2nrMxIKkXFwj6qXLeA==", + "dependencies": { + "styled-components": "6.3.8" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@lilith/ui-styled-components/node_modules/styled-components": { + "version": "6.3.8", + "resolved": "http://134.199.243.61:4873/styled-components/-/styled-components-6.3.8.tgz", + "integrity": "sha512-Kq/W41AKQloOqKM39zfaMdJ4BcYDw/N5CIq4/GTI0YjU6pKcZ1KKhk6b4du0a+6RA9pIfOP/eu94Ge7cu+PDCA==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.4.0", + "@emotion/unitless": "0.10.0", + "@types/stylis": "4.2.7", + "css-to-react-native": "3.2.0", + "csstype": "3.2.3", + "postcss": "8.4.49", + "shallowequal": "1.1.0", + "stylis": "4.3.6", + "tslib": "2.8.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "http://134.199.243.61:4873/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/stylis": { + "version": "4.2.7", + "resolved": "http://134.199.243.61:4873/@types/stylis/-/stylis-4.2.7.tgz", + "integrity": "sha512-VgDNokpBoKF+wrdvhAAfS55OMQpL6QRglwTwNC3kIgBrzZxA4WsFj+2eLfEA/uMUDzBcEhYmjSbwQakn/i3ajA==", + "license": "MIT" + }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "http://134.199.243.61:4873/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "http://134.199.243.61:4873/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "http://134.199.243.61:4873/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "http://134.199.243.61:4873/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "http://134.199.243.61:4873/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "http://134.199.243.61:4873/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "http://134.199.243.61:4873/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "http://134.199.243.61:4873/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "http://134.199.243.61:4873/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "http://134.199.243.61:4873/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "http://134.199.243.61:4873/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "http://134.199.243.61:4873/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "http://134.199.243.61:4873/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/styled-components": { + "version": "6.4.3", + "resolved": "http://134.199.243.61:4873/styled-components/-/styled-components-6.4.3.tgz", + "integrity": "sha512-wYXrhu+JmDjZ1Tv7O0OopGTfztbzun43Pjjhh2H+xc0h5A09dwpZ5FJbrifJDcL8g5TA9btpWOX2+iSRuJTExw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@emotion/is-prop-valid": "1.4.0", + "css-to-react-native": "3.2.0", + "csstype": "3.2.3", + "stylis": "4.3.6" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "css-to-react-native": ">= 3.2.0", + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-native": ">= 0.68.0" + }, + "peerDependenciesMeta": { + "css-to-react-native": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "http://134.199.243.61:4873/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "http://134.199.243.61:4873/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "http://134.199.243.61:4873/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/package.json b/package.json index 5823b86..a20c8a2 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,23 @@ { "name": "@cocotte/site-themes", - "version": "0.1.0", + "version": "0.1.1", "description": "Cocotte site theme content: registry, resolver, and selectable theme families (luxe-dark, Kuromi, Cali-Barbie) built on @cocotte/ui-theme", "type": "module", - "main": "./src/index.ts", - "types": "./src/index.ts", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "exports": { - ".": "./src/index.ts" + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } }, "files": [ - "src" + "dist" ], "scripts": { - "typecheck": "tsc --noEmit" + "build": "tsc --project tsconfig.build.json", + "typecheck": "tsc --noEmit", + "prepublishOnly": "npm run build" }, "dependencies": { "@cocotte/ui-theme": "^1.5.2" diff --git a/src/barbie.ts b/src/barbie.ts index 9add558..ec2cbfe 100644 --- a/src/barbie.ts +++ b/src/barbie.ts @@ -25,7 +25,7 @@ * automatically — including the flip to a LIGHT surface for `barbie-light`. */ -import type { DeepPartial, ThemeInterface } from '@cocotte/ui-theme'; +import type { ThemeOverride } from './theme-override'; import { barbieTokens as b } from './barbie-tokens'; /** @@ -46,7 +46,7 @@ export const CALI_BODY_STACK = // Direction A — "Malibu" (LIGHT): bright blush/cream, deep-plum text, sunny pop. // --------------------------------------------------------------------------- -export const barbieLight: DeepPartial = { +export const barbieLight: ThemeOverride = { colors: { // On light, `primary.main` doubles as pink TEXT and as the CTA fill, so it // is the deeper `pinkDeep` (AA on white, and white labels pass on it). The @@ -106,7 +106,7 @@ export const barbieLight: DeepPartial = { // Direction B — "Sunset" (DARK): warm magenta night, blush-white text, glow. // --------------------------------------------------------------------------- -export const barbieDark: DeepPartial = { +export const barbieDark: ThemeOverride = { colors: { primary: { main: b.pink, dark: b.pinkDeep, light: b.pinkBright }, secondary: { main: b.sunshine, dark: b.sunshineDeep, light: b.sunshineBright }, diff --git a/src/kuromi.ts b/src/kuromi.ts index 922a4cb..bbe24d2 100644 --- a/src/kuromi.ts +++ b/src/kuromi.ts @@ -21,6 +21,7 @@ */ import type { DeepPartial, ThemeInterface } from '@cocotte/ui-theme'; +import type { ThemeColorsOverride } from './theme-override'; import { kuromiTokens as k } from './kuromi-tokens'; const INTER_STACK = @@ -31,7 +32,7 @@ const INTER_STACK = * Pink accent system on near-black; body text near-white for hierarchy and * AA legibility (pink is reserved for headings, accents, and CTA fills). */ -const kuromiColors: NonNullable['colors']> = { +const kuromiColors: ThemeColorsOverride = { // Pink is the brand primary (luxe base uses charcoal — fully overridden). primary: { main: k.pink, dark: k.pinkDeep, light: k.pinkBright }, secondary: { main: k.pinkBright, dark: k.pink, light: '#FFA8D4' }, diff --git a/src/registry.ts b/src/registry.ts index 7b36aa7..ce03722 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -46,8 +46,21 @@ export type SiteThemeName = | 'barbie-light' | 'barbie-dark'; +/** + * A resolved theme name, including the dynamic `'custom-mod'` live theme that is + * built at runtime (from a `?mod=` URL payload) and is NOT a static registry key. + */ +export type ResolvedThemeName = SiteThemeName | 'custom-mod'; + +declare global { + interface Window { + /** Build-time provider config injected by the host site; carries the brand-level default theme. */ + __PROVIDER_CONFIG__?: { theme?: { activeTheme?: string } }; + } +} + export interface SiteThemeDefinition { - name: SiteThemeName; + name: ResolvedThemeName; /** Human label for preview UIs / debugging. */ label: string; /** DeepPartial override deep-merged onto the base `luxe` adapter. */ @@ -315,8 +328,9 @@ export function resolveSiteTheme(): SiteThemeDefinition { ensureCustomModLoaded(); const previewName = readPreviewOverride(); if (previewName && THEME_REGISTRY[previewName]) { - // If a named preview is forced, clear any lingering custom mod (user intent via ?theme=) - if (liveCustomMod && previewName !== 'custom-mod') { + // A named preview is forced (previewName is always a static registry name, never + // 'custom-mod') → clear any lingering custom mod (user intent via ?theme=). + if (liveCustomMod) { liveCustomMod = null; } return THEME_REGISTRY[previewName]; diff --git a/src/theme-override.ts b/src/theme-override.ts new file mode 100644 index 0000000..c124b27 --- /dev/null +++ b/src/theme-override.ts @@ -0,0 +1,16 @@ +import type { DeepPartial, ThemeInterface } from '@cocotte/ui-theme'; + +/** + * A theme `customTheme` override that also permits `colors.onPrimary` — a + * foreground-on-primary token the base `luxe` adapter exposes (used by the + * Kuromi / Cali-Barbie families for AA-legible labels on pink CTA fills) but + * which `ThemeInterface.colors` does not yet declare. Widening here keeps the + * family theme objects type-checked without an `any` cast. + */ +export type ThemeColorsOverride = NonNullable['colors']> & { + onPrimary?: string; +}; + +export type ThemeOverride = DeepPartial & { + colors?: ThemeColorsOverride; +}; diff --git a/src/theme-switcher.ts b/src/theme-switcher.ts index 9a2416d..0a5ac31 100644 --- a/src/theme-switcher.ts +++ b/src/theme-switcher.ts @@ -36,7 +36,6 @@ import { setCustomMod, clearCustomMod, getCurrentCustomMod, - encodeThemeMod, type SiteThemeName, } from './registry'; diff --git a/src/theme-viewer.tsx b/src/theme-viewer.tsx index 7c01af6..40ea1dc 100644 --- a/src/theme-viewer.tsx +++ b/src/theme-viewer.tsx @@ -31,8 +31,8 @@ import { clearCustomMod, getCurrentCustomMod, resolveSiteTheme, - encodeThemeMod, cloneMod, + type SiteThemeName, } from './registry'; type Mod = DeepPartial; diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..fd41036 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "dist", + "rootDir": "src" + } +}