@sidekick-coder/zenith-kit 0.3.12 → 0.4.1

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.
@@ -0,0 +1,347 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import tailwindAutoPrefix from './tailwindAutoPrefix.js'
3
+
4
+ /**
5
+ * Helper to run the source plugin's transform hook directly, bypassing vite internals.
6
+ */
7
+ function transform(code: string, id: string, options: Parameters<typeof tailwindAutoPrefix>[0]) {
8
+ const [sourcePlugin] = tailwindAutoPrefix(options)
9
+
10
+ // @ts-expect-error - transform is a function in this plugin's implementation
11
+ const result = sourcePlugin.transform(code, id)
12
+
13
+ return result ? result.code : null
14
+ }
15
+
16
+ /**
17
+ * Helper to run the CSS plugin's generateBundle hook directly, bypassing vite internals.
18
+ */
19
+ function generateCss(source: string, options: Parameters<typeof tailwindAutoPrefix>[0]) {
20
+ const [, cssPlugin] = tailwindAutoPrefix(options)
21
+
22
+ const bundle: Record<string, any> = {
23
+ 'styles.css': { type: 'asset', source },
24
+ }
25
+
26
+ // @ts-expect-error - generateBundle is a function in this plugin's implementation
27
+ cssPlugin.generateBundle({}, bundle)
28
+
29
+ return bundle['styles.css'].source
30
+ }
31
+
32
+ describe('tailwindAutoPrefix.js', () => {
33
+ it('throws when no prefix is provided', () => {
34
+ // @ts-expect-error - intentionally omitting required option
35
+ expect(() => tailwindAutoPrefix({})).toThrow('"prefix" option is required')
36
+ })
37
+
38
+ it('prefixes classes in a static class attribute', () => {
39
+ const code = `<div class="flex p-4 hover:bg-red-500"></div>`
40
+
41
+ const result = transform(code, 'test.vue', { prefix: 'tw' })
42
+
43
+ expect(result).toBe(`<div class="tw:flex tw:p-4 tw:hover:bg-red-500"></div>`)
44
+ })
45
+
46
+ it('prefixes classes in a className attribute', () => {
47
+ const code = `<span className="text-sm font-bold"></span>`
48
+
49
+ const result = transform(code, 'test.tsx', { prefix: 'tw' })
50
+
51
+ expect(result).toBe(`<span className="tw:text-sm tw:font-bold"></span>`)
52
+ })
53
+
54
+ it('prefixes only string literals inside a :class binding', () => {
55
+ const code = `<p :class="['a b', active ? 'c' : '']"></p>`
56
+
57
+ const result = transform(code, 'test.vue', { prefix: 'tw' })
58
+
59
+ expect(result).toBe(`<p :class="['tw:a tw:b', active ? 'tw:c' : '']"></p>`)
60
+ })
61
+
62
+ it('prefixes only string literals inside a v-bind:class binding', () => {
63
+ const code = `<p v-bind:class="isOpen ? 'block' : 'hidden'"></p>`
64
+
65
+ const result = transform(code, 'test.vue', { prefix: 'tw' })
66
+
67
+ expect(result).toBe(`<p v-bind:class="isOpen ? 'tw:block' : 'tw:hidden'"></p>`)
68
+ })
69
+
70
+ it('does not prefix string literals compared against a variable with ===', () => {
71
+ const code = `<div :class="side === 'left' ? 'left-0' : 'right-0'"></div>`
72
+
73
+ const result = transform(code, 'test.vue', { prefix: 'tw' })
74
+
75
+ // 'left'/'right' are comparison operands (not classes) and must stay
76
+ // untouched, only the ternary branches are actual class values
77
+ expect(result).toBe(`<div :class="side === 'left' ? 'tw:left-0' : 'tw:right-0'"></div>`)
78
+ })
79
+
80
+ it('does not prefix string literals compared against a variable with !==', () => {
81
+ const code = `<div :class="variant !== 'floating' ? 'p-2' : 'p-4'"></div>`
82
+
83
+ const result = transform(code, 'test.vue', { prefix: 'tw' })
84
+
85
+ expect(result).toBe(`<div :class="variant !== 'floating' ? 'tw:p-2' : 'tw:p-4'"></div>`)
86
+ })
87
+
88
+ it('does not prefix string literals compared on the left-hand side of ===', () => {
89
+ const code = `<div :class="'left' === side ? 'left-0' : 'right-0'"></div>`
90
+
91
+ const result = transform(code, 'test.vue', { prefix: 'tw' })
92
+
93
+ expect(result).toBe(`<div :class="'left' === side ? 'tw:left-0' : 'tw:right-0'"></div>`)
94
+ })
95
+
96
+ it('prefixes object-syntax class bindings by key, leaving the condition untouched', () => {
97
+ const code = `<div :class="{ 'text-red-500': hasError, 'text-green-500': isValid }"></div>`
98
+
99
+ const result = transform(code, 'test.vue', { prefix: 'tw' })
100
+
101
+ expect(result).toBe(`<div :class="{ 'tw:text-red-500': hasError, 'tw:text-green-500': isValid }"></div>`)
102
+ })
103
+
104
+ it('prefixes multiple class comparisons like the real Sidebar component', () => {
105
+ const code = `<div :class="cn(
106
+ 'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) md:flex',
107
+ side === 'left'
108
+ ? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
109
+ : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
110
+ )"></div>`
111
+
112
+ const result = transform(code, 'test.vue', { prefix: 'zkit' })
113
+
114
+ expect(result).toContain(`side === 'left'`)
115
+ expect(result).toContain(`'zkit:left-0 zkit:group-data-[collapsible=offcanvas]:left-[calc(var(--zkit-sidebar-width)*-1)]'`)
116
+ expect(result).toContain(`'zkit:right-0 zkit:group-data-[collapsible=offcanvas]:right-[calc(var(--zkit-sidebar-width)*-1)]'`)
117
+ expect(result).not.toContain(`'zkit:left'`)
118
+ expect(result).not.toContain(`'zkit:right'`)
119
+ })
120
+
121
+ it('leaves plain variable :class bindings untouched', () => {
122
+ const code = `<p :class="isOpen"></p>`
123
+
124
+ const result = transform(code, 'test.vue', { prefix: 'tw' })
125
+
126
+ expect(result).toBeNull()
127
+ })
128
+
129
+ it('does not double-prefix classes that are already prefixed', () => {
130
+ const code = `<div class="tw:flex p-4"></div>`
131
+
132
+ const result = transform(code, 'test.vue', { prefix: 'tw' })
133
+
134
+ expect(result).toBe(`<div class="tw:flex tw:p-4"></div>`)
135
+ })
136
+
137
+ it('returns null when there is nothing to change', () => {
138
+ const code = `<div class="tw:flex tw:p-4"></div>`
139
+
140
+ const result = transform(code, 'test.vue', { prefix: 'tw' })
141
+
142
+ expect(result).toBeNull()
143
+ })
144
+
145
+ it('respects the include option', () => {
146
+ const code = `<div class="flex"></div>`
147
+
148
+ const result = transform(code, 'test.css', { prefix: 'tw', include: '**/*.vue' })
149
+
150
+ expect(result).toBeNull()
151
+ })
152
+
153
+ it('respects the exclude option', () => {
154
+ const code = `<div class="flex"></div>`
155
+
156
+ const result = transform(code, `${process.cwd()}/node_modules/foo/test.vue`, { prefix: 'tw', exclude: 'node_modules/**' })
157
+
158
+ expect(result).toBeNull()
159
+ })
160
+ })
161
+
162
+ describe('tailwindAutoPrefix.js CSS plugin', () => {
163
+ it('prefixes plain class selectors in generated CSS', () => {
164
+ const css = `.flex{display:flex}`
165
+
166
+ const result = generateCss(css, { prefix: 'zkit' })
167
+
168
+ expect(result).toBe(`.zkit\\:flex{display:flex}`)
169
+ })
170
+
171
+ it('prefixes class selectors with variants, preserving escaped colons', () => {
172
+ const css = `.hover\\:bg-red-500:hover{background-color:red}`
173
+
174
+ const result = generateCss(css, { prefix: 'zkit' })
175
+
176
+ expect(result).toBe(`.zkit\\:hover\\:bg-red-500:hover{background-color:red}`)
177
+ })
178
+
179
+ it('prefixes arbitrary variant selectors such as data attributes', () => {
180
+ const css = `.data-\\[state\\=open\\]\\:animate-in[data-state=open]{opacity:1}`
181
+
182
+ const result = generateCss(css, { prefix: 'zkit' })
183
+
184
+ expect(result).toBe(`.zkit\\:data-\\[state\\=open\\]\\:animate-in[data-state=open]{opacity:1}`)
185
+ })
186
+
187
+ it('does not double-prefix already-prefixed selectors', () => {
188
+ const css = `.zkit\\:flex{display:flex}`
189
+
190
+ const result = generateCss(css, { prefix: 'zkit' })
191
+
192
+ expect(result).toBe(`.zkit\\:flex{display:flex}`)
193
+ })
194
+
195
+ it('ignores non-css assets in the bundle', () => {
196
+ const [, cssPlugin] = tailwindAutoPrefix({ prefix: 'zkit' })
197
+
198
+ const bundle: Record<string, any> = {
199
+ 'index.es.js': { type: 'chunk', code: '.flex{}' },
200
+ }
201
+
202
+ // @ts-expect-error - generateBundle is a function in this plugin's implementation
203
+ cssPlugin.generateBundle({}, bundle)
204
+
205
+ expect(bundle['index.es.js'].code).toBe('.flex{}')
206
+ })
207
+
208
+ it('prefixes a css custom property declaration and its var() usage', () => {
209
+ const css = `:root{--sidebar-width:16rem}.w-\\(--sidebar-width\\){width:var(--sidebar-width)}`
210
+
211
+ const result = generateCss(css, { prefix: 'zkit' })
212
+
213
+ expect(result).toBe(`:root{--zkit-sidebar-width:16rem}.zkit\\:w-\\(--zkit-sidebar-width\\){width:var(--zkit-sidebar-width)}`)
214
+ })
215
+
216
+ it('prefixes @property at-rules declaring a custom property', () => {
217
+ const css = `@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}`
218
+
219
+ const result = generateCss(css, { prefix: 'zkit' })
220
+
221
+ expect(result).toBe(`@property --zkit-tw-translate-x{syntax:"*";inherits:false;initial-value:0}`)
222
+ })
223
+
224
+ it('does not double-prefix an already-prefixed css variable', () => {
225
+ const css = `:root{--zkit-sidebar-width:16rem}`
226
+
227
+ const result = generateCss(css, { prefix: 'zkit' })
228
+
229
+ expect(result).toBe(`:root{--zkit-sidebar-width:16rem}`)
230
+ })
231
+
232
+ it('leaves excluded css variables untouched', () => {
233
+ const css = `:root{--reka-navigation-menu-viewport-height:100px}.foo{height:var(--reka-navigation-menu-viewport-height)}`
234
+
235
+ const result = generateCss(css, { prefix: 'zkit', excludeVars: ['--reka-*'] })
236
+
237
+ expect(result).toBe(`:root{--reka-navigation-menu-viewport-height:100px}.zkit\\:foo{height:var(--reka-navigation-menu-viewport-height)}`)
238
+ })
239
+ })
240
+
241
+ describe('tailwindAutoPrefix.js style bindings', () => {
242
+ it('prefixes a css variable reference embedded in a class', () => {
243
+ const code = `<div class="w-(--sidebar-width)"></div>`
244
+
245
+ const result = transform(code, 'test.vue', { prefix: 'zkit' })
246
+
247
+ expect(result).toBe(`<div class="zkit:w-(--zkit-sidebar-width)"></div>`)
248
+ })
249
+
250
+ it('prefixes a css variable reference inside an arbitrary value class', () => {
251
+ const code = `<div :class="'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+2px)]'"></div>`
252
+
253
+ const result = transform(code, 'test.vue', { prefix: 'zkit' })
254
+
255
+ expect(result).toBe(`<div :class="'zkit:group-data-[collapsible=icon]:w-[calc(var(--zkit-sidebar-width-icon)+2px)]'"></div>`)
256
+ })
257
+
258
+ it('prefixes a custom property key set via a dynamic :style binding', () => {
259
+ const code = `<div :style="{ '--sidebar-width': SIDEBAR_WIDTH }"></div>`
260
+
261
+ const result = transform(code, 'test.vue', { prefix: 'zkit' })
262
+
263
+ expect(result).toBe(`<div :style="{ '--zkit-sidebar-width': SIDEBAR_WIDTH }"></div>`)
264
+ })
265
+
266
+ it('prefixes var() references inside a dynamic :style binding value', () => {
267
+ const code = `<div :style="{ '--normal-bg': 'var(--popover)', '--normal-text': 'var(--popover-foreground)' }"></div>`
268
+
269
+ const result = transform(code, 'test.vue', { prefix: 'zkit' })
270
+
271
+ expect(result).toBe(`<div :style="{ '--zkit-normal-bg': 'var(--zkit-popover)', '--zkit-normal-text': 'var(--zkit-popover-foreground)' }"></div>`)
272
+ })
273
+
274
+ it('prefixes var() references inside a static style attribute', () => {
275
+ const code = `<div style="--sidebar-width: 16rem; width: var(--sidebar-width)"></div>`
276
+
277
+ const result = transform(code, 'test.vue', { prefix: 'zkit' })
278
+
279
+ expect(result).toBe(`<div style="--zkit-sidebar-width: 16rem; width: var(--zkit-sidebar-width)"></div>`)
280
+ })
281
+
282
+ it('respects excludeVars for both class-embedded and :style bindings', () => {
283
+ const code = `<div class="w-(--reka-viewport-width)" :style="{ '--reka-viewport-width': width }"></div>`
284
+
285
+ const result = transform(code, 'test.vue', { prefix: 'zkit', excludeVars: ['--reka-*'] })
286
+
287
+ expect(result).toBe(`<div class="zkit:w-(--reka-viewport-width)" :style="{ '--reka-viewport-width': width }"></div>`)
288
+ })
289
+ })
290
+
291
+ describe('tailwindAutoPrefix.js classes.ignore', () => {
292
+ it('leaves a standalone ignored class untouched in a static class attribute', () => {
293
+ const code = `<div class="dark"></div>`
294
+
295
+ const result = transform(code, 'test.vue', { prefix: 'zkit', classes: { ignore: ['.dark'] } })
296
+
297
+ expect(result).toBeNull()
298
+ })
299
+
300
+ it('still prefixes other classes alongside an ignored one', () => {
301
+ const code = `<div class="dark flex p-4"></div>`
302
+
303
+ const result = transform(code, 'test.vue', { prefix: 'zkit', classes: { ignore: ['.dark'] } })
304
+
305
+ expect(result).toBe(`<div class="dark zkit:flex zkit:p-4"></div>`)
306
+ })
307
+
308
+ it('still prefixes compound variant classes that merely contain the ignored name', () => {
309
+ const code = `<div class="dark:bg-input/30"></div>`
310
+
311
+ const result = transform(code, 'test.vue', { prefix: 'zkit', classes: { ignore: ['.dark'] } })
312
+
313
+ expect(result).toBe(`<div class="zkit:dark:bg-input/30"></div>`)
314
+ })
315
+
316
+ it('leaves an ignored class untouched inside a dynamic :class binding', () => {
317
+ const code = `<div :class="['dark', 'flex']"></div>`
318
+
319
+ const result = transform(code, 'test.vue', { prefix: 'zkit', classes: { ignore: ['.dark'] } })
320
+
321
+ expect(result).toBe(`<div :class="['dark', 'zkit:flex']"></div>`)
322
+ })
323
+
324
+ it('leaves the literal .dark selector untouched in generated CSS while still renaming its custom properties', () => {
325
+ const css = `.dark{--background:oklch(14.1% .005 285.823)}`
326
+
327
+ const result = generateCss(css, { prefix: 'zkit', classes: { ignore: ['.dark'] } })
328
+
329
+ expect(result).toBe(`.dark{--zkit-background:oklch(14.1% .005 285.823)}`)
330
+ })
331
+
332
+ it('still prefixes the compound dark: variant utility while leaving the .dark ancestor selector untouched', () => {
333
+ const css = `.dark\\:bg-input\\/30:is(.dark *){background-color:red}`
334
+
335
+ const result = generateCss(css, { prefix: 'zkit', classes: { ignore: ['.dark'] } })
336
+
337
+ expect(result).toBe(`.zkit\\:dark\\:bg-input\\/30:is(.dark *){background-color:red}`)
338
+ })
339
+
340
+ it('does not ignore any class when classes.ignore is not provided', () => {
341
+ const css = `.dark{--background:oklch(14.1% .005 285.823)}`
342
+
343
+ const result = generateCss(css, { prefix: 'zkit' })
344
+
345
+ expect(result).toBe(`.zkit\\:dark{--zkit-background:oklch(14.1% .005 285.823)}`)
346
+ })
347
+ })
@@ -2,21 +2,22 @@ import { createLogger, defineConfig, UserConfig } from 'vite'
2
2
  import vue from '@vitejs/plugin-vue'
