@transtyle/exporter-daisyui 0.1.0-alpha.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.
Files changed (2) hide show
  1. package/package.json +42 -0
  2. package/src/index.js +166 -0
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@transtyle/exporter-daisyui",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "Transtyle exporter for daisyUI themes (v5, Tailwind 4 era).",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js"
9
+ },
10
+ "dependencies": {
11
+ "@transtyle/ir": "0.1.0-alpha.0"
12
+ },
13
+ "transtyle": {
14
+ "kind": "exporter",
15
+ "name": "daisyui",
16
+ "irSpec": "v0-draft",
17
+ "pluginApi": "0",
18
+ "targets": {
19
+ "daisyui": [
20
+ ">=5 <6"
21
+ ]
22
+ },
23
+ "modes": [
24
+ "color-scheme"
25
+ ],
26
+ "capabilities": [
27
+ "build"
28
+ ]
29
+ },
30
+ "files": [
31
+ "src"
32
+ ],
33
+ "publishConfig": { "access": "public" },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/transtyle/transtyle.git",
37
+ "directory": "packages/exporter-daisyui"
38
+ },
39
+ "homepage": "https://github.com/transtyle/transtyle#readme",
40
+ "bugs": "https://github.com/transtyle/transtyle/issues",
41
+ "license": "MIT"
42
+ }
package/src/index.js ADDED
@@ -0,0 +1,166 @@
1
+ /**
2
+ * @transtyle/exporter-daisyui — emits daisyUI v5 theme blocks
3
+ * (`@plugin "daisyui/theme" { … }`, Tailwind 4 era, OKLCH-native).
4
+ *
5
+ * Notable vs shadcn: daisyUI's `secondary`/`accent` are true BRAND roles —
6
+ * they map from our brand slots directly, not from subtle surfaces. Same
7
+ * words, different meanings per ecosystem; the mapping tables encode it
8
+ * (docs/language.md "false friends").
9
+ */
10
+
11
+ import { droppedDimensions } from '@transtyle/ir';
12
+
13
+ const S = 'semantic.color.';
14
+
15
+ /** slot → daisyUI variable; content = paired foreground slot. */
16
+ const COLOR_MAPPING = [
17
+ { css: '--color-base-100', slot: `${S}elevation.0.surface`, cls: 'native' },
18
+ { css: '--color-base-200', slot: `${S}elevation.1.surface`, cls: 'native' },
19
+ // base-300 is the third step of a background ramp; border is the closest tone we have
20
+ { css: '--color-base-300', slot: `${S}border`, cls: 'approximated', note: 'bg-ramp step ← border tone' },
21
+ { css: '--color-base-content', slot: `${S}text.base`, cls: 'native' },
22
+ { css: '--color-primary', slot: `${S}primary.solid`, cls: 'native' },
23
+ { css: '--color-primary-content', slot: `${S}primary.on-solid`, cls: 'native' },
24
+ { css: '--color-secondary', slot: `${S}secondary.solid`, cls: 'native', note: 'true brand secondary (unlike shadcn)' },
25
+ { css: '--color-secondary-content', slot: `${S}secondary.on-solid`, cls: 'native' },
26
+ { css: '--color-accent', slot: `${S}accent.solid`, cls: 'native' },
27
+ { css: '--color-accent-content', slot: `${S}accent.on-solid`, cls: 'native' },
28
+ { css: '--color-neutral', slot: `${S}neutral.solid`, cls: 'native' },
29
+ { css: '--color-neutral-content', slot: `${S}neutral.on-solid`, cls: 'native' },
30
+ { css: '--color-info', slot: `${S}info.solid`, cls: 'native' },
31
+ { css: '--color-info-content', slot: `${S}info.on-solid`, cls: 'native' },
32
+ { css: '--color-success', slot: `${S}success.solid`, cls: 'native' },
33
+ { css: '--color-success-content', slot: `${S}success.on-solid`, cls: 'native' },
34
+ { css: '--color-warning', slot: `${S}warning.solid`, cls: 'native' },
35
+ { css: '--color-warning-content', slot: `${S}warning.on-solid`, cls: 'native' },
36
+ { css: '--color-error', slot: `${S}danger.solid`, cls: 'native', note: 'name translation: danger → error' },
37
+ { css: '--color-error-content', slot: `${S}danger.on-solid`, cls: 'native' },
38
+ ];
39
+
40
+ export default {
41
+ name: 'daisyui',
42
+
43
+ // Validated by core against the target's `options` at load time (audit A8).
44
+ optionsSchema: {
45
+ type: 'object',
46
+ additionalProperties: false,
47
+ properties: { era: { type: 'string', enum: ['v5'] } },
48
+ },
49
+
50
+ emit(normalized, ctx) {
51
+ const era = ctx.targetConfig.options?.era ?? 'v5';
52
+ if (era !== 'v5') throw new Error(`exporter-daisyui skeleton supports era "v5" only (got "${era}")`);
53
+
54
+ // Mode polarity rule: bind mode names, never the default flag.
55
+ const light = normalized.modes.light ?? normalized.modes[normalized.defaultMode];
56
+ const dark = normalized.modes.dark;
57
+
58
+ const coverage = [];
59
+ const radius = light.get('semantic.radius.md');
60
+ const blocks = [];
61
+
62
+ const themeBlock = (map, mode, flags) => {
63
+ const lines = [
64
+ `@plugin "daisyui/theme" {`,
65
+ ` name: "${ctx.projectName}-${mode}";`,
66
+ ...flags,
67
+ ` color-scheme: ${mode};`,
68
+ ];
69
+ for (const m of COLOR_MAPPING) {
70
+ const entry = map.get(m.slot);
71
+ if (!entry?.value) {
72
+ if (mode === 'light') coverage.push({ variable: m.css, slot: m.slot, class: 'unsupported', note: 'slot missing from IR' });
73
+ continue;
74
+ }
75
+ if (mode === 'light') {
76
+ const provKind = entry.provenance.kind;
77
+ const cls = m.cls === 'approximated' ? 'approximated' : (provKind === 'derived' ? 'derived' : m.cls);
78
+ coverage.push({ variable: m.css, slot: m.slot, class: cls, provenance: provKind, ...(m.note && { note: m.note }) });
79
+ }
80
+ lines.push(` ${m.css}: ${ctx.formatColor(entry.value)}; /* ${m.slot.replace('semantic.', '')} */`);
81
+ }
82
+ // Custom archetyped roles (T7): daisyUI has an open color set — any
83
+ // `--color-<name>` custom property becomes a usable utility color.
84
+ for (const name of normalized.roleArchetypes.keys()) {
85
+ const solid = map.get(`${S}${name}.solid`);
86
+ if (!solid?.value) continue;
87
+ const onSolid = map.get(`${S}${name}.on-solid`);
88
+ if (mode === 'light') {
89
+ coverage.push({ variable: `--color-${name}`, slot: `${S}${name}.solid`, class: 'native', note: 'custom role archetype (open role set)' });
90
+ }
91
+ lines.push(` --color-${name}: ${ctx.formatColor(solid.value)}; /* ${name}.solid */`);
92
+ if (onSolid?.value) {
93
+ if (mode === 'light') coverage.push({ variable: `--color-${name}-content`, slot: `${S}${name}.on-solid`, class: 'native' });
94
+ lines.push(` --color-${name}-content: ${ctx.formatColor(onSolid.value)}; /* ${name}.on-solid */`);
95
+ }
96
+ }
97
+ if (radius) {
98
+ // daisyUI splits radius by component family; one authored radius feeds all three
99
+ lines.push(` --radius-selector: ${radius.value};`);
100
+ lines.push(` --radius-field: ${radius.value};`);
101
+ lines.push(` --radius-box: ${radius.value};`);
102
+ }
103
+ lines.push('}');
104
+ return lines.join('\n');
105
+ };
106
+
107
+ blocks.push(themeBlock(light, 'light', [' default: true;', ' prefersdark: false;']));
108
+ if (dark) blocks.push(themeBlock(dark, 'dark', [' prefersdark: true;']));
109
+
110
+ if (radius) {
111
+ coverage.push({ variable: '--radius-{selector,field,box}', slot: 'semantic.radius.md', class: 'approximated', provenance: radius.provenance.kind, note: 'one radius feeds three component families' });
112
+ }
113
+ coverage.push({ variable: '--depth / --noise / --size-*', slot: '—', class: 'dropped', note: 'daisyUI stylistic effects with no token semantics; theme uses daisyUI defaults' });
114
+ // Mode dimensions this exporter doesn't express (T8, ir.md#modes) — a
115
+ // no-op unless the compile actually declares one, e.g. `density`.
116
+ coverage.push(...droppedDimensions(normalized.dimensionNames, ['color-scheme']));
117
+
118
+ const css = [
119
+ '/*',
120
+ ` * GENERATED by transtyle — do not edit; source: ${ctx.projectName} token files`,
121
+ ' * Target: daisyUI (era: v5, Tailwind 4) · rules standard@1 (skeleton subset)',
122
+ ' */',
123
+ '',
124
+ ...blocks,
125
+ '',
126
+ ].join('\n');
127
+
128
+ return {
129
+ files: [
130
+ { path: 'daisyui.transtyle.css', contents: css, kind: 'stylesheet' },
131
+ { path: 'usage.md', contents: renderUsage(ctx, coverage), kind: 'doc' },
132
+ ],
133
+ coverage,
134
+ };
135
+ },
136
+ };
137
+
138
+ function renderUsage(ctx, coverage) {
139
+ const counts = {};
140
+ for (const c of coverage) counts[c.class] = (counts[c.class] ?? 0) + 1;
141
+ const summary = Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(' · ');
142
+ return `# Using this daisyUI theme
143
+
144
+ Generated from the **${ctx.projectName}** design system by transtyle (daisyUI v5, Tailwind 4). Coverage: ${summary}.
145
+
146
+ ## Install
147
+
148
+ In your global CSS, after Tailwind and the daisyUI plugin:
149
+
150
+ \`\`\`css
151
+ @import "tailwindcss";
152
+ @plugin "daisyui" {
153
+ themes: ${ctx.projectName}-light --default, ${ctx.projectName}-dark --prefersdark;
154
+ }
155
+ @import "./daisyui.transtyle.css";
156
+ \`\`\`
157
+
158
+ The light theme is the default; the dark theme activates via \`prefers-color-scheme\` (or set \`data-theme="${ctx.projectName}-dark"\` manually — standard daisyUI behavior).
159
+
160
+ ## Notes
161
+
162
+ - daisyUI's \`secondary\`/\`accent\` are mapped from your **brand** secondary/accent — in shadcn the same words mean subtle surfaces. Same design system, correct meaning in each ecosystem.
163
+ - \`--color-base-300\` is approximated from your border tone (daisyUI wants a third background-ramp step the IR doesn't define; see report.json).
164
+ - Regenerate with \`transtyle build daisyui\`; never edit this file.
165
+ `;
166
+ }