@hobui/viui-cli 0.0.4 → 0.0.6
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/README.md +2 -0
- package/dist/assets/plugins/viui-conf/apply-theme-body.ts +26 -0
- package/dist/assets/plugins/viui-conf/defaults/README.md +6 -0
- package/dist/assets/viui-themes/README.md +114 -0
- package/dist/assets/viui-themes/_THEME_CONVENTION.md +30 -0
- package/dist/assets/viui-themes/_bento-grid.scss +8 -0
- package/dist/assets/viui-themes/_glassmorphism.scss +8 -0
- package/dist/assets/viui-themes/_material.scss +8 -0
- package/dist/assets/viui-themes/_minimalist-2.scss +192 -0
- package/dist/assets/viui-themes/_minimalist.scss +9 -0
- package/dist/assets/viui-themes/_neo-brutalism.scss +281 -0
- package/dist/assets/viui-themes/bento-grid-global.scss +5 -0
- package/dist/assets/viui-themes/bento-grid.scss +4 -0
- package/dist/assets/viui-themes/glassmorphism-global.scss +5 -0
- package/dist/assets/viui-themes/glassmorphism.scss +4 -0
- package/dist/assets/viui-themes/index.scss +11 -0
- package/dist/assets/viui-themes/material-global.scss +5 -0
- package/dist/assets/viui-themes/material.scss +4 -0
- package/dist/assets/viui-themes/minimalist-2-global.scss +5 -0
- package/dist/assets/viui-themes/minimalist-2.scss +5 -0
- package/dist/assets/viui-themes/minimalist-global.scss +5 -0
- package/dist/assets/viui-themes/minimalist.scss +4 -0
- package/dist/assets/viui-themes/neo-brutalism-global.scss +5 -0
- package/dist/assets/viui-themes/neo-brutalism.scss +5 -0
- package/dist/cli.js +206 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -108,6 +108,8 @@ After init/sync, `.cursor/.design-system-version` contains the design system ver
|
|
|
108
108
|
|
|
109
109
|
**viui-conf:** Init and sync also copy the design system's **Vuetify config folder** (theme-base, v-light, v-dark, defaults) to **`src/plugins/viui-conf/`** in the consumer repo. The file **`vuetify.ts`** is **not** synced — you keep your own `src/plugins/vuetify.ts` and import from `./viui-conf` (e.g. `import { defaults } from './viui-conf/defaults'`, `import LightTheme from './viui-conf/v-light'`). See [packages/docs/consumer-update.md](../docs/consumer-update.md) (§4).
|
|
110
110
|
|
|
111
|
+
**viui-themes (SCSS):** Init and sync also copy **design style themes** (neo-brutalism, material, minimalist, etc.) to **`src/assets/styles/viui-themes/`** and create **`src/assets/styles/viui-themes-entry.scss`**. If the consumer has **`src/assets/styles/main.scss`**, the CLI adds an import for `viui-themes-entry` so theme SCSS is loaded automatically. Set `VITE_VIUI_THEME=neo-brutalism` (or another theme id) in `.env` and add `body.classList.add('design-style-neo-brutalism')` (or the matching class) in your app so the theme styles apply globally. See `src/plugins/viui-conf/defaults/README.md` after sync.
|
|
112
|
+
|
|
111
113
|
### Excluding files when building the CLI (`.cursorignore`)
|
|
112
114
|
|
|
113
115
|
In **packages/cli**, the file **`.cursorignore`** is read by the copy script when building the CLI. Paths matching its patterns are not copied from i-design-system `.cursor/` into `dist/assets/cursor/`, so they are not synced to consumers. One pattern per line; lines starting with `#` and empty lines are ignored. Supported patterns:
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Áp dụng body class theo VITE_VIUI_THEME để SCSS viui-themes (design-style-*) có hiệu lực toàn app.
|
|
3
|
+
* Import file này trong vuetify.ts (hoặc main.ts) để tự động gắn class khi load; export để gọi khi đổi theme tại runtime.
|
|
4
|
+
*/
|
|
5
|
+
import { VIUI_DEFAULT_THEME_ID } from './defaults/index'
|
|
6
|
+
|
|
7
|
+
const themeId = (import.meta as unknown as { env?: { VITE_VIUI_THEME?: string } }).env?.VITE_VIUI_THEME ?? VIUI_DEFAULT_THEME_ID
|
|
8
|
+
|
|
9
|
+
const DESIGN_STYLE_PREFIX = 'design-style-'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Gắn class `design-style-<id>` lên body, gỡ mọi class `design-style-*` cũ.
|
|
13
|
+
* Gọi khi khởi động app hoặc khi user đổi theme (vd. từ settings).
|
|
14
|
+
*/
|
|
15
|
+
export function applyViuiThemeBodyClass(id: string): void {
|
|
16
|
+
if (typeof document === 'undefined') return
|
|
17
|
+
const body = document.body
|
|
18
|
+
if (!body) return
|
|
19
|
+
const toAdd = DESIGN_STYLE_PREFIX + id
|
|
20
|
+
Array.from(body.classList).forEach((c) => {
|
|
21
|
+
if (c.startsWith(DESIGN_STYLE_PREFIX)) body.classList.remove(c)
|
|
22
|
+
})
|
|
23
|
+
body.classList.add(toAdd)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
applyViuiThemeBodyClass(themeId)
|
|
@@ -19,6 +19,12 @@ Defaults Vuetify (global + từng component) cho createVuetify. Merge base + **t
|
|
|
19
19
|
- **vuetify.ts:** Khi không cấu hình `.env`, dùng theme mặc định từ `by-theme/index.ts` (`VIUI_DEFAULT_THEME_ID` = `minimalist-2`). Gọi `getDefaults(import.meta.env.VITE_VIUI_THEME ?? VIUI_DEFAULT_THEME_ID)`.
|
|
20
20
|
- **.env:** `VITE_VIUI_THEME=minimalist-2` (mặc định) | `neo-brutalism` | `material`.
|
|
21
21
|
|
|
22
|
+
## SCSS theme (viui-themes) — tự động khi dùng viui-cli
|
|
23
|
+
|
|
24
|
+
Khi chạy `viui-cli init` hoặc `viui-cli sync`, CLI đồng bộ **viui-themes** vào `src/assets/styles/viui-themes/` và tạo `viui-themes-entry.scss`; nếu có `main.scss` thì CLI tự thêm dòng import để SCSS theme được load.
|
|
25
|
+
|
|
26
|
+
**Body class (tự động):** Trong `vuetify.ts` (hoặc `main.ts`), thêm một dòng: `import './viui-conf/apply-theme-body'` (hoặc `import '@/plugins/viui-conf/apply-theme-body'`). Khi load, module sẽ đọc `VITE_VIUI_THEME` và gắn class `design-style-<themeId>` lên `body`, nhờ đó style SCSS theme áp toàn app. Đổi theme tại runtime: gọi `import { applyViuiThemeBodyClass } from '@/plugins/viui-conf/apply-theme-body'; applyViuiThemeBodyClass('neo-brutalism')`.
|
|
27
|
+
|
|
22
28
|
## Cấu trúc
|
|
23
29
|
|
|
24
30
|
- `index.ts` — merge base + export `getDefaults(themeId?)`, `defaults` (= getDefaults()).
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# @viui/themes
|
|
2
|
+
|
|
3
|
+
ViUi design style themes (SCSS) for use with Vuetify. Styles are scoped so they only apply when a wrapper has the matching class.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Design tokens (CSS custom properties) must be loaded in the app before or with the theme — e.g. iNET design tokens (`inet-tokens.scss` or equivalent). Variables used: `--color-neutral-900`, `--radius-sm`, `--font-weight-bold`, `--duration-fast`, `--easing-default`, `--space-8pt-1`, `--font-weight-medium`, `--color-state-focus`, `--color-border-default`, `--color-border-subtle`, `--color-border-focus`.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
In your consumer app (after adding the design system / viui-cli sync):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add @viui/themes
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or with workspace link (monorepo):
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@viui/themes": "workspace:*"
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### 1. Import themes in `plugins/vuetify.ts`
|
|
28
|
+
|
|
29
|
+
Load the theme SCSS once so the scoped rules are available. Import **after** your tokens and base styles (e.g. `vuetify/styles`, your `main.scss` or `inet-tokens.scss`).
|
|
30
|
+
|
|
31
|
+
**Entry chung (khuyến nghị)** — load tất cả themes một lần:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// plugins/vuetify.ts
|
|
35
|
+
import 'vuetify/styles'
|
|
36
|
+
import '@/assets/styles/main.scss' // or inet-tokens + base
|
|
37
|
+
import '@viui/themes' // hoặc import '@viui/themes/index.scss'
|
|
38
|
+
|
|
39
|
+
import { createVuetify } from 'vuetify'
|
|
40
|
+
// ...
|
|
41
|
+
export const vuetify = createVuetify({ /* ... */ })
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Chỉ một theme (scoped hoặc global)** — mỗi theme có **tối đa 2 file**: `_<id>.scss` (partial) + `<id>.scss` (entry). Entry `*-global` là alias trỏ cùng file `<id>.scss` (không tạo file trùng lặp).
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import '@viui/themes/neo-brutalism.scss'
|
|
48
|
+
import '@viui/themes/minimalist-2.scss' // hoặc minimalist-2-global (cùng nội dung)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Theme global (áp toàn app)** — import cùng file `<id>.scss` (hoặc alias `<id>-global`) và thêm class `design-style-<id>` vào `body`:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import '@viui/themes/minimalist-2.scss' // hoặc @viui/themes/minimalist-2-global
|
|
55
|
+
// main.ts / App.vue:
|
|
56
|
+
import { applyViuiTheme } from './plugins/viui-theme'
|
|
57
|
+
applyViuiTheme() // đọc VITE_VIUI_THEME (mặc định minimalist-2) và thêm body class
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Cấu hình env: `VITE_VIUI_THEME=minimalist-2 | neo-brutalism | material | …` — **mặc định: Minimalist 2.0** (`minimalist-2`) khi không set.
|
|
61
|
+
|
|
62
|
+
### 2. Apply the style with a wrapper class (scoped only)
|
|
63
|
+
|
|
64
|
+
Neo-Brutalism applies only inside an element that has both:
|
|
65
|
+
|
|
66
|
+
- `components-overview-wrapper`
|
|
67
|
+
- `design-style-neo-brutalism`
|
|
68
|
+
|
|
69
|
+
**Option A — Whole app:**
|
|
70
|
+
|
|
71
|
+
```html
|
|
72
|
+
<template>
|
|
73
|
+
<div class="components-overview-wrapper design-style-neo-brutalism">
|
|
74
|
+
<v-app>...</v-app>
|
|
75
|
+
</div>
|
|
76
|
+
</template>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Option B — One section (e.g. dashboard):**
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<div class="components-overview-wrapper design-style-neo-brutalism">
|
|
83
|
+
<!-- ViUi components here use Neo-Brutalism -->
|
|
84
|
+
</div>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Available themes
|
|
88
|
+
|
|
89
|
+
Entry chung: `@viui/themes` hoặc `@viui/themes/index.scss` (load tất cả). Import riêng một theme: `@viui/themes/<id>.scss` hoặc `@viui/themes/<id>-global.scss`.
|
|
90
|
+
|
|
91
|
+
| Theme | Id | Wrapper class | Trạng thái |
|
|
92
|
+
|----------------|------------------|--------------------------------|-------------|
|
|
93
|
+
| Neo-Brutalism | `neo-brutalism` | `design-style-neo-brutalism` | Đã có |
|
|
94
|
+
| Bento Grid | `bento-grid` | `design-style-bento-grid` | Stub (TODO) |
|
|
95
|
+
| Glassmorphism | `glassmorphism` | `design-style-glassmorphism` | Stub (TODO) |
|
|
96
|
+
| Minimalist | `minimalist` | `design-style-minimalist` | Stub (TODO) |
|
|
97
|
+
| Material | `material` | `design-style-material` | Stub (TODO) |
|
|
98
|
+
|
|
99
|
+
## Thêm theme mới
|
|
100
|
+
|
|
101
|
+
Quy ước đầy đủ: xem **[_THEME_CONVENTION.md](./_THEME_CONVENTION.md)** trong package.
|
|
102
|
+
|
|
103
|
+
**Cấu trúc file (tối đa 2 file per theme — quản lý tập trung, tránh trùng lặp):**
|
|
104
|
+
|
|
105
|
+
1. **Một partial:** `_<id>.scss` — chứa cả scoped (`.components-overview-wrapper.design-style-<id>`) và global (`body.design-style-<id>`). Không tạo `_<id>-global.scss` riêng.
|
|
106
|
+
2. **Một entry:** `<id>.scss` — chỉ `@use './_<id>' as *;`.
|
|
107
|
+
3. **package.json** → `exports`: thêm `<id>.scss`, `<id>`, `<id>-global.scss`, `<id>-global` (hai entry global trỏ cùng `<id>.scss` để tương thích ngược).
|
|
108
|
+
4. **index.scss**: thêm `@use './_<id>' as *;`.
|
|
109
|
+
|
|
110
|
+
Dùng design tokens (CSS vars), không hard-code màu; đảm bảo WCAG AA contrast (xem `.cursor/rules/accessibility-contrast.mdc`).
|
|
111
|
+
|
|
112
|
+
## viui-cli sync
|
|
113
|
+
|
|
114
|
+
`viui-cli sync` updates only `.cursor` (rules, skills, commands, plans). Theme files are delivered via this package; add `@viui/themes` as a dependency and import in `plugins/vuetify.ts` as above.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Quy ước theme @viui/themes
|
|
2
|
+
|
|
3
|
+
Mỗi theme có **id** dạng kebab-case (vd: `neo-brutalism`, `bento-grid`, `glassmorphism`, `minimalist`, `material`).
|
|
4
|
+
|
|
5
|
+
## Cấu trúc file — tối đa 2 file per theme
|
|
6
|
+
|
|
7
|
+
Quản lý tập trung: **không** tạo file `_<id>-global.scss` hay `<id>-global.scss` riêng; một partial chứa cả scoped + body, một entry; alias `*-global` trỏ cùng entry.
|
|
8
|
+
|
|
9
|
+
| File | Mục đích |
|
|
10
|
+
|------|----------|
|
|
11
|
+
| `_<id>.scss` | Partial **duy nhất**: style trong `.components-overview-wrapper.design-style-<id>` (scoped) **và** `body.design-style-<id>` (teleport + global). Một nguồn chân lý. |
|
|
12
|
+
| `<id>.scss` | Entry: chỉ `@use './_<id>' as *;`. Import này dùng cho cả scoped và global (consumer thêm class vào body nếu muốn toàn app). |
|
|
13
|
+
|
|
14
|
+
**Export alias (tương thích ngược):** Trong `package.json`, `"./<id>-global.scss"` và `"./<id>-global"` trỏ cùng `"./<id>.scss"` — không tạo file vật lý `*-global.scss`.
|
|
15
|
+
|
|
16
|
+
## Wrapper class
|
|
17
|
+
|
|
18
|
+
- Scoped: `design-style-<id>` (kèm `components-overview-wrapper`).
|
|
19
|
+
- Global: consumer thêm `body` class `design-style-<id>` hoặc inject theo config (cùng file SCSS).
|
|
20
|
+
|
|
21
|
+
## Thêm theme mới
|
|
22
|
+
|
|
23
|
+
1. Tạo **2 file**: `_<id>.scss` (partial), `<id>.scss` (entry: `@use './_<id>' as *;`). **Không** tạo `_<id>-global.scss` hoặc `<id>-global.scss` riêng.
|
|
24
|
+
2. Trong `_<id>.scss`: viết cả selector `.components-overview-wrapper.design-style-<id>` và `body.design-style-<id>` (cho overlay/toast và dùng global).
|
|
25
|
+
3. Trong `package.json` → `exports`: thêm
|
|
26
|
+
`"./<id>.scss": "./<id>.scss"`, `"./<id>": "./<id>.scss"`,
|
|
27
|
+
`"./<id>-global.scss": "./<id>.scss"`, `"./<id>-global": "./<id>.scss"`.
|
|
28
|
+
4. Trong `index.scss`: thêm `@use './_<id>' as *;`.
|
|
29
|
+
|
|
30
|
+
Dùng design tokens (CSS vars), không hard-code màu; đảm bảo WCAG AA contrast.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bento Grid design style — một file duy nhất: scoped + body. Entry: bento-grid.scss; *-global alias.
|
|
3
|
+
* TODO: grid layout, card tiles, spacing tokens.
|
|
4
|
+
*/
|
|
5
|
+
.components-overview-wrapper.design-style-bento-grid,
|
|
6
|
+
body.design-style-bento-grid {
|
|
7
|
+
/* Bento Grid styles — use design tokens */
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Glassmorphism design style — một file duy nhất: scoped + body. Entry: glassmorphism.scss; *-global alias.
|
|
3
|
+
* TODO: backdrop-filter, semi-transparent surfaces, blur.
|
|
4
|
+
*/
|
|
5
|
+
.components-overview-wrapper.design-style-glassmorphism,
|
|
6
|
+
body.design-style-glassmorphism {
|
|
7
|
+
/* Glassmorphism styles — use design tokens */
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Material design style — một file duy nhất: scoped + body. Entry: material.scss; *-global alias.
|
|
3
|
+
* TODO: Material 3 / M3-like elevation, shapes, motion.
|
|
4
|
+
*/
|
|
5
|
+
.components-overview-wrapper.design-style-material,
|
|
6
|
+
body.design-style-material {
|
|
7
|
+
/* Material styles — use design tokens */
|
|
8
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimalist 2.0 design style — một file duy nhất: scoped (wrapper) + body (teleport + global).
|
|
3
|
+
* Import: minimalist-2.scss hoặc minimalist-2-global (alias). Không tạo _*-global.scss riêng.
|
|
4
|
+
* Phong cách UI tối giản: viền mỏng (1px), shadow nhẹ hoặc không, typography 400–500, design tokens; WCAG AA.
|
|
5
|
+
*/
|
|
6
|
+
$min2-border-width: 1px;
|
|
7
|
+
$min2-radius: var(--radius-base);
|
|
8
|
+
$min2-shadow-subtle: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
9
|
+
|
|
10
|
+
.components-overview-wrapper.design-style-minimalist-2 {
|
|
11
|
+
--min2-border-width: #{$min2-border-width};
|
|
12
|
+
--min2-radius: #{$min2-radius};
|
|
13
|
+
--min2-spacing-section: var(--space-8pt-2);
|
|
14
|
+
|
|
15
|
+
/* Button — chỉ hình dạng: viền mỏng, radius, shadow; không set border-color để prop color kiểm soát màu */
|
|
16
|
+
.vi-button.v-btn {
|
|
17
|
+
border-width: $min2-border-width !important;
|
|
18
|
+
border-style: solid !important;
|
|
19
|
+
border-radius: $min2-radius !important;
|
|
20
|
+
font-weight: var(--font-weight-medium) !important;
|
|
21
|
+
box-shadow: none !important;
|
|
22
|
+
|
|
23
|
+
&.v-btn--variant-elevated {
|
|
24
|
+
box-shadow: none !important;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
&:hover:not(:disabled).v-btn--variant-elevated {
|
|
28
|
+
box-shadow: $min2-shadow-subtle !important;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Card — border chỉ khi variant="outlined"; màu theo prop color. Mọi card: no shadow. */
|
|
33
|
+
.vi-card.outlined {
|
|
34
|
+
border-width: $min2-border-width;
|
|
35
|
+
border-style: solid;
|
|
36
|
+
border-color: var(--color-border-default);
|
|
37
|
+
border-radius: $min2-radius;
|
|
38
|
+
box-shadow: none;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.vi-card {
|
|
42
|
+
box-shadow: none;
|
|
43
|
+
|
|
44
|
+
.v-card-title {
|
|
45
|
+
font-weight: var(--font-weight-medium);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Section — typography 400–500, spacing rộng rãi */
|
|
50
|
+
.vi-section__title {
|
|
51
|
+
font-weight: var(--font-weight-medium);
|
|
52
|
+
border-bottom: $min2-border-width solid var(--color-border-default);
|
|
53
|
+
padding-bottom: var(--min2-spacing-section);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Demo area background */
|
|
57
|
+
.themes-minimalist-2__area {
|
|
58
|
+
background: var(--color-surface-background);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Input / Field — chỉ hình dạng; border-color không !important để state (focus, error) hoạt động */
|
|
62
|
+
.vi-input .v-field,
|
|
63
|
+
.v-field {
|
|
64
|
+
border-width: $min2-border-width !important;
|
|
65
|
+
border-style: solid !important;
|
|
66
|
+
border-color: var(--color-border-default);
|
|
67
|
+
border-radius: $min2-radius !important;
|
|
68
|
+
box-shadow: none !important;
|
|
69
|
+
|
|
70
|
+
&.v-field--focused {
|
|
71
|
+
border-color: var(--color-border-focus) !important;
|
|
72
|
+
box-shadow: 0 0 0 1px var(--color-state-focus);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.vi-select .v-field {
|
|
77
|
+
border-width: $min2-border-width !important;
|
|
78
|
+
border-style: solid !important;
|
|
79
|
+
border-color: var(--color-border-default);
|
|
80
|
+
border-radius: $min2-radius !important;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Checkbox — chỉ hình dạng */
|
|
84
|
+
.vi-checkbox .v-selection-control .v-checkbox-btn {
|
|
85
|
+
border-width: $min2-border-width;
|
|
86
|
+
border-style: solid;
|
|
87
|
+
border-color: var(--color-border-default);
|
|
88
|
+
border-radius: var(--radius-sm);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Alert */
|
|
92
|
+
.vi-alert {
|
|
93
|
+
border: $min2-border-width solid currentColor;
|
|
94
|
+
border-radius: $min2-radius;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* Dialog — chỉ hình dạng */
|
|
98
|
+
.vi-dialog-card {
|
|
99
|
+
border-width: $min2-border-width;
|
|
100
|
+
border-style: solid;
|
|
101
|
+
border-color: var(--color-border-default);
|
|
102
|
+
border-radius: $min2-radius;
|
|
103
|
+
box-shadow: $min2-shadow-subtle;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Toast — chỉ hình dạng + typography */
|
|
107
|
+
.vi-toast.v-snackbar .v-snackbar__wrapper {
|
|
108
|
+
border-width: $min2-border-width;
|
|
109
|
+
border-style: solid;
|
|
110
|
+
border-color: var(--color-border-default);
|
|
111
|
+
border-radius: $min2-radius;
|
|
112
|
+
box-shadow: $min2-shadow-subtle;
|
|
113
|
+
font-weight: var(--font-weight-regular);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Progress linear */
|
|
117
|
+
.themes-minimalist-2__progress.v-progress-linear {
|
|
118
|
+
border: none;
|
|
119
|
+
border-radius: $min2-radius;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.vi-dialog-title {
|
|
123
|
+
font-weight: var(--font-weight-medium);
|
|
124
|
+
border-bottom-width: $min2-border-width;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.vi-dialog-actions {
|
|
128
|
+
border-top-width: $min2-border-width;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* DataTable — chỉ hình dạng */
|
|
132
|
+
.vi-data-table .v-table {
|
|
133
|
+
border-width: $min2-border-width;
|
|
134
|
+
border-style: solid;
|
|
135
|
+
border-color: var(--color-border-default);
|
|
136
|
+
|
|
137
|
+
th,
|
|
138
|
+
td {
|
|
139
|
+
border-width: 1px;
|
|
140
|
+
border-style: solid;
|
|
141
|
+
border-color: var(--color-border-subtle);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* Pagination — chỉ hình dạng */
|
|
146
|
+
.vi-pagination .v-pagination__item,
|
|
147
|
+
.vi-pagination .v-pagination__prev,
|
|
148
|
+
.vi-pagination .v-pagination__next {
|
|
149
|
+
border-width: $min2-border-width;
|
|
150
|
+
border-style: solid;
|
|
151
|
+
border-color: var(--color-border-default);
|
|
152
|
+
border-radius: $min2-radius;
|
|
153
|
+
font-weight: var(--font-weight-medium);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* Teleported content (dialog, snackbar) */
|
|
158
|
+
body.design-style-minimalist-2 {
|
|
159
|
+
.vi-dialog-card {
|
|
160
|
+
border-width: $min2-border-width;
|
|
161
|
+
border-style: solid;
|
|
162
|
+
border-color: var(--color-border-default);
|
|
163
|
+
border-radius: $min2-radius;
|
|
164
|
+
box-shadow: $min2-shadow-subtle;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.vi-dialog-title {
|
|
168
|
+
font-weight: var(--font-weight-medium);
|
|
169
|
+
border-bottom-width: $min2-border-width;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.vi-dialog-actions {
|
|
173
|
+
border-top-width: $min2-border-width;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.vi-dialog .vi-button.v-btn {
|
|
177
|
+
border-width: $min2-border-width !important;
|
|
178
|
+
border-style: solid !important;
|
|
179
|
+
border-radius: $min2-radius !important;
|
|
180
|
+
font-weight: var(--font-weight-medium) !important;
|
|
181
|
+
box-shadow: none !important;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.vi-toast.v-snackbar .v-snackbar__wrapper {
|
|
185
|
+
border-width: $min2-border-width;
|
|
186
|
+
border-style: solid;
|
|
187
|
+
border-color: var(--color-border-default);
|
|
188
|
+
border-radius: $min2-radius;
|
|
189
|
+
box-shadow: $min2-shadow-subtle;
|
|
190
|
+
font-weight: var(--font-weight-regular);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimalist design style — scoped + global (body).
|
|
3
|
+
* Một file duy nhất: wrapper + body (teleport + toàn app). Entry: minimalist.scss; *-global alias tới đó.
|
|
4
|
+
* TODO: clean lines, reduced chrome, generous whitespace.
|
|
5
|
+
*/
|
|
6
|
+
.components-overview-wrapper.design-style-minimalist,
|
|
7
|
+
body.design-style-minimalist {
|
|
8
|
+
/* Minimalist styles — use design tokens */
|
|
9
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Neo-Brutalism design style — một file duy nhất: scoped (wrapper) + body (teleport + global).
|
|
3
|
+
* Entry: neo-brutalism.scss hoặc neo-brutalism-global (alias). Không tạo _*-global.scss riêng.
|
|
4
|
+
* Spec: Figma Design Foundations Page. Thick borders, bold type, flat colors, offset shadow. Tokens; WCAG AA.
|
|
5
|
+
*/
|
|
6
|
+
$neo-border-width: 3px;
|
|
7
|
+
$neo-shadow-offset: 4px 4px 0 0 var(--color-neutral-900, #1e1e1e);
|
|
8
|
+
|
|
9
|
+
.components-overview-wrapper.design-style-neo-brutalism {
|
|
10
|
+
--neo-border-width: #{$neo-border-width};
|
|
11
|
+
--neo-radius: var(--radius-sm);
|
|
12
|
+
--neo-shadow-offset: #{$neo-shadow-offset};
|
|
13
|
+
--neo-font-weight: var(--font-weight-bold);
|
|
14
|
+
|
|
15
|
+
/* Button — .v-btn for specificity over Vuetify defaults */
|
|
16
|
+
.vi-button.v-btn {
|
|
17
|
+
border: var(--neo-border-width) solid var(--color-neutral-900) !important;
|
|
18
|
+
border-radius: var(--neo-radius) !important;
|
|
19
|
+
font-weight: var(--neo-font-weight) !important;
|
|
20
|
+
box-shadow: var(--neo-shadow-offset) !important;
|
|
21
|
+
transition: transform var(--duration-fast) var(--easing-default, ease);
|
|
22
|
+
|
|
23
|
+
&:hover:not(:disabled) {
|
|
24
|
+
transform: translate(1px, 1px);
|
|
25
|
+
box-shadow: 3px 3px 0 0 var(--color-neutral-900, #1e1e1e) !important;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
&:active:not(:disabled) {
|
|
29
|
+
transform: translate(2px, 2px);
|
|
30
|
+
box-shadow: 2px 2px 0 0 var(--color-neutral-900, #1e1e1e) !important;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Card */
|
|
35
|
+
.vi-card {
|
|
36
|
+
border: var(--neo-border-width) solid var(--color-neutral-900);
|
|
37
|
+
border-radius: var(--neo-radius);
|
|
38
|
+
box-shadow: var(--neo-shadow-offset);
|
|
39
|
+
|
|
40
|
+
.v-card-title {
|
|
41
|
+
font-weight: var(--neo-font-weight);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Section — Figma: uppercase, font-black */
|
|
46
|
+
.vi-section__title {
|
|
47
|
+
font-weight: var(--neo-font-weight);
|
|
48
|
+
text-transform: uppercase;
|
|
49
|
+
border-bottom: var(--neo-border-width) solid var(--color-border-default);
|
|
50
|
+
padding-bottom: var(--space-8pt-1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Demo area background — Figma Design Foundations page bg */
|
|
54
|
+
.themes-neo-brutalism__area {
|
|
55
|
+
background: #fef3c7;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Input / Field (Vuetify v-field inside vi-input) */
|
|
59
|
+
.vi-input,
|
|
60
|
+
.v-field {
|
|
61
|
+
border: var(--neo-border-width) solid var(--color-border-default) !important;
|
|
62
|
+
border-radius: var(--neo-radius) !important;
|
|
63
|
+
font-weight: var(--font-weight-medium);
|
|
64
|
+
|
|
65
|
+
&.v-field--focused {
|
|
66
|
+
border-color: var(--color-border-focus) !important;
|
|
67
|
+
box-shadow: 0 0 0 2px var(--color-state-focus);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Select */
|
|
72
|
+
.vi-select .v-field {
|
|
73
|
+
border: var(--neo-border-width) solid var(--color-border-default) !important;
|
|
74
|
+
border-radius: var(--neo-radius) !important;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Checkbox */
|
|
78
|
+
.vi-checkbox .v-selection-control .v-checkbox-btn {
|
|
79
|
+
border: var(--neo-border-width) solid var(--color-neutral-900);
|
|
80
|
+
border-radius: var(--radius-sm);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Alert */
|
|
84
|
+
.vi-alert {
|
|
85
|
+
border: var(--neo-border-width) solid currentColor;
|
|
86
|
+
border-radius: var(--neo-radius);
|
|
87
|
+
font-weight: var(--font-weight-medium);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Dialog */
|
|
91
|
+
.vi-dialog-card {
|
|
92
|
+
border: var(--neo-border-width) solid var(--color-neutral-900);
|
|
93
|
+
border-radius: var(--neo-radius);
|
|
94
|
+
box-shadow: 8px 8px 0 0 var(--color-neutral-900, #1e1e1e);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* Toast (v-snackbar) */
|
|
98
|
+
.vi-toast.v-snackbar .v-snackbar__wrapper {
|
|
99
|
+
border: var(--neo-border-width) solid var(--color-neutral-900);
|
|
100
|
+
border-radius: var(--neo-radius);
|
|
101
|
+
box-shadow: 4px 4px 0 0 var(--color-neutral-900, #1e1e1e);
|
|
102
|
+
font-weight: var(--neo-font-weight);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Progress linear (Loading) */
|
|
106
|
+
.themes-neo-brutalism__progress.v-progress-linear {
|
|
107
|
+
border: var(--neo-border-width) solid var(--color-neutral-900);
|
|
108
|
+
border-radius: var(--neo-radius);
|
|
109
|
+
|
|
110
|
+
.v-progress-linear__determinate {
|
|
111
|
+
border-radius: 2px;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.vi-dialog-title {
|
|
116
|
+
font-weight: var(--neo-font-weight);
|
|
117
|
+
border-bottom-width: var(--neo-border-width);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.vi-dialog-actions {
|
|
121
|
+
border-top-width: var(--neo-border-width);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* DataTable */
|
|
125
|
+
.vi-data-table .v-table {
|
|
126
|
+
border: var(--neo-border-width) solid var(--color-border-default);
|
|
127
|
+
|
|
128
|
+
th,
|
|
129
|
+
td {
|
|
130
|
+
border: 1px solid var(--color-border-subtle);
|
|
131
|
+
font-weight: var(--font-weight-medium);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
thead th {
|
|
135
|
+
font-weight: var(--neo-font-weight);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* Pagination */
|
|
140
|
+
.vi-pagination .v-pagination__item,
|
|
141
|
+
.vi-pagination .v-pagination__prev,
|
|
142
|
+
.vi-pagination .v-pagination__next {
|
|
143
|
+
border: 2px solid var(--color-border-default);
|
|
144
|
+
border-radius: var(--neo-radius);
|
|
145
|
+
font-weight: var(--font-weight-medium);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Body: teleported content (dialog, snackbar) + global use (consumer adds class to body) */
|
|
150
|
+
body.design-style-neo-brutalism {
|
|
151
|
+
.vi-button.v-btn {
|
|
152
|
+
border: $neo-border-width solid var(--color-neutral-900) !important;
|
|
153
|
+
border-radius: var(--radius-lg, var(--radius-sm, 16px)) !important;
|
|
154
|
+
font-weight: var(--font-weight-bold) !important;
|
|
155
|
+
box-shadow: $neo-shadow-offset !important;
|
|
156
|
+
transition: transform var(--duration-fast) var(--easing-default, ease);
|
|
157
|
+
|
|
158
|
+
&:hover:not(:disabled) {
|
|
159
|
+
transform: translate(1px, 1px);
|
|
160
|
+
box-shadow: 3px 3px 0 0 var(--color-neutral-900, #1e1e1e) !important;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
&:active:not(:disabled) {
|
|
164
|
+
transform: translate(2px, 2px);
|
|
165
|
+
box-shadow: 2px 2px 0 0 var(--color-neutral-900, #1e1e1e) !important;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.vi-card {
|
|
170
|
+
border: $neo-border-width solid var(--color-neutral-900);
|
|
171
|
+
border-radius: var(--radius-sm);
|
|
172
|
+
box-shadow: $neo-shadow-offset;
|
|
173
|
+
|
|
174
|
+
.v-card-title {
|
|
175
|
+
font-weight: var(--font-weight-bold);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.vi-section__title {
|
|
180
|
+
font-weight: var(--font-weight-bold);
|
|
181
|
+
text-transform: uppercase;
|
|
182
|
+
border-bottom: $neo-border-width solid var(--color-border-default);
|
|
183
|
+
padding-bottom: var(--space-8pt-1);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.vi-input,
|
|
187
|
+
.v-field {
|
|
188
|
+
border: $neo-border-width solid var(--color-border-default) !important;
|
|
189
|
+
border-radius: var(--radius-sm) !important;
|
|
190
|
+
font-weight: var(--font-weight-medium);
|
|
191
|
+
|
|
192
|
+
&.v-field--focused {
|
|
193
|
+
border-color: var(--color-border-focus) !important;
|
|
194
|
+
box-shadow: 0 0 0 2px var(--color-state-focus);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.vi-select .v-field {
|
|
199
|
+
border: $neo-border-width solid var(--color-border-default) !important;
|
|
200
|
+
border-radius: var(--radius-sm) !important;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.vi-checkbox .v-selection-control .v-checkbox-btn {
|
|
204
|
+
border: $neo-border-width solid var(--color-neutral-900);
|
|
205
|
+
border-radius: var(--radius-sm);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.vi-alert {
|
|
209
|
+
border: $neo-border-width solid currentColor;
|
|
210
|
+
border-radius: var(--radius-sm);
|
|
211
|
+
font-weight: var(--font-weight-medium);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.vi-dialog-card {
|
|
215
|
+
border: $neo-border-width solid var(--color-neutral-900);
|
|
216
|
+
border-radius: var(--radius-sm);
|
|
217
|
+
box-shadow: 8px 8px 0 0 var(--color-neutral-900, #1e1e1e);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.vi-dialog-title {
|
|
221
|
+
font-weight: var(--font-weight-bold);
|
|
222
|
+
border-bottom-width: $neo-border-width;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.vi-dialog-actions {
|
|
226
|
+
border-top-width: $neo-border-width;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.vi-dialog .vi-button.v-btn {
|
|
230
|
+
border: $neo-border-width solid var(--color-neutral-900) !important;
|
|
231
|
+
border-radius: var(--radius-sm) !important;
|
|
232
|
+
font-weight: var(--font-weight-bold) !important;
|
|
233
|
+
box-shadow: $neo-shadow-offset !important;
|
|
234
|
+
|
|
235
|
+
&:hover:not(:disabled) {
|
|
236
|
+
box-shadow: 3px 3px 0 0 var(--color-neutral-900, #1e1e1e) !important;
|
|
237
|
+
}
|
|
238
|
+
&:active:not(:disabled) {
|
|
239
|
+
box-shadow: 2px 2px 0 0 var(--color-neutral-900, #1e1e1e) !important;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.vi-toast.v-snackbar .v-snackbar__wrapper {
|
|
244
|
+
border: $neo-border-width solid var(--color-neutral-900);
|
|
245
|
+
border-radius: var(--radius-sm);
|
|
246
|
+
box-shadow: 4px 4px 0 0 var(--color-neutral-900, #1e1e1e);
|
|
247
|
+
font-weight: var(--font-weight-bold);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.themes-neo-brutalism__progress.v-progress-linear,
|
|
251
|
+
.v-progress-linear {
|
|
252
|
+
border: $neo-border-width solid var(--color-neutral-900);
|
|
253
|
+
border-radius: var(--radius-sm);
|
|
254
|
+
|
|
255
|
+
.v-progress-linear__determinate {
|
|
256
|
+
border-radius: 2px;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.vi-data-table .v-table {
|
|
261
|
+
border: $neo-border-width solid var(--color-border-default);
|
|
262
|
+
|
|
263
|
+
th,
|
|
264
|
+
td {
|
|
265
|
+
border: 1px solid var(--color-border-subtle);
|
|
266
|
+
font-weight: var(--font-weight-medium);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
thead th {
|
|
270
|
+
font-weight: var(--font-weight-bold);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.vi-pagination .v-pagination__item,
|
|
275
|
+
.vi-pagination .v-pagination__prev,
|
|
276
|
+
.vi-pagination .v-pagination__next {
|
|
277
|
+
border: 2px solid var(--color-border-default);
|
|
278
|
+
border-radius: var(--radius-sm);
|
|
279
|
+
font-weight: var(--font-weight-medium);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @viui/themes — Entry chung: load tất cả design style themes (scoped).
|
|
3
|
+
* Mỗi theme scope theo .components-overview-wrapper.design-style-<id> và body (teleport).
|
|
4
|
+
* Import một lần trong app/Storybook; thêm theme mới: thêm @use và file theo _THEME_CONVENTION.md.
|
|
5
|
+
*/
|
|
6
|
+
@use './_neo-brutalism' as *;
|
|
7
|
+
@use './_bento-grid' as *;
|
|
8
|
+
@use './_glassmorphism' as *;
|
|
9
|
+
@use './_minimalist' as *;
|
|
10
|
+
@use './_minimalist-2' as *;
|
|
11
|
+
@use './_material' as *;
|
package/dist/cli.js
CHANGED
|
@@ -20,6 +20,10 @@ function getAssetsCursorDir() {
|
|
|
20
20
|
function getAssetsPluginsDir() {
|
|
21
21
|
return path.join(__dirname, 'assets', 'plugins');
|
|
22
22
|
}
|
|
23
|
+
/** viui-themes SCSS bundle path (synced to consumer src/assets/styles/viui-themes). */
|
|
24
|
+
function getAssetsViuiThemesDir() {
|
|
25
|
+
return path.join(__dirname, 'assets', 'viui-themes');
|
|
26
|
+
}
|
|
23
27
|
function loadManifest(assetsDir) {
|
|
24
28
|
const p = path.join(assetsDir, 'sync-manifest.json');
|
|
25
29
|
if (!fs.existsSync(p))
|
|
@@ -153,6 +157,52 @@ function runInit(cwd, assetsDir, opts) {
|
|
|
153
157
|
if (!opts.dryRun)
|
|
154
158
|
console.log('Synced viui-conf to src/plugins/viui-conf');
|
|
155
159
|
}
|
|
160
|
+
syncViuiThemesAndEntry(cwd, opts.dryRun ?? false);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Sync viui-themes SCSS to consumer and ensure theme SCSS is loaded.
|
|
164
|
+
* - Copies dist/assets/viui-themes → src/assets/styles/viui-themes
|
|
165
|
+
* - Writes viui-themes-entry.scss that @use's viui-themes/index.scss
|
|
166
|
+
* - Injects import into main.scss if present and not already there
|
|
167
|
+
*/
|
|
168
|
+
function syncViuiThemesAndEntry(cwd, dryRun) {
|
|
169
|
+
const themesSrc = getAssetsViuiThemesDir();
|
|
170
|
+
const stylesDir = path.join(cwd, 'src', 'assets', 'styles');
|
|
171
|
+
const themesDest = path.join(stylesDir, 'viui-themes');
|
|
172
|
+
const entryPath = path.join(stylesDir, 'viui-themes-entry.scss');
|
|
173
|
+
const mainScssPath = path.join(stylesDir, 'main.scss');
|
|
174
|
+
const entryImport = "@use './viui-themes-entry' as *;";
|
|
175
|
+
if (!fs.existsSync(themesSrc))
|
|
176
|
+
return;
|
|
177
|
+
if (dryRun) {
|
|
178
|
+
console.log('Would sync viui-themes to src/assets/styles/viui-themes');
|
|
179
|
+
console.log('Would write src/assets/styles/viui-themes-entry.scss');
|
|
180
|
+
console.log('Would ensure main.scss imports viui-themes-entry');
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (!fs.existsSync(path.join(cwd, 'src', 'assets')))
|
|
184
|
+
fs.mkdirSync(path.join(cwd, 'src', 'assets'), { recursive: true });
|
|
185
|
+
if (!fs.existsSync(stylesDir))
|
|
186
|
+
fs.mkdirSync(stylesDir, { recursive: true });
|
|
187
|
+
copyRecursive(themesSrc, themesDest, { dryRun: false });
|
|
188
|
+
console.log('Synced viui-themes to src/assets/styles/viui-themes');
|
|
189
|
+
const entryContent = `/**
|
|
190
|
+
* ViUi design style themes — auto-loaded when using viui-conf (VITE_VIUI_THEME).
|
|
191
|
+
* Do not edit: generated by viui-cli sync. Styles apply when body has .design-style-<id> or wrapper has .design-style-<id>.
|
|
192
|
+
*/
|
|
193
|
+
@use './viui-themes/index.scss' as *;
|
|
194
|
+
`;
|
|
195
|
+
fs.writeFileSync(entryPath, entryContent, 'utf8');
|
|
196
|
+
console.log('Wrote viui-themes-entry.scss');
|
|
197
|
+
if (fs.existsSync(mainScssPath)) {
|
|
198
|
+
const mainContent = fs.readFileSync(mainScssPath, 'utf8');
|
|
199
|
+
const hasEntry = /viui-themes-entry/.test(mainContent);
|
|
200
|
+
if (!hasEntry) {
|
|
201
|
+
const appended = mainContent.trimEnd() + '\n' + entryImport + '\n';
|
|
202
|
+
fs.writeFileSync(mainScssPath, appended, 'utf8');
|
|
203
|
+
console.log('Added viui-themes-entry import to main.scss');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
156
206
|
}
|
|
157
207
|
function runSync(cwd, assetsDir, opts) {
|
|
158
208
|
const manifest = loadManifest(assetsDir);
|
|
@@ -208,6 +258,7 @@ function runSync(cwd, assetsDir, opts) {
|
|
|
208
258
|
console.log('Synced viui-conf to src/plugins/viui-conf');
|
|
209
259
|
}
|
|
210
260
|
}
|
|
261
|
+
syncViuiThemesAndEntry(cwd, opts.dryRun ?? false);
|
|
211
262
|
}
|
|
212
263
|
function detectPackageManager(cwd) {
|
|
213
264
|
if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml')))
|
|
@@ -230,6 +281,87 @@ function addPostinstallScript(cwd, pm) {
|
|
|
230
281
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
|
|
231
282
|
return true;
|
|
232
283
|
}
|
|
284
|
+
const VIUI_THEME_IDS = ['minimalist-2', 'neo-brutalism', 'material'];
|
|
285
|
+
function setEnvTheme(cwd, themeId) {
|
|
286
|
+
const envPath = path.join(cwd, '.env');
|
|
287
|
+
const examplePath = path.join(cwd, '.env.example');
|
|
288
|
+
const line = `VITE_VIUI_THEME=${themeId}`;
|
|
289
|
+
const targets = [envPath, examplePath].filter((p) => fs.existsSync(p));
|
|
290
|
+
if (targets.length === 0) {
|
|
291
|
+
fs.writeFileSync(envPath, line + '\n', 'utf8');
|
|
292
|
+
console.log('Created .env with', line);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
for (const p of targets) {
|
|
296
|
+
let content = fs.readFileSync(p, 'utf8');
|
|
297
|
+
const re = /^\s*VITE_VIUI_THEME=.*/m;
|
|
298
|
+
if (re.test(content)) {
|
|
299
|
+
content = content.replace(re, line);
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
content = content.trimEnd() + '\n' + line + '\n';
|
|
303
|
+
}
|
|
304
|
+
fs.writeFileSync(p, content, 'utf8');
|
|
305
|
+
console.log('Set', line, 'in', path.basename(p));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function ensureApplyThemeBodyInVuetify(cwd) {
|
|
309
|
+
const vuetifyPath = path.join(cwd, 'src', 'plugins', 'vuetify.ts');
|
|
310
|
+
if (!fs.existsSync(vuetifyPath))
|
|
311
|
+
return false;
|
|
312
|
+
const content = fs.readFileSync(vuetifyPath, 'utf8');
|
|
313
|
+
if (/apply-theme-body|applyThemeBody/.test(content))
|
|
314
|
+
return false;
|
|
315
|
+
const importLine = "import './viui-conf/apply-theme-body'";
|
|
316
|
+
const defaultsImportMatch = content.match(/import\s+.+\s+from\s+['"]\.\/viui-conf\/defaults[^'"]*['"]/);
|
|
317
|
+
const insert = defaultsImportMatch
|
|
318
|
+
? content.replace(defaultsImportMatch[0], defaultsImportMatch[0] + '\n' + importLine)
|
|
319
|
+
: content.trimStart().replace(/^(\s*)/, `$1${importLine}\n$1`);
|
|
320
|
+
fs.writeFileSync(vuetifyPath, insert, 'utf8');
|
|
321
|
+
console.log('Added apply-theme-body import to src/plugins/vuetify.ts');
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
function showHelpDocs(cwd) {
|
|
325
|
+
console.log('\n--- Design system setup ---');
|
|
326
|
+
console.log('Design themes (viui-themes) are in: src/assets/styles/viui-themes/');
|
|
327
|
+
console.log('Available theme ids: minimalist-2 (default), neo-brutalism, material');
|
|
328
|
+
console.log('To use a theme: set VITE_VIUI_THEME in .env and add in vuetify.ts:');
|
|
329
|
+
console.log(" import './viui-conf/apply-theme-body'");
|
|
330
|
+
console.log('Details: src/plugins/viui-conf/defaults/README.md');
|
|
331
|
+
console.log('CLI docs: pnpm exec viui-cli --help');
|
|
332
|
+
console.log('---\n');
|
|
333
|
+
}
|
|
334
|
+
async function runConfigureTheme(cwd, rl, opts) {
|
|
335
|
+
console.log('\n Design theme (viui-themes)');
|
|
336
|
+
console.log(' Available: minimalist-2 (default), neo-brutalism, material\n');
|
|
337
|
+
console.log(' a) Use minimalist-2 (default) — thin borders, flat');
|
|
338
|
+
console.log(' b) Use neo-brutalism — thick borders, hard shadow');
|
|
339
|
+
console.log(' c) Use material — elevated, standard Material');
|
|
340
|
+
console.log(' d) Skip / configure manually — set .env yourself\n');
|
|
341
|
+
const a = await rl.question(' Choose (a-d, or Enter for a): ');
|
|
342
|
+
const choice = (a.trim().toLowerCase() || 'a')[0];
|
|
343
|
+
const map = {
|
|
344
|
+
a: 'minimalist-2',
|
|
345
|
+
b: 'neo-brutalism',
|
|
346
|
+
c: 'material',
|
|
347
|
+
d: 'skip',
|
|
348
|
+
};
|
|
349
|
+
const themeId = map[choice] ?? map.a;
|
|
350
|
+
if (themeId === 'skip') {
|
|
351
|
+
console.log(' Skipped. Set VITE_VIUI_THEME in .env and add import in vuetify.ts. See src/plugins/viui-conf/defaults/README.md');
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
setEnvTheme(cwd, themeId);
|
|
355
|
+
const vuetifyPath = path.join(cwd, 'src', 'plugins', 'vuetify.ts');
|
|
356
|
+
const hasImport = fs.existsSync(vuetifyPath) && /apply-theme-body|applyThemeBody/.test(fs.readFileSync(vuetifyPath, 'utf8'));
|
|
357
|
+
if (!hasImport && fs.existsSync(vuetifyPath)) {
|
|
358
|
+
const ans = await rl.question(' Add apply-theme-body import to vuetify.ts? (Y/n): ');
|
|
359
|
+
const add = opts.yes || (ans.trim().toLowerCase() === '' || ans.trim().toLowerCase() === 'y' || ans.trim().toLowerCase() === 'yes');
|
|
360
|
+
if (add)
|
|
361
|
+
ensureApplyThemeBodyInVuetify(cwd);
|
|
362
|
+
}
|
|
363
|
+
console.log(' Theme configured:', themeId);
|
|
364
|
+
}
|
|
233
365
|
async function runSetup(cwd, assetsDir, opts) {
|
|
234
366
|
const isPostinstall = opts.postinstall === true;
|
|
235
367
|
if (isPostinstall && !process.stdin.isTTY) {
|
|
@@ -243,50 +375,97 @@ async function runSetup(cwd, assetsDir, opts) {
|
|
|
243
375
|
}
|
|
244
376
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
245
377
|
const ask = async (q, defaultYes) => {
|
|
246
|
-
const
|
|
247
|
-
const a = await rl.question(`${q} (${def}): `);
|
|
378
|
+
const a = await rl.question(`${q} (${defaultYes ? 'Y/n' : 'y/N'}): `);
|
|
248
379
|
const s = a.trim().toLowerCase();
|
|
249
380
|
if (s === '')
|
|
250
381
|
return defaultYes;
|
|
251
382
|
return s === 'y' || s === 'yes';
|
|
252
383
|
};
|
|
253
384
|
console.log(`\n${pkg.name ?? '@viui/cli'} — Setup i-design-system in this repo\n`);
|
|
385
|
+
console.log(' This will: sync .cursor, viui-conf, viui-themes; optional @viui/web, theme config, postinstall.\n');
|
|
254
386
|
const runNow = opts.yes || (await ask('Run setup now?', true));
|
|
255
387
|
if (!runNow) {
|
|
256
388
|
console.log('Skipped. Run later: pnpm exec viui-cli setup');
|
|
257
389
|
rl.close();
|
|
258
390
|
return;
|
|
259
391
|
}
|
|
392
|
+
const pm = detectPackageManager(cwd);
|
|
260
393
|
const cursorDir = path.join(cwd, '.cursor');
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
console.log('
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
394
|
+
for (;;) {
|
|
395
|
+
console.log('\n What do you want to do?\n');
|
|
396
|
+
console.log(' 1) Full setup (recommended) — init/sync .cursor + viui-conf + viui-themes, install @viui/web, configure theme, add postinstall');
|
|
397
|
+
console.log(' 2) Sync only — update .cursor, viui-conf, viui-themes (no npm install)');
|
|
398
|
+
console.log(' 3) Install @viui/web only — add Vi* components dependency');
|
|
399
|
+
console.log(' 4) Configure design theme — set VITE_VIUI_THEME in .env and ensure body class is applied');
|
|
400
|
+
console.log(' 5) Add postinstall script — auto-sync on every pnpm install');
|
|
401
|
+
console.log(' 6) Show help / docs — print links and next steps\n');
|
|
402
|
+
const choiceInput = await rl.question(' Choose (1-6, or Enter for 1): ');
|
|
403
|
+
const choice = (choiceInput.trim() || '1').replace(/[^1-6]/, '1');
|
|
404
|
+
const n = parseInt(choice, 10);
|
|
405
|
+
if (n < 1 || n > 6)
|
|
406
|
+
continue;
|
|
407
|
+
if (n === 1) {
|
|
408
|
+
if (!fs.existsSync(cursorDir)) {
|
|
409
|
+
console.log('\nStep: Initializing .cursor, viui-conf, viui-themes...');
|
|
410
|
+
runInit(cwd, assetsDir, { yes: true, dryRun: false, noPlans: false });
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
console.log('\nStep: Syncing .cursor, viui-conf, viui-themes...');
|
|
414
|
+
runSync(cwd, assetsDir, { dryRun: false, backup: false });
|
|
415
|
+
}
|
|
416
|
+
console.log('\nStep: Installing @viui/web...');
|
|
417
|
+
const { execSync } = await import('node:child_process');
|
|
418
|
+
const addCmd = pm === 'pnpm' ? 'pnpm add @viui/web' : pm === 'yarn' ? 'yarn add @viui/web' : 'npm install @viui/web';
|
|
419
|
+
execSync(addCmd, { cwd, stdio: 'inherit' });
|
|
420
|
+
await runConfigureTheme(cwd, rl, { yes: opts.yes });
|
|
421
|
+
if (!isPostinstall && (opts.yes || (await ask('\nAdd postinstall to auto-sync on install?', false)))) {
|
|
422
|
+
if (addPostinstallScript(cwd, pm))
|
|
423
|
+
console.log('Added postinstall script to package.json.');
|
|
424
|
+
}
|
|
425
|
+
console.log('\nDone. Use: import { ViButton, ViInput, ... } from \'@viui/web\'');
|
|
426
|
+
console.log('Design themes: src/assets/styles/viui-themes/ — set VITE_VIUI_THEME in .env; see src/plugins/viui-conf/defaults/README.md');
|
|
427
|
+
break;
|
|
270
428
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (addPostinstallScript(cwd, pm)) {
|
|
283
|
-
console.log('Added postinstall script to package.json.');
|
|
429
|
+
if (n === 2) {
|
|
430
|
+
if (!fs.existsSync(cursorDir)) {
|
|
431
|
+
console.log('\nInitializing .cursor, viui-conf, viui-themes...');
|
|
432
|
+
runInit(cwd, assetsDir, { yes: true, dryRun: false, noPlans: false });
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
console.log('\nSyncing .cursor, viui-conf, viui-themes...');
|
|
436
|
+
runSync(cwd, assetsDir, { dryRun: false, backup: false });
|
|
437
|
+
}
|
|
438
|
+
console.log('Sync done.');
|
|
439
|
+
break;
|
|
284
440
|
}
|
|
285
|
-
|
|
286
|
-
|
|
441
|
+
if (n === 3) {
|
|
442
|
+
const { execSync } = await import('node:child_process');
|
|
443
|
+
const addCmd = pm === 'pnpm' ? 'pnpm add @viui/web' : pm === 'yarn' ? 'yarn add @viui/web' : 'npm install @viui/web';
|
|
444
|
+
console.log('\nRunning:', addCmd);
|
|
445
|
+
execSync(addCmd, { cwd, stdio: 'inherit' });
|
|
446
|
+
console.log('Done. Use: import { ViButton, ViInput, ... } from \'@viui/web\'');
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
if (n === 4) {
|
|
450
|
+
await runConfigureTheme(cwd, rl, { yes: opts.yes });
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
if (n === 5) {
|
|
454
|
+
if (addPostinstallScript(cwd, pm)) {
|
|
455
|
+
console.log('\nAdded postinstall script to package.json.');
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
console.log('\npostinstall already contains viui-cli or package.json not found.');
|
|
459
|
+
}
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
if (n === 6) {
|
|
463
|
+
showHelpDocs(cwd);
|
|
464
|
+
const again = opts.yes || (await ask('Show menu again?', true));
|
|
465
|
+
if (!again)
|
|
466
|
+
break;
|
|
287
467
|
}
|
|
288
468
|
}
|
|
289
|
-
console.log('\nDone. Use: import { ViButton, ViInput, ... } from \'@viui/web\'');
|
|
290
469
|
rl.close();
|
|
291
470
|
}
|
|
292
471
|
const result = parseArgs({
|