3
3
  import tailwindcss from '@tailwindcss/vite'
4
4
  import dts from 'vite-plugin-dts'
5
+ import prefixer from './vite/plugins/tailwindAutoPrefix.js'
5
6
 
6
7
  export const logger = createLogger()
7
8
 
8
9
  const externals = [
9
10
  'vue',
10
- // 'vue-router',
11
- // '@vueuse/core',
12
- // '@vueuse/router',
11
+ 'vue-router',
12
+ '@vueuse/core',
13
+ '@vueuse/router',
13
14
  'vee-validate',
14
15
  '@vee-validate/valibot',
15
- // "@unhead/vue",
16
- // "vue-router",
17
- // "vue-sonner",
18
- // "vee-validate",
19
- // "reka-ui",
16
+ "@unhead/vue",
17
+ "vue-router",
18
+ "vue-sonner",
19
+ "vee-validate",
20
+ "reka-ui",
20
21
  ]
21
22
 
22
23
  const plugins: UserConfig['plugins'] = [
@@ -36,12 +37,22 @@ plugins.push(dts({
36
37
  staticImport: true,
37
38
  }))
38
39
 
40
+
41
+ plugins.push(prefixer({
42
+ prefix: 'zkit',
43
+ include: ['**/*.vue', '**/*.ts', "**/*.css"],
44
+ classes: {
45
+ ignore: [".dark"]
46
+ }
47
+ }))
48
+
39
49
  plugins.push(tailwindcss())
40
50
 
41
51
  export default defineConfig({
42
52
  customLogger: logger,
43
53
  plugins: plugins,
44
54
  build: {
55
+ minify: process.env.NO_MINIFY ? false : true,
45
56
  rollupOptions: {
46
57
  external: externals,
47
58
  },
@@ -17,7 +17,7 @@ const prebuild = () => ({
17
17
  'src/client/components/ui/**/index.ts',
18
18
  'src/client/components/*.vue',
19
19
  'src/client/layouts/*.vue',
20
- 'src/client/css/*.css',
20
+ 'src/client/css/styles.css',
21
21
  ]
22
22
  })
23
23
 
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
File without changes