@webjsdev/ui 0.3.7 → 0.3.8
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/package.json +1 -1
- package/packages/registry/components/alert-dialog.ts +2 -1
- package/packages/registry/components/dialog.ts +2 -1
- package/packages/registry/components/dropdown-menu.ts +2 -1
- package/packages/registry/components/hover-card.ts +2 -1
- package/packages/registry/components/sonner.ts +1 -1
- package/packages/registry/components/tooltip.ts +2 -1
- package/packages/registry/lib/dom.ts +18 -0
- package/packages/registry/lib/utils.ts +6 -105
- package/packages/registry/registry.json +25 -5
- package/src/commands/add.js +33 -13
- package/src/commands/init.js +30 -6
- package/src/utils/detect-project.js +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webjsdev/ui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "An AI-first component library - class-helper functions for visuals, custom elements only where state matters. Source-copied into your repo, you own it. Works with any Tailwind v4 project.",
|
|
6
6
|
"bin": {
|
|
@@ -65,7 +65,8 @@
|
|
|
65
65
|
*/
|
|
66
66
|
import { WebComponent, html, prop } from '@webjsdev/core';
|
|
67
67
|
import { ref, createRef } from '@webjsdev/core/directives';
|
|
68
|
-
import { ensureId
|
|
68
|
+
import { ensureId } from '../lib/utils.ts';
|
|
69
|
+
import { onBeforeCache } from '../lib/dom.ts';
|
|
69
70
|
import { buttonClass, type ButtonVariant, type ButtonSize } from './button.ts';
|
|
70
71
|
|
|
71
72
|
export const alertDialogContentClass = (): string =>
|
|
@@ -65,7 +65,8 @@
|
|
|
65
65
|
*/
|
|
66
66
|
import { WebComponent, html, unsafeHTML, prop } from '@webjsdev/core';
|
|
67
67
|
import { ref, createRef } from '@webjsdev/core/directives';
|
|
68
|
-
import { ensureId
|
|
68
|
+
import { ensureId } from '../lib/utils.ts';
|
|
69
|
+
import { onBeforeCache } from '../lib/dom.ts';
|
|
69
70
|
import { buttonClass } from './button.ts';
|
|
70
71
|
|
|
71
72
|
// Wires a dialog panel's accessible name + description to its title /
|
|
@@ -78,7 +78,8 @@
|
|
|
78
78
|
* --accent-foreground, --destructive, --muted-foreground, --border.
|
|
79
79
|
*/
|
|
80
80
|
import { WebComponent, html, unsafeHTML, signal, prop } from '@webjsdev/core';
|
|
81
|
-
import { ensureId
|
|
81
|
+
import { ensureId } from '../lib/utils.ts';
|
|
82
|
+
import { onBeforeCache } from '../lib/dom.ts';
|
|
82
83
|
import { positionFloating, type PopoverSide, type PopoverAlign } from './popover.ts';
|
|
83
84
|
|
|
84
85
|
// --------------------------------------------------------------------------
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
* Design tokens used: --popover, --popover-foreground, --border.
|
|
40
40
|
*/
|
|
41
41
|
import { WebComponent, html, prop } from '@webjsdev/core';
|
|
42
|
-
import { ensureId
|
|
42
|
+
import { ensureId } from '../lib/utils.ts';
|
|
43
|
+
import { onBeforeCache } from '../lib/dom.ts';
|
|
43
44
|
import { positionFloating, type PopoverSide, type PopoverAlign } from './popover.ts';
|
|
44
45
|
|
|
45
46
|
// `fixed m-0` opts out of the UA `[popover]` auto-centering margin so
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
* Design tokens used: --popover, --popover-foreground, --border, --radius.
|
|
45
45
|
*/
|
|
46
46
|
import { WebComponent, html, repeat, unsafeHTML, signal, prop } from '@webjsdev/core';
|
|
47
|
-
import { onBeforeCache } from '../lib/
|
|
47
|
+
import { onBeforeCache } from '../lib/dom.ts';
|
|
48
48
|
|
|
49
49
|
type ToastType = 'default' | 'success' | 'error' | 'info' | 'warning' | 'loading';
|
|
50
50
|
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
* Design tokens used: --foreground, --background.
|
|
42
42
|
*/
|
|
43
43
|
import { WebComponent, html, prop } from '@webjsdev/core';
|
|
44
|
-
import { ensureId
|
|
44
|
+
import { ensureId } from '../lib/utils.ts';
|
|
45
|
+
import { onBeforeCache } from '../lib/dom.ts';
|
|
45
46
|
import { positionFloating, type PopoverSide, type PopoverAlign } from './popover.ts';
|
|
46
47
|
|
|
47
48
|
// UA `[popover]` defaults paint a bordered, padded panel centered with
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Client-only DOM helpers. Kept SEPARATE from lib/utils.ts because these
|
|
2
|
+
// reference `document`: any module that references a client global is treated
|
|
3
|
+
// as client-effecting by the elision analyzer, so bundling this with the pure
|
|
4
|
+
// `cn()` would pin every page that imports `cn` to the browser (#819). Import
|
|
5
|
+
// these ONLY from components (which ship anyway), never from a page/layout.
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Run `reset` just before the webjs client router snapshots the page into its
|
|
9
|
+
* back/forward cache (the `webjs:before-cache` event). Transient overlays use
|
|
10
|
+
* this to close themselves so a restored snapshot is clean. SSR-safe (a no-op
|
|
11
|
+
* when there is no `document`). Wire it in `connectedCallback` and call the
|
|
12
|
+
* returned disposer in `disconnectedCallback` so it never leaks across navs.
|
|
13
|
+
*/
|
|
14
|
+
export function onBeforeCache(reset: () => void): () => void {
|
|
15
|
+
if (typeof document === 'undefined') return () => {};
|
|
16
|
+
document.addEventListener('webjs:before-cache', reset);
|
|
17
|
+
return () => document.removeEventListener('webjs:before-cache', reset);
|
|
18
|
+
}
|
|
@@ -127,95 +127,12 @@ export function ensureId(el: { id: string }, prefix = 'ui'): string {
|
|
|
127
127
|
return el.id;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
// inserted into a live DOM, which doesn't happen during webjs SSR.
|
|
137
|
-
// ---------------------------------------------------------------------------
|
|
138
|
-
|
|
139
|
-
const HasHTMLElement = typeof HTMLElement !== 'undefined';
|
|
140
|
-
|
|
141
|
-
class ServerHTMLElementStub {
|
|
142
|
-
// Minimal surface so attribute reads/writes inside synchronous code paths
|
|
143
|
-
// that DO execute during SSR (e.g. attribute reflection) don't throw.
|
|
144
|
-
_ssrAttrs: Record<string, string> = {};
|
|
145
|
-
getAttribute(name: string): string | null {
|
|
146
|
-
return this._ssrAttrs[name] ?? null;
|
|
147
|
-
}
|
|
148
|
-
setAttribute(name: string, value: string): void {
|
|
149
|
-
this._ssrAttrs[name] = String(value);
|
|
150
|
-
}
|
|
151
|
-
hasAttribute(name: string): boolean {
|
|
152
|
-
return name in this._ssrAttrs;
|
|
153
|
-
}
|
|
154
|
-
removeAttribute(name: string): void {
|
|
155
|
-
delete this._ssrAttrs[name];
|
|
156
|
-
}
|
|
157
|
-
toggleAttribute(name: string, force?: boolean): boolean {
|
|
158
|
-
const want = force === undefined ? !this.hasAttribute(name) : force;
|
|
159
|
-
if (want) this.setAttribute(name, '');
|
|
160
|
-
else this.removeAttribute(name);
|
|
161
|
-
return want;
|
|
162
|
-
}
|
|
163
|
-
addEventListener(): void {}
|
|
164
|
-
removeEventListener(): void {}
|
|
165
|
-
dispatchEvent(): boolean {
|
|
166
|
-
return true;
|
|
167
|
-
}
|
|
168
|
-
// Tree-walk APIs no-op into null: components that call them server-side
|
|
169
|
-
// simply see "no children / no siblings", which is acceptable for SSR.
|
|
170
|
-
closest(): null {
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
querySelector(): null {
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
querySelectorAll(): never[] {
|
|
177
|
-
return [];
|
|
178
|
-
}
|
|
179
|
-
focus(): void {}
|
|
180
|
-
blur(): void {}
|
|
181
|
-
contains(): boolean {
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
insertBefore<T>(node: T): T {
|
|
185
|
-
return node;
|
|
186
|
-
}
|
|
187
|
-
appendChild<T>(node: T): T {
|
|
188
|
-
return node;
|
|
189
|
-
}
|
|
190
|
-
replaceChildren(): void {}
|
|
191
|
-
get firstChild(): null {
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
get classList() {
|
|
195
|
-
return { add: () => {}, remove: () => {}, toggle: () => false, contains: () => false };
|
|
196
|
-
}
|
|
197
|
-
get className(): string {
|
|
198
|
-
return this._ssrAttrs.class ?? '';
|
|
199
|
-
}
|
|
200
|
-
set className(v: string) {
|
|
201
|
-
this._ssrAttrs.class = v;
|
|
202
|
-
}
|
|
203
|
-
get style(): Record<string, string> {
|
|
204
|
-
return {};
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/** SSR-safe base class: `HTMLElement` in browser, a thin stub in Node. */
|
|
209
|
-
export const Base: typeof HTMLElement = (HasHTMLElement
|
|
210
|
-
? HTMLElement
|
|
211
|
-
: (ServerHTMLElementStub as unknown as typeof HTMLElement)) as typeof HTMLElement;
|
|
212
|
-
|
|
213
|
-
/** Register a custom element. No-op on server (no `customElements` global). */
|
|
214
|
-
export function defineElement(name: string, cls: CustomElementConstructor): void {
|
|
215
|
-
if (typeof customElements === 'undefined') return;
|
|
216
|
-
if (customElements.get(name)) return; // already defined (HMR / double-import)
|
|
217
|
-
customElements.define(name, cls);
|
|
218
|
-
}
|
|
130
|
+
// NOTE: the old `HTMLElement`-era `Base` / `defineElement` / SSR-stub helpers
|
|
131
|
+
// were removed. The ui registry components extend `WebComponent` from
|
|
132
|
+
// `@webjsdev/core` now, so those helpers were dead code, and referencing
|
|
133
|
+
// `HTMLElement` / `customElements` at module scope marked this util (and any
|
|
134
|
+
// page that imports `cn`) as client-effecting. Everything below is pure or
|
|
135
|
+
// SSR-safe, so importing `cn` no longer pins a page to the browser (#819).
|
|
219
136
|
|
|
220
137
|
// ---------------------------------------------------------------------------
|
|
221
138
|
// Layout helpers: encode the design-system rhythm (spacing between label /
|
|
@@ -266,19 +183,3 @@ export const helpClass = () => 'text-xs text-muted-foreground';
|
|
|
266
183
|
|
|
267
184
|
/** Validation error text: replaces hint when the field is invalid. */
|
|
268
185
|
export const errorClass = () => 'text-sm font-medium text-destructive';
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Run `reset` just before the webjs client router snapshots the page into its
|
|
272
|
-
* back/forward cache (the `webjs:before-cache` event). Transient overlays use
|
|
273
|
-
* this to close themselves so a restored snapshot is clean rather than, say, a
|
|
274
|
-
* hover-card frozen open after Back then Forward (#766). Wire it in
|
|
275
|
-
* `connectedCallback` and call the returned disposer in `disconnectedCallback`
|
|
276
|
-
* so the listener never leaks across soft navigations.
|
|
277
|
-
*
|
|
278
|
-
* SSR-safe: a no-op when there is no `document` (server / no client router).
|
|
279
|
-
*/
|
|
280
|
-
export function onBeforeCache(reset: () => void): () => void {
|
|
281
|
-
if (typeof document === 'undefined') return () => {};
|
|
282
|
-
document.addEventListener('webjs:before-cache', reset);
|
|
283
|
-
return () => document.removeEventListener('webjs:before-cache', reset);
|
|
284
|
-
}
|
|
@@ -15,6 +15,18 @@
|
|
|
15
15
|
}
|
|
16
16
|
]
|
|
17
17
|
},
|
|
18
|
+
{
|
|
19
|
+
"name": "lib-dom",
|
|
20
|
+
"type": "registry:lib",
|
|
21
|
+
"description": "onBeforeCache() client-router cache hook",
|
|
22
|
+
"files": [
|
|
23
|
+
{
|
|
24
|
+
"path": "lib/dom.ts",
|
|
25
|
+
"type": "registry:lib",
|
|
26
|
+
"target": "lib/dom.ts"
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
18
30
|
{
|
|
19
31
|
"name": "theme-neutral",
|
|
20
32
|
"type": "registry:theme",
|
|
@@ -59,7 +71,8 @@
|
|
|
59
71
|
"type": "registry:ui",
|
|
60
72
|
"registryDependencies": [
|
|
61
73
|
"button",
|
|
62
|
-
"dialog"
|
|
74
|
+
"dialog",
|
|
75
|
+
"lib-dom"
|
|
63
76
|
],
|
|
64
77
|
"dependencies": [
|
|
65
78
|
"@webjsdev/core"
|
|
@@ -189,7 +202,8 @@
|
|
|
189
202
|
"type": "registry:ui",
|
|
190
203
|
"registryDependencies": [
|
|
191
204
|
"lib-utils",
|
|
192
|
-
"button"
|
|
205
|
+
"button",
|
|
206
|
+
"lib-dom"
|
|
193
207
|
],
|
|
194
208
|
"dependencies": [
|
|
195
209
|
"@webjsdev/core"
|
|
@@ -205,7 +219,8 @@
|
|
|
205
219
|
"name": "dropdown-menu",
|
|
206
220
|
"type": "registry:ui",
|
|
207
221
|
"registryDependencies": [
|
|
208
|
-
"popover"
|
|
222
|
+
"popover",
|
|
223
|
+
"lib-dom"
|
|
209
224
|
],
|
|
210
225
|
"dependencies": [
|
|
211
226
|
"@webjsdev/core"
|
|
@@ -221,7 +236,8 @@
|
|
|
221
236
|
"name": "hover-card",
|
|
222
237
|
"type": "registry:ui",
|
|
223
238
|
"registryDependencies": [
|
|
224
|
-
"popover"
|
|
239
|
+
"popover",
|
|
240
|
+
"lib-dom"
|
|
225
241
|
],
|
|
226
242
|
"dependencies": [
|
|
227
243
|
"@webjsdev/core"
|
|
@@ -381,6 +397,9 @@
|
|
|
381
397
|
{
|
|
382
398
|
"name": "sonner",
|
|
383
399
|
"type": "registry:ui",
|
|
400
|
+
"registryDependencies": [
|
|
401
|
+
"lib-dom"
|
|
402
|
+
],
|
|
384
403
|
"dependencies": [
|
|
385
404
|
"@webjsdev/core"
|
|
386
405
|
],
|
|
@@ -476,7 +495,8 @@
|
|
|
476
495
|
"name": "tooltip",
|
|
477
496
|
"type": "registry:ui",
|
|
478
497
|
"registryDependencies": [
|
|
479
|
-
"popover"
|
|
498
|
+
"popover",
|
|
499
|
+
"lib-dom"
|
|
480
500
|
],
|
|
481
501
|
"dependencies": [
|
|
482
502
|
"@webjsdev/core"
|
package/src/commands/add.js
CHANGED
|
@@ -76,31 +76,51 @@ async function writeRegistryFile(cwd, config, item, file, opts) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
|
-
* Rewrite the registry-relative `'../lib/utils.ts'`
|
|
80
|
-
* that
|
|
81
|
-
* cn() helper.
|
|
79
|
+
* Rewrite the registry-relative `'../lib/utils.ts'` and `'../lib/dom.ts'`
|
|
80
|
+
* imports to the paths that resolve correctly from the file's target
|
|
81
|
+
* location to the user's cn() helper and onBeforeCache() DOM helper.
|
|
82
82
|
*
|
|
83
83
|
* The registry source assumes its own layout (`<registry>/components/<x>.ts`
|
|
84
|
-
* imports `'../lib/utils.ts'`). When that file lands in
|
|
85
|
-
* components/ui/<x>.ts, the literal `'../lib/utils.ts'` resolves
|
|
86
|
-
* `components/lib/utils.ts`, which doesn't exist. We compute the actual
|
|
87
|
-
* relative path from the target directory to
|
|
88
|
-
* (
|
|
89
|
-
*
|
|
84
|
+
* imports `'../lib/utils.ts'` / `'../lib/dom.ts'`). When that file lands in
|
|
85
|
+
* the user's components/ui/<x>.ts, the literal `'../lib/utils.ts'` resolves
|
|
86
|
+
* to `components/lib/utils.ts`, which doesn't exist. We compute the actual
|
|
87
|
+
* relative path from the target directory to the user's configured helper
|
|
88
|
+
* (`config.resolvedPaths.utils`, from components.json's aliases.utils) and
|
|
89
|
+
* substitute it in. The DOM helper is written as a SIBLING of the utils
|
|
90
|
+
* file (the scaffold puts it at `lib/utils/dom.ts`, next to `cn.ts`), so its
|
|
91
|
+
* absolute target is `dom.ts` in the utils file's directory. sonner imports
|
|
92
|
+
* ONLY the DOM helper, so both specifiers are handled independently and
|
|
93
|
+
* neither presence gates the other.
|
|
90
94
|
*
|
|
91
95
|
* @param {string} content raw file content from the registry
|
|
92
96
|
* @param {string} target absolute path where the file will be written
|
|
93
97
|
* @param {{ resolvedPaths: { utils: string } }} config parsed components.json
|
|
94
98
|
*/
|
|
95
99
|
export function rewriteUtilsImport(content, target, config) {
|
|
96
|
-
if (!content.includes('../lib/utils.ts')) return content;
|
|
97
100
|
const utilsAbs = config?.resolvedPaths?.utils;
|
|
98
101
|
if (!utilsAbs) return content;
|
|
99
|
-
let
|
|
102
|
+
let out = replaceImportSpecifier(content, '../lib/utils.ts', utilsAbs, target);
|
|
103
|
+
const domAbs = join(dirname(utilsAbs), 'dom.ts');
|
|
104
|
+
out = replaceImportSpecifier(out, '../lib/dom.ts', domAbs, target);
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Replace a single registry-relative import specifier with the relative path
|
|
110
|
+
* from `target`'s directory to `destAbs`. No-op when `spec` is absent.
|
|
111
|
+
*
|
|
112
|
+
* @param {string} content
|
|
113
|
+
* @param {string} spec the registry-relative specifier to replace
|
|
114
|
+
* @param {string} destAbs absolute path of the destination file
|
|
115
|
+
* @param {string} target absolute path where the importing file is written
|
|
116
|
+
*/
|
|
117
|
+
function replaceImportSpecifier(content, spec, destAbs, target) {
|
|
118
|
+
if (!content.includes(spec)) return content;
|
|
119
|
+
let rel = relPath(dirname(target), destAbs).split(/[\\/]/).join('/');
|
|
100
120
|
if (!rel.startsWith('.')) rel = './' + rel;
|
|
101
121
|
return content
|
|
102
|
-
.replaceAll(
|
|
103
|
-
.replaceAll(
|
|
122
|
+
.replaceAll(`'${spec}'`, `'${rel}'`)
|
|
123
|
+
.replaceAll(`"${spec}"`, `"${rel}"`);
|
|
104
124
|
}
|
|
105
125
|
|
|
106
126
|
function resolveTarget(cwd, config, item, file) {
|
package/src/commands/init.js
CHANGED
|
@@ -75,21 +75,45 @@ export const init = new Command()
|
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
async function writeLibUtils(cwd, utilsAlias, registryUrl) {
|
|
78
|
+
// `utils` alias points at e.g. "lib/utils" so we write to lib/utils.ts
|
|
79
|
+
const utilsRel = utilsAlias.replace(/^@\//, '') + '.ts';
|
|
80
|
+
const utilsTarget = join(cwd, utilsRel);
|
|
78
81
|
try {
|
|
79
82
|
const item = await fetchRegistryItem('lib-utils', registryUrl);
|
|
83
|
+
if (item.files) {
|
|
84
|
+
for (const f of item.files) {
|
|
85
|
+
ensureDir(dirname(utilsTarget));
|
|
86
|
+
writeFileSync(utilsTarget, f.content || '', 'utf8');
|
|
87
|
+
logger.success(`Wrote ${utilsAlias}.ts`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} catch (e) {
|
|
91
|
+
logger.warn(`Could not fetch lib-utils from registry (${e.message}). You may need to write lib/utils.ts manually.`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// The onBeforeCache() DOM helper lives in a SEPARATE module (#819) because
|
|
95
|
+
// it references `document`, so keeping it out of cn()'s file prevents the
|
|
96
|
+
// elision analyzer pinning every page that imports cn to the browser.
|
|
97
|
+
// Overlay components import it from `../lib/dom.ts`, so write it as a
|
|
98
|
+
// sibling of the utils file (e.g. lib/utils/dom.ts next to cn.ts).
|
|
99
|
+
const domTarget = join(dirname(utilsTarget), 'dom.ts');
|
|
100
|
+
try {
|
|
101
|
+
const item = await fetchRegistryItem('lib-dom', registryUrl);
|
|
80
102
|
if (!item.files) return;
|
|
81
103
|
for (const f of item.files) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
writeFileSync(target, f.content || '', 'utf8');
|
|
86
|
-
logger.success(`Wrote ${utilsAlias}.ts`);
|
|
104
|
+
ensureDir(dirname(domTarget));
|
|
105
|
+
writeFileSync(domTarget, f.content || '', 'utf8');
|
|
106
|
+
logger.success(`Wrote ${relative(cwd, domTarget)}`);
|
|
87
107
|
}
|
|
88
108
|
} catch (e) {
|
|
89
|
-
logger.warn(`Could not fetch lib-
|
|
109
|
+
logger.warn(`Could not fetch lib-dom from registry (${e.message}). You may need to write lib/dom.ts manually.`);
|
|
90
110
|
}
|
|
91
111
|
}
|
|
92
112
|
|
|
113
|
+
function relative(cwd, p) {
|
|
114
|
+
return p.startsWith(cwd) ? p.slice(cwd.length + 1) : p;
|
|
115
|
+
}
|
|
116
|
+
|
|
93
117
|
async function writeTheme(cwd, baseColor, cssPath, registryUrl) {
|
|
94
118
|
try {
|
|
95
119
|
const item = await fetchRegistryItem(`theme-${baseColor}`, registryUrl);
|
|
@@ -34,7 +34,9 @@ export function defaultsForProject(cwd = process.cwd()) {
|
|
|
34
34
|
switch (type) {
|
|
35
35
|
case 'webjs':
|
|
36
36
|
return {
|
|
37
|
-
|
|
37
|
+
// styles/globals.css, NOT app/globals.css: in webjs, app/ is routing-only,
|
|
38
|
+
// so a non-routing stylesheet lives outside it.
|
|
39
|
+
tailwindCss: 'styles/globals.css',
|
|
38
40
|
aliases: { components: 'components', utils: 'lib/utils', ui: 'components/ui', lib: 'lib' },
|
|
39
41
|
};
|
|
40
42
|
case 'next':
|