@hobui/viui-cli 0.0.3 → 0.0.5

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 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,5 @@
1
+ /**
2
+ * @viui/themes — Bento Grid (global alias).
3
+ * Same content as bento-grid.scss; physical file so Vite/consumers resolve without package exports.
4
+ */
5
+ @forward './bento-grid';
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @viui/themes — Bento Grid entry (scoped).
3
+ */
4
+ @use './_bento-grid' as *;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @viui/themes — Glassmorphism (global alias).
3
+ * Same content as glassmorphism.scss; physical file so Vite/consumers resolve without package exports.
4
+ */
5
+ @forward './glassmorphism';
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @viui/themes — Glassmorphism entry (scoped).
3
+ */
4
+ @use './_glassmorphism' as *;
@@ -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 *;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @viui/themes — Material (global alias).
3
+ * Same content as material.scss; physical file so Vite/consumers resolve without package exports.
4
+ */
5
+ @forward './material';
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @viui/themes — Material entry (scoped).
3
+ */
4
+ @use './_material' as *;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @viui/themes — Minimalist 2.0 (global alias).
3
+ * Same content as minimalist-2.scss; physical file so Vite/consumers resolve without package exports.
4
+ */
5
+ @forward './minimalist-2';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @viui/themes — Minimalist 2.0 entry.
3
+ * Scoped to .components-overview-wrapper.design-style-minimalist-2 and body.
4
+ */
5
+ @use './_minimalist-2' as *;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @viui/themes — Minimalist (global alias).
3
+ * Same content as minimalist.scss; physical file so Vite/consumers resolve without package exports.
4
+ */
5
+ @forward './minimalist';
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @viui/themes — Minimalist entry (scoped).
3
+ */
4
+ @use './_minimalist' as *;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @viui/themes — Neo-Brutalism (global alias).
3
+ * Same content as neo-brutalism.scss; physical file so Vite/consumers resolve without package exports.
4
+ */
5
+ @forward './neo-brutalism';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @viui/themes — Neo-Brutalism entry.
3
+ * Scoped to .components-overview-wrapper.design-style-neo-brutalism.
4
+ */
5
+ @use './_neo-brutalism' 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')))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hobui/viui-cli",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "CLI to sync i-design-system .cursor (rules, skills, commands) into consumer repos",
5
5
  "type": "module",
6
6
  "files": [