@fwkui/x-css 1.0.11 → 1.0.13
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/LICENSE +21 -0
- package/README.md +351 -204
- package/dist/index-auto.js +11 -11
- package/dist/index-auto.js.map +1 -1
- package/dist/index-auto.mjs +11 -11
- package/dist/index-auto.mjs.map +1 -1
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +11 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +11 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,101 +1,34 @@
|
|
|
1
|
-
# @fwkui/x-css
|
|
1
|
+
# @fwkui/x-css
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@fwkui/x-css` là utility CSS engine siêu nhẹ, parse class theo cú pháp ngắn và sinh CSS runtime theo layer + media.
|
|
4
|
+
|
|
5
|
+
Mục tiêu của README này:
|
|
6
|
+
1. Người dùng có thể tích hợp ngay.
|
|
7
|
+
2. AI có thể suy luận đúng cú pháp để sinh class dùng được ngay.
|
|
8
|
+
3. QA có thể kiểm thử parser theo vector cố định.
|
|
4
9
|
|
|
5
10
|

|
|
6
11
|

|
|
7
12
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
## 📖 Hướng dẫn Cơ bản
|
|
11
|
-
|
|
12
|
-
### 1. Cú pháp Cốt lõi
|
|
13
|
-
Mỗi class trong @fwkui/x-css được cấu tạo theo công thức:
|
|
14
|
-
`[Media]:[Layer][Property][Value][@Selector]`
|
|
15
|
-
|
|
16
|
-
**Thứ tự parse chính xác:**
|
|
17
|
-
1. `Media` (tùy chọn) + dấu `:`
|
|
18
|
-
2. `Layer` (tùy chọn, dạng số)
|
|
19
|
-
3. `Property` (bắt buộc)
|
|
20
|
-
4. `Value` (bắt buộc)
|
|
21
|
-
5. `@Selector` (tùy chọn, nằm cuối class)
|
|
22
|
-
|
|
23
|
-
**Media mặc định và thứ tự áp dụng nội bộ:**
|
|
24
|
-
|
|
25
|
-
| Key | Query |
|
|
26
|
-
| :--- | :--- |
|
|
27
|
-
| `default` | Không bọc `@media` (áp dụng trực tiếp) |
|
|
28
|
-
| `xs` | `screen and (max-width: 575px)` |
|
|
29
|
-
| `sm` | `screen and (min-width: 576px)` |
|
|
30
|
-
| `md` | `screen and (min-width: 768px)` |
|
|
31
|
-
| `lg` | `screen and (min-width: 992px)` |
|
|
32
|
-
| `xl` | `screen and (min-width: 1200px)` |
|
|
33
|
-
| `2xl` | `screen and (min-width: 1400px)` |
|
|
34
|
-
| `sma` | `screen and (max-width: 768px)` |
|
|
35
|
-
| `mda` | `screen and (max-width: 992px)` |
|
|
36
|
-
| `lga` | `screen and (max-width: 1200px)` |
|
|
37
|
-
| `xla` | `screen and (max-width: 1400px)` |
|
|
38
|
-
|
|
39
|
-
`breakpoints` custom sẽ được nối vào sau danh sách trên. Nếu trùng key, giá trị khai báo sau cùng sẽ ghi đè key trước đó.
|
|
40
|
-
|
|
41
|
-
**Layer mặc định và dải số:**
|
|
42
|
-
- Nếu không truyền `Layer`, hệ thống dùng mặc định `0`.
|
|
43
|
-
- Engine tạo sẵn 24 layer: `l0` đến `l23`.
|
|
44
|
-
- Để thứ tự ổn định, nên dùng dải số `0-23`.
|
|
45
|
-
- Số layer lớn hơn sẽ có độ ưu tiên cascade cao hơn trong cùng media.
|
|
46
|
-
|
|
47
|
-
**Ví dụ:**
|
|
48
|
-
- `m10px` ⮕ `margin: 10px`
|
|
49
|
-
- `cRed` ⮕ `color: red`
|
|
50
|
-
- `sm:p20px` ⮕ `@media (min-width: 576px) { padding: 20px }`
|
|
51
|
-
- `3bgWhite` ⮕ `@layer l3 { background: white }`
|
|
52
|
-
- `sm:3bgWhite` ⮕ `@media (min-width: 576px) { @layer l3 { background: white } }`
|
|
53
|
-
- `cBlue@:hover` ⮕ `.class:hover { color: blue }`
|
|
54
|
-
|
|
55
|
-
### 2. Nguyên lý Parser (Scan & Slice) 🧠
|
|
56
|
-
Thư viện quét class từ trái sang phải và tự động cắt Property/Value dựa trên các điểm ngắt (Số, Chữ Hoa, Ký tự đặc biệt...), giúp tốc độ xử lý nhanh hơn ~1.6x so với Regex truyền thống.
|
|
57
|
-
|
|
58
|
-
| Loại điểm ngắt | Ví dụ Class | Phân tích (Prop \| Value) |
|
|
59
|
-
| :--- | :--- | :--- |
|
|
60
|
-
| **Số (0-9)** | `w100px` | `w` (width) \| `100px` |
|
|
61
|
-
| **Chữ Hoa (A-Z)** | `dF` | `d` (display) \| `F` |
|
|
62
|
-
| **Dấu gạch ngang + Số** | `m-10px` | `m` (margin) \| `-10px` |
|
|
63
|
-
|
|
64
|
-
> [!IMPORTANT]
|
|
65
|
-
> **Lưu ý về CamelCase**: Sử dụng `mt10px` hoặc `margin-top-10px`, tránh `marginTop10px` để đảm bảo parser hoạt động chính xác.
|
|
66
|
-
>
|
|
67
|
-
> **Lưu ý quan trọng về Value viết tắt bằng chữ cái**:
|
|
68
|
-
> - Value dạng chữ cái phải viết hoa ký tự đầu (ví dụ: `bdN`, `dF`, `posA`).
|
|
69
|
-
> - Tránh viết thường toàn bộ như `bdn`, `df`, `posa`.
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
## 📚 Bộ Từ điển (Dictionary)
|
|
74
|
-
Danh sách đầy đủ các từ viết tắt được cập nhật liên tục tại [DICTIONARY.md](./DICTIONARY.md).
|
|
75
|
-
|
|
76
|
-
### Một số Alias phổ biến:
|
|
77
|
-
- **Layout**: `d` (display), `pos` (position), `z` (z-index), `fl` (float).
|
|
78
|
-
- **Flexbox**: `fx` (flex), `ai` (align-items), `jc` (justify-content).
|
|
79
|
-
- **Spacing**: `m` (margin), `p` (padding), `w` (width), `h` (height).
|
|
80
|
-
- **Styling**: `c` (color), `bg` (background), `bd` (border), `op` (opacity).
|
|
81
|
-
- **Typography**: `fns` (font-size), `fw` (font-weight), `ta` (text-align).
|
|
13
|
+
## Cài Đặt Nhanh
|
|
82
14
|
|
|
83
|
-
|
|
15
|
+
### 1) NPM
|
|
84
16
|
|
|
85
|
-
|
|
17
|
+
```bash
|
|
18
|
+
npm install @fwkui/x-css
|
|
19
|
+
```
|
|
86
20
|
|
|
87
|
-
|
|
21
|
+
### 2) Dùng trực tiếp qua URL (không cần bundler)
|
|
88
22
|
|
|
89
|
-
|
|
23
|
+
Lưu ý:
|
|
24
|
+
1. `dist/index.js` là CommonJS (Node).
|
|
25
|
+
2. Trình duyệt dùng `dist/index.mjs` hoặc `dist/index-auto.mjs`.
|
|
90
26
|
|
|
91
|
-
|
|
92
|
-
> Bản `dist/index.js` là CommonJS cho môi trường Node.
|
|
93
|
-
> Khi chạy trực tiếp trên trình duyệt, hãy dùng `dist/index.mjs` hoặc `dist/index-auto.mjs`.
|
|
27
|
+
Option A: chủ động khởi tạo
|
|
94
28
|
|
|
95
|
-
**Option A - Tự khởi tạo (khuyên dùng):**
|
|
96
29
|
```html
|
|
97
30
|
<script type="module">
|
|
98
|
-
import xcss from 'https://unpkg.com/@fwkui/x-css@
|
|
31
|
+
import xcss from 'https://unpkg.com/@fwkui/x-css@latest/dist/index.mjs';
|
|
99
32
|
|
|
100
33
|
xcss.cssObserve(document, {
|
|
101
34
|
dictionaryImport: true
|
|
@@ -103,65 +36,195 @@ Bạn có thể tải thư viện hoặc xem mã nguồn tại: [https://github.
|
|
|
103
36
|
</script>
|
|
104
37
|
```
|
|
105
38
|
|
|
106
|
-
|
|
39
|
+
Option B: auto observe khi import
|
|
40
|
+
|
|
107
41
|
```html
|
|
108
|
-
<script type="module" src="https://unpkg.com/@fwkui/x-css@
|
|
42
|
+
<script type="module" src="https://unpkg.com/@fwkui/x-css@latest/dist/index-auto.mjs"></script>
|
|
109
43
|
```
|
|
110
44
|
|
|
111
|
-
|
|
112
|
-
`https://cdn.jsdelivr.net/npm/@fwkui/x-css@
|
|
45
|
+
CDN thay thế:
|
|
46
|
+
`https://cdn.jsdelivr.net/npm/@fwkui/x-css@latest/dist/index.mjs`
|
|
113
47
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
48
|
+
## Dùng Trong 60 Giây
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
import xcss from '@fwkui/x-css';
|
|
52
|
+
|
|
53
|
+
xcss.cssObserve(document);
|
|
117
54
|
```
|
|
118
55
|
|
|
119
|
-
|
|
56
|
+
```html
|
|
57
|
+
<button class="dF aiC jcC p10px;16px bdN bdra8px bgc#0a64e8 cWhite">
|
|
58
|
+
Đăng nhập
|
|
59
|
+
</button>
|
|
60
|
+
```
|
|
120
61
|
|
|
121
|
-
##
|
|
62
|
+
## Contract Cú Pháp
|
|
122
63
|
|
|
123
|
-
|
|
64
|
+
Mỗi utility class theo form:
|
|
124
65
|
|
|
125
|
-
|
|
66
|
+
`[Media]:[Layer][Property][Value][@Selector]`
|
|
126
67
|
|
|
127
|
-
|
|
128
|
-
|
|
68
|
+
Thứ tự parse bắt buộc:
|
|
69
|
+
1. `selector` (hậu tố `@...`, nằm ngoài `[]`).
|
|
70
|
+
2. `media` (tiền tố trước `:`).
|
|
71
|
+
3. `layer` (chuỗi số liên tiếp ở đầu).
|
|
72
|
+
4. `property`.
|
|
73
|
+
5. `value`.
|
|
129
74
|
|
|
130
|
-
|
|
131
|
-
|
|
75
|
+
Ý nghĩa từng phần:
|
|
76
|
+
1. `Media` (tùy chọn): key media như `sm`, `md`, `lg`, hoặc key custom.
|
|
77
|
+
2. `Layer` (tùy chọn): số ưu tiên cascade.
|
|
78
|
+
3. `Property` (bắt buộc): alias thuộc dictionary hoặc CSS property hợp lệ.
|
|
79
|
+
4. `Value` (bắt buộc với utility chuẩn): giá trị CSS, alias value hoặc arbitrary value.
|
|
80
|
+
5. `@Selector` (tùy chọn): ví dụ `@:hover`, `@::before`.
|
|
81
|
+
|
|
82
|
+
Ngoại lệ parser (special syntax):
|
|
83
|
+
1. `[AliasName]`: class group alias (không dùng value trực tiếp).
|
|
84
|
+
2. `&...`: nhánh selector đặc biệt theo parser hiện tại.
|
|
85
|
+
|
|
86
|
+
### Media Mặc Định Và Thứ Tự Nội Bộ
|
|
87
|
+
|
|
88
|
+
Engine nạp media theo thứ tự:
|
|
89
|
+
|
|
90
|
+
| Thứ tự | Key | Query |
|
|
91
|
+
| :--- | :--- | :--- |
|
|
92
|
+
| 1 | `default` | Không bọc `@media` |
|
|
93
|
+
| 2 | `xs` | `screen and (max-width: 575px)` |
|
|
94
|
+
| 3 | `sm` | `screen and (min-width: 576px)` |
|
|
95
|
+
| 4 | `md` | `screen and (min-width: 768px)` |
|
|
96
|
+
| 5 | `lg` | `screen and (min-width: 992px)` |
|
|
97
|
+
| 6 | `xl` | `screen and (min-width: 1200px)` |
|
|
98
|
+
| 7 | `2xl` | `screen and (min-width: 1400px)` |
|
|
99
|
+
| 8 | `sma` | `screen and (max-width: 768px)` |
|
|
100
|
+
| 9 | `mda` | `screen and (max-width: 992px)` |
|
|
101
|
+
| 10 | `lga` | `screen and (max-width: 1200px)` |
|
|
102
|
+
| 11 | `xla` | `screen and (max-width: 1400px)` |
|
|
103
|
+
|
|
104
|
+
Quy tắc custom breakpoint:
|
|
105
|
+
1. `breakpoints` được nối vào sau danh sách mặc định.
|
|
106
|
+
2. Nếu trùng key, key khai báo sau cùng ghi đè key trước (`last write wins`).
|
|
107
|
+
|
|
108
|
+
Ví dụ:
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
xcss.cssObserve(document, {
|
|
112
|
+
breakpoints: [
|
|
113
|
+
{ tablet: 'screen and (min-width: 768px)' }
|
|
114
|
+
]
|
|
115
|
+
});
|
|
132
116
|
```
|
|
133
117
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
118
|
+
Dùng class: `tablet:dB`.
|
|
119
|
+
|
|
120
|
+
### Layer Mặc Định
|
|
121
|
+
|
|
122
|
+
1. Nếu không khai báo layer, engine dùng `0`.
|
|
123
|
+
2. Engine tạo sẵn 24 layer: `l0 -> l23`.
|
|
124
|
+
3. Nên dùng dải `0-23` để giữ thứ tự ổn định.
|
|
125
|
+
4. Số layer lớn hơn có ưu tiên cascade cao hơn trong cùng media.
|
|
126
|
+
|
|
127
|
+
## Quy Tắc Điểm Ngắt Đầy Đủ (Theo Parser Hiện Tại)
|
|
128
|
+
|
|
129
|
+
Mục tiêu là tách class thành tuple:
|
|
130
|
+
`{ media, layer, property, value, selector }`
|
|
131
|
+
|
|
132
|
+
Thứ tự suy luận bắt buộc:
|
|
133
|
+
1. Tách `selector`: lấy phần sau ký tự `@` cuối cùng, chỉ khi `@` nằm ngoài `[]`.
|
|
134
|
+
2. Tách `media`: nếu còn `:` thì phần trước `:` là `media`.
|
|
135
|
+
3. Tách `layer`: đọc chuỗi số liên tiếp ở đầu phần còn lại.
|
|
136
|
+
4. Tách `property/value`: quét trái -> phải và dừng `property` theo bảng quyết định.
|
|
137
|
+
5. Nếu phần còn lại bắt đầu bằng `&` hoặc `[` thì đi vào nhánh special syntax.
|
|
138
|
+
|
|
139
|
+
### Bảng Quyết Định Khi Quét `property`
|
|
140
|
+
|
|
141
|
+
| Ký tự đang xét | Điều kiện | Hành động |
|
|
142
|
+
| :--- | :--- | :--- |
|
|
143
|
+
| `a-z` | luôn đúng | vẫn là `property` |
|
|
144
|
+
| `-` hoặc `.` | ký tự kế tiếp là số | dừng `property`, phần còn lại là `value` |
|
|
145
|
+
| `-` | gặp `--` và đã có ít nhất 1 ký tự property | dừng `property`, bắt đầu `value` (CSS variable) |
|
|
146
|
+
| `-` hoặc `.` | không rơi vào 2 điều kiện trên | vẫn là `property` |
|
|
147
|
+
| ký tự khác (`A-Z`, `0-9`, `#`, `!`, `[`, `(`, `%`, ...) | luôn đúng | dừng `property`, phần còn lại là `value` |
|
|
148
|
+
|
|
149
|
+
### Chuẩn Hóa `value` Sau Khi Tách
|
|
150
|
+
|
|
151
|
+
1. Value bắt đầu bằng `!` -> thêm hậu tố `!important`.
|
|
152
|
+
2. Value bắt đầu bằng `--` -> đổi thành `var(--...)`.
|
|
153
|
+
3. Value dạng `[...]` -> bỏ `[` `]`, rồi thay `;` thành khoảng trắng.
|
|
154
|
+
4. Ký tự `#` trong value giữ nguyên (hex color).
|
|
155
|
+
|
|
156
|
+
Pseudo-flow cho AI:
|
|
157
|
+
|
|
158
|
+
```text
|
|
159
|
+
class -> selector -> media -> layer -> property -> value
|
|
160
|
+
if value startsWith('!') => important
|
|
161
|
+
if value startsWith('--') => var(value)
|
|
162
|
+
if value is bracketed [..] => strip brackets + replace ';' with ' '
|
|
137
163
|
```
|
|
138
164
|
|
|
139
|
-
|
|
165
|
+
### Test Vector Mini (10 input -> expected tuple)
|
|
166
|
+
|
|
167
|
+
| # | Input | Expected tuple |
|
|
168
|
+
| :--- | :--- | :--- |
|
|
169
|
+
| 1 | `m10px` | `{ media: '', layer: '', property: 'm', value: '10px', selector: '' }` |
|
|
170
|
+
| 2 | `md:w100%` | `{ media: 'md', layer: '', property: 'w', value: '100%', selector: '' }` |
|
|
171
|
+
| 3 | `sm:3bgWhite` | `{ media: 'sm', layer: '3', property: 'bg', value: 'White', selector: '' }` |
|
|
172
|
+
| 4 | `cBlue@:hover` | `{ media: '', layer: '', property: 'c', value: 'Blue', selector: ':hover' }` |
|
|
173
|
+
| 5 | `m-10px` | `{ media: '', layer: '', property: 'm', value: '-10px', selector: '' }` |
|
|
174
|
+
| 6 | `opc0.8` | `{ media: '', layer: '', property: 'opc', value: '0.8', selector: '' }` |
|
|
175
|
+
| 7 | `bgc--brand` | `{ media: '', layer: '', property: 'bgc', value: '--brand', selector: '' }` |
|
|
176
|
+
| 8 | `c!#0a64e8` | `{ media: '', layer: '', property: 'c', value: '!#0a64e8', selector: '' }` |
|
|
177
|
+
| 9 | `w[calc(100%;-;10px)]` | `{ media: '', layer: '', property: 'w', value: '[calc(100%;-;10px)]', selector: '' }` |
|
|
178
|
+
| 10 | `[btnPrimary]` | `{ media: '', layer: '', property: '[btnPrimary]', value: '', selector: '' }` |
|
|
179
|
+
|
|
180
|
+
## Bảng Sai -> Đúng (Những Lỗi Gây Vỡ Parse)
|
|
181
|
+
|
|
182
|
+
| Sai | Đúng | Giải thích |
|
|
183
|
+
| :--- | :--- | :--- |
|
|
184
|
+
| `bdn` | `bdN` | Value viết tắt dạng chữ cái phải viết hoa ký tự đầu (`N` = none). |
|
|
185
|
+
| `df` | `dF` | `F` là value viết tắt của `flex`. |
|
|
186
|
+
| `posa` | `posA` | `A` là value viết tắt của `absolute`. |
|
|
187
|
+
| `tr0.2s` | `tran0.2s` | Property `transition` là `tran`, không phải `tr`. |
|
|
188
|
+
| `op0.8` | `opc0.8` | `op` là `object-position`; `opc` mới là `opacity`. |
|
|
189
|
+
| `3:bgWhite` | `3bgWhite` | Layer là số đứng liền trước property, không có `:` sau layer. |
|
|
190
|
+
| `hover:cRed` | `cRed@:hover` | Selector modifier dùng hậu tố `@Selector`. |
|
|
191
|
+
| `tablet:dB` (chưa khai báo) | `tablet:dB` + `breakpoints` config | Media custom phải được khai báo trước trong config. |
|
|
192
|
+
| `m--10px` | `m-10px` | Số âm dùng `-`; `--` dành cho CSS variable (`bgc--brand`). |
|
|
193
|
+
| `bgcbrand` | `bgcBrand` hoặc `bgc--brand` | Cần điểm ngắt rõ ràng để parser tách đúng value. |
|
|
194
|
+
| `wcalc(100%-10px)` | `w[calc(100%;-;10px)]` | Value phức tạp nên bọc `[]`, dùng `;` để biểu diễn khoảng trắng. |
|
|
195
|
+
| `!cRed` | `c!Red` | `!` phải đứng trong phần value (sau property), không đứng đầu class. |
|
|
196
|
+
|
|
197
|
+
## Ví Dụ Chính Xác Theo Dictionary
|
|
198
|
+
|
|
199
|
+
Danh sách đầy đủ alias xem tại [DICTIONARY.md](./DICTIONARY.md).
|
|
200
|
+
|
|
201
|
+
Một số alias dễ nhầm:
|
|
202
|
+
1. `op` = `object-position`
|
|
203
|
+
2. `opc` = `opacity`
|
|
204
|
+
3. `tran` = `transition`
|
|
205
|
+
4. `tr` = `transparent` (value alias, không phải property transition)
|
|
206
|
+
|
|
207
|
+
Ví dụ:
|
|
140
208
|
|
|
141
209
|
```html
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
210
|
+
<div class="dF aiC jcSB p12px;16px bdN bgcWhite"></div>
|
|
211
|
+
<div class="tran0.2s opc0.8@:hover"></div>
|
|
212
|
+
<div class="c!#0a64e8"></div>
|
|
213
|
+
<div class="w[calc(100%;-;10px)]"></div>
|
|
146
214
|
```
|
|
147
215
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
Sử dụng `clsx` để kết hợp class động và tối ưu việc gom nhóm string (tương tự `classnames` nhưng tích hợp sẵn parser engine):
|
|
216
|
+
## Dùng Trong React / Component
|
|
151
217
|
|
|
152
218
|
```jsx
|
|
153
219
|
import { clsx } from '@fwkui/x-css';
|
|
154
220
|
|
|
155
|
-
function Button({ primary, children }) {
|
|
221
|
+
export function Button({ primary, children }) {
|
|
156
222
|
return (
|
|
157
|
-
<button
|
|
223
|
+
<button
|
|
158
224
|
className={clsx(
|
|
159
|
-
'p10px;
|
|
160
|
-
'
|
|
161
|
-
'
|
|
162
|
-
'cWhite', // color: white
|
|
163
|
-
primary ? 'bgBlue' : 'bgGray',
|
|
164
|
-
'opc0.8@:hover' // opacity: 0.8 when hover
|
|
225
|
+
'dF aiC jcC p10px;16px bdN bdra8px tran0.2s',
|
|
226
|
+
primary ? 'bgc#0a64e8 cWhite' : 'bgc#e5e7eb c#111827',
|
|
227
|
+
'opc0.9@:hover'
|
|
165
228
|
)}
|
|
166
229
|
>
|
|
167
230
|
{children}
|
|
@@ -170,133 +233,217 @@ function Button({ primary, children }) {
|
|
|
170
233
|
}
|
|
171
234
|
```
|
|
172
235
|
|
|
173
|
-
|
|
236
|
+
## Cấu Hình
|
|
174
237
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
```javascript
|
|
238
|
+
```js
|
|
178
239
|
import xcss from '@fwkui/x-css';
|
|
179
240
|
|
|
180
241
|
xcss.cssObserve(document, {
|
|
181
|
-
// Thêm màu sắc hoặc giá trị custom
|
|
182
242
|
theme: {
|
|
183
|
-
brand: '#
|
|
184
|
-
|
|
243
|
+
brand: '#0a64e8',
|
|
244
|
+
danger: '#ef4444'
|
|
185
245
|
},
|
|
186
|
-
// Thêm breakpoint tùy chỉnh
|
|
187
246
|
breakpoints: [
|
|
188
247
|
{ tablet: 'screen and (min-width: 768px)' }
|
|
189
248
|
],
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
prefix: 'fk-', // Chỉ xử lý các class bắt đầu bằng 'fk-'
|
|
195
|
-
|
|
196
|
-
// [New] Điều khiển import dictionary viết tắt
|
|
197
|
-
// true (mặc định): dùng dictionary.ts của thư viện
|
|
198
|
-
// false: tắt dictionary mặc định
|
|
199
|
-
// string URL/path: import dictionary external
|
|
249
|
+
base: 'body{margin:0;font-family:system-ui,sans-serif;}',
|
|
250
|
+
prefix: 'fk-',
|
|
251
|
+
excludePrefixes: ['bs-', 'rs-'],
|
|
252
|
+
excludes: ['legacy-*'],
|
|
200
253
|
dictionaryImport: true
|
|
201
254
|
});
|
|
202
255
|
```
|
|
203
256
|
|
|
204
|
-
Sau đó
|
|
205
|
-
|
|
257
|
+
Sau đó dùng class: `fk-cBrand fk-tablet:dB`.
|
|
258
|
+
|
|
259
|
+
Gợi ý tối ưu bỏ qua parse:
|
|
260
|
+
1. Dùng `prefix` nếu bạn kiểm soát được class framework (nhanh và sạch nhất).
|
|
261
|
+
2. Dùng `excludePrefixes` để bỏ qua nhanh theo tiền tố, ví dụ `bs-`, `rs-`.
|
|
262
|
+
3. Dùng `excludes` khi cần rule chính xác hoặc wildcard (`*`), ví dụ `legacy-*`, `tmp-debug`.
|
|
206
263
|
|
|
207
|
-
|
|
264
|
+
Ví dụ đầu vào cho `excludes`:
|
|
208
265
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
266
|
+
| Cấu hình | Input class | Kết quả mong đợi |
|
|
267
|
+
| :--- | :--- | :--- |
|
|
268
|
+
| `excludes: ['container']` | `container m10px` | `container` giữ nguyên; chỉ `m10px` được parse thành CSS |
|
|
269
|
+
| `excludes: ['bs-*']` | `bs-btn m10px` | `bs-btn` bị bỏ qua parse; `m10px` vẫn parse |
|
|
270
|
+
| `excludes: ['*-debug']` | `card-debug p8px` | `card-debug` bị bỏ qua parse; `p8px` vẫn parse |
|
|
271
|
+
| `excludes: ['tmp-*', 'legacy-*']` | `tmp-a legacy-card dF` | `tmp-a`, `legacy-card` bị bỏ qua; `dF` vẫn parse |
|
|
272
|
+
| `excludePrefixes: ['bs-', 'rs-']` | `bs-modal rs-open h100%` | `bs-modal`, `rs-open` bị bỏ qua nhanh; `h100%` vẫn parse |
|
|
273
|
+
|
|
274
|
+
Ví dụ output thực tế của `clsx`:
|
|
275
|
+
|
|
276
|
+
| Config | Gọi `clsx(...)` | Output | Ghi chú |
|
|
277
|
+
| :--- | :--- | :--- | :--- |
|
|
278
|
+
| `excludePrefixes: ['bs-'], excludes: ['abc*']` | `clsx('bs-a', 'abcde')` | `bs-a abcde` | Cả hai bị bỏ qua, giữ nguyên class gốc. |
|
|
279
|
+
| `excludePrefixes: ['bs-'], excludes: ['abc*']` | `clsx('bs-a', 'abcde', 'm10px')` | `bs-a abcde D0` | Chỉ `m10px` parse thành class hash. |
|
|
280
|
+
| `excludes: ['bs-', 'abc*']` | `clsx('bs-a', 'abcde', 'm10px')` | `bs-a abcde D0` | `bs-` là exact match nên không bắt `bs-a`; `bs-a` vẫn giữ nguyên vì token không sinh CSS hợp lệ. |
|
|
281
|
+
| `excludes: ['abc*def']` | `clsx('abcXYZdef', 'm10px')` | `abcXYZdef D0` | Wildcard giữa chuỗi hoạt động bình thường. |
|
|
282
|
+
| `excludes: ['*-abc']` | `clsx('foo-abc', 'm10px')` | `foo-abc D0` | Wildcard cuối chuỗi hoạt động bình thường. |
|
|
283
|
+
|
|
284
|
+
Lưu ý format output:
|
|
285
|
+
1. `clsx` trả chuỗi class phân tách bằng khoảng trắng, không dùng dấu phẩy.
|
|
286
|
+
2. Token không parse được hoặc bị exclude sẽ giữ nguyên ở output.
|
|
287
|
+
|
|
288
|
+
Lưu ý khi dùng cùng `prefix`:
|
|
289
|
+
1. Engine kiểm tra `prefix` trước, rồi mới kiểm tra `excludes`/`excludePrefixes`.
|
|
290
|
+
2. Nếu có `prefix: 'fk-'`, class không bắt đầu bằng `fk-` đã bị bỏ qua từ đầu.
|
|
291
|
+
3. Vì vậy pattern excludes nên viết theo class thực tế sau khi thêm prefix, ví dụ `excludes: ['fk-bs-*']`, `excludePrefixes: ['fk-rs-']`.
|
|
292
|
+
4. `exclude` vẫn được hỗ trợ để tương thích ngược, nhưng key khuyến nghị là `excludes`.
|
|
293
|
+
|
|
294
|
+
`dictionaryImport`:
|
|
295
|
+
1. `true` (mặc định): dùng dictionary tích hợp.
|
|
296
|
+
2. `false`: tắt dictionary mặc định.
|
|
297
|
+
3. `string` URL/path: import dictionary ngoài.
|
|
298
|
+
|
|
299
|
+
Nếu import dictionary ngoài:
|
|
300
|
+
|
|
301
|
+
```js
|
|
302
|
+
const engine = xcss.css({ dictionaryImport: 'https://cdn.example.com/xcss-dict.mjs' });
|
|
303
|
+
await engine.ready;
|
|
304
|
+
const { clsx, observe } = engine.buildCss(document);
|
|
213
305
|
observe();
|
|
214
306
|
```
|
|
215
307
|
|
|
216
|
-
|
|
308
|
+
Mẫu file để thay thế trực tiếp URL `https://cdn.example.com/xcss-dict.mjs`:
|
|
309
|
+
|
|
310
|
+
```js
|
|
311
|
+
// xcss-dict.mjs
|
|
312
|
+
// Có thể public lên CDN của bạn rồi truyền URL vào dictionaryImport
|
|
313
|
+
|
|
314
|
+
export const SHORT_PROPERTIES = {
|
|
315
|
+
d: 'display',
|
|
316
|
+
c: 'color',
|
|
317
|
+
bgc: 'background-color',
|
|
318
|
+
bd: 'border',
|
|
319
|
+
w: 'width',
|
|
320
|
+
h: 'height',
|
|
321
|
+
p: 'padding',
|
|
322
|
+
m: 'margin',
|
|
323
|
+
tran: 'transition',
|
|
324
|
+
opc: 'opacity'
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
export const COMMON_VALUES = {
|
|
328
|
+
n: 'none',
|
|
329
|
+
b: 'block',
|
|
330
|
+
f: 'flex',
|
|
331
|
+
t: 'transparent',
|
|
332
|
+
i: 'inherit'
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
export const SPECIFIC_VALUES = {
|
|
336
|
+
d: {
|
|
337
|
+
f: 'flex',
|
|
338
|
+
b: 'block',
|
|
339
|
+
ib: 'inline-block'
|
|
340
|
+
},
|
|
341
|
+
bd: {
|
|
342
|
+
n: 'none'
|
|
343
|
+
},
|
|
344
|
+
c: {
|
|
345
|
+
pri: '#0a64e8',
|
|
346
|
+
danger: '#ef4444'
|
|
347
|
+
},
|
|
348
|
+
bgc: {
|
|
349
|
+
pri: '#0a64e8',
|
|
350
|
+
soft: '#e8f1ff'
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
export default {
|
|
355
|
+
SHORT_PROPERTIES,
|
|
356
|
+
COMMON_VALUES,
|
|
357
|
+
SPECIFIC_VALUES
|
|
358
|
+
};
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Quy trình thay link:
|
|
362
|
+
1. Tạo file `xcss-dict.mjs` theo mẫu trên.
|
|
363
|
+
2. Upload lên CDN/public URL của bạn.
|
|
364
|
+
3. Thay `dictionaryImport` bằng URL thật.
|
|
365
|
+
4. Chờ `await engine.ready` trước khi render class.
|
|
366
|
+
|
|
367
|
+
## SSR Và Static Extraction
|
|
217
368
|
|
|
218
|
-
|
|
369
|
+
SSR:
|
|
219
370
|
|
|
220
|
-
```
|
|
371
|
+
```js
|
|
221
372
|
import { getCss } from '@fwkui/x-css';
|
|
222
373
|
|
|
223
|
-
// Trong file layout/server entry
|
|
224
374
|
const styles = getCss();
|
|
225
|
-
|
|
226
|
-
// Inject HTML
|
|
227
|
-
// Inject HTML
|
|
228
375
|
// <style dangerouslySetInnerHTML={{ __html: styles }} />
|
|
229
376
|
```
|
|
230
377
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
Nếu bạn muốn tạo file `.css` tĩnh (dành cho Static Site Generation hoặc Cache build), bạn có thể dùng script sau:
|
|
378
|
+
Static extraction:
|
|
234
379
|
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const xcss = require('@fwkui/x-css');
|
|
380
|
+
```js
|
|
381
|
+
import xcss from '@fwkui/x-css';
|
|
382
|
+
import fs from 'node:fs';
|
|
239
383
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
// Ở đây ví dụ gọi thủ công:
|
|
243
|
-
const { clsx, getCssString } = xcss({
|
|
244
|
-
theme: { brand: '#ff0000' } // Cấu hình (nếu có)
|
|
384
|
+
const { clsx, getCssString } = xcss.css({
|
|
385
|
+
theme: { brand: '#0a64e8' }
|
|
245
386
|
}).buildCss();
|
|
246
387
|
|
|
247
|
-
// Gọi clsx với các class bạn sử dụng trong project
|
|
248
388
|
clsx('m10px p20px cBrand dF');
|
|
249
389
|
|
|
250
|
-
|
|
251
|
-
const cssContent = getCssString();
|
|
252
|
-
|
|
253
|
-
// 3. Ghi ra file
|
|
254
|
-
fs.writeFileSync('./public/styles.css', cssContent);
|
|
255
|
-
console.log('✅ CSS file generated!');
|
|
390
|
+
fs.writeFileSync('./public/styles.css', getCssString());
|
|
256
391
|
```
|
|
257
392
|
|
|
258
|
-
|
|
259
|
-
> **Custom Config SSR**: Nếu dự án dùng config tùy chỉnh (Theme, Prefix...), hãy đảm bảo khởi tạo `xcss(config)` và truyền instance đó xuống các component (qua Context/Props) thay vì dùng `import { clsx }` mặc định. Điều này đảm bảo Server và Client đồng bộ hash.
|
|
393
|
+
## Prompt Mẫu Cho AI (Dùng Thẳng)
|
|
260
394
|
|
|
261
|
-
|
|
395
|
+
Bạn có thể đưa block này vào prompt system/project rules:
|
|
262
396
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
397
|
+
```markdown
|
|
398
|
+
You are using @fwkui/x-css.
|
|
399
|
+
Generate class names strictly with syntax: [Media]:[Layer][Property][Value][@Selector].
|
|
400
|
+
|
|
401
|
+
Rules:
|
|
402
|
+
1. Value is required for normal utility classes.
|
|
403
|
+
2. Layer must be numeric and placed directly before Property (e.g. 3bgWhite).
|
|
404
|
+
3. Selector must be suffix @Selector (e.g. cBlue@:hover).
|
|
405
|
+
4. Use dictionary aliases from DICTIONARY.md.
|
|
406
|
+
5. Keep abbreviation values capitalized when needed (bdN, dF, posA).
|
|
407
|
+
6. For complex CSS values, use bracket notation, and use ';' as space placeholder:
|
|
408
|
+
w[calc(100%;-;10px)].
|
|
409
|
+
7. Use opc for opacity, tran for transition, op for object-position.
|
|
410
|
+
|
|
411
|
+
Before final answer:
|
|
412
|
+
- Validate each class can be parsed into {media, layer, property, value, selector}.
|
|
413
|
+
- Avoid invalid forms like bdn, tr0.2s, op0.8, 3:bgWhite, hover:cRed.
|
|
270
414
|
```
|
|
271
415
|
|
|
272
|
-
|
|
273
|
-
1. **Lần đầu truy cập**: Thư viện load bình thường, sinh CSS và tự động lưu vào `localStorage`.
|
|
274
|
-
2. **Lần sau (F5/Revisit)**: Script trên sẽ chạy ngay lập tức (10-50ms), đọc CSS từ cache và inject vào trang trước khi bất kỳ nội dung nào được render.
|
|
275
|
-
3. **Tự động Invalidate**: Nếu bạn thay đổi config (Theme/Breakpoints), cache cũ sẽ tự động bị xóa để tránh lỗi giao diện.
|
|
416
|
+
Template giao việc cho AI thiết kế UI:
|
|
276
417
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
418
|
+
```markdown
|
|
419
|
+
Thiết kế giao diện [màn hình] bằng @fwkui/x-css.
|
|
420
|
+
Yêu cầu:
|
|
421
|
+
1. Trả về HTML/JSX hoàn chỉnh.
|
|
422
|
+
2. Chỉ dùng class theo cú pháp [Media]:[Layer][Property][Value][@Selector].
|
|
423
|
+
3. Với value phức tạp, dùng [] và ';' thay cho khoảng trắng.
|
|
424
|
+
4. Không dùng class sai quy tắc (bdn, tr0.2s, op0.8, hover:cRed...).
|
|
425
|
+
5. Cuối câu trả lời thêm bảng kiểm:
|
|
426
|
+
- class
|
|
427
|
+
- parsed tuple {media, layer, property, value, selector}
|
|
428
|
+
- css dự kiến
|
|
429
|
+
```
|
|
284
430
|
|
|
285
|
-
|
|
431
|
+
## Checklist QA Trước Khi Build
|
|
286
432
|
|
|
287
|
-
|
|
433
|
+
1. Không còn class sai viết hoa value (`bdn`, `df`, `posa`).
|
|
434
|
+
2. Không dùng nhầm alias (`op`/`opc`, `tr`/`tran`).
|
|
435
|
+
3. Các value phức tạp đều bọc `[]`.
|
|
436
|
+
4. Media custom đã khai báo trong `breakpoints`.
|
|
437
|
+
5. Không có dạng sai layer/selector (`3:bg`, `hover:cRed`).
|
|
438
|
+
6. Test parser với ít nhất bộ 10 test vector ở trên.
|
|
288
439
|
|
|
289
|
-
|
|
440
|
+
## Tài Liệu Liên Quan
|
|
290
441
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
3. Selector suffix: `cRed@:hover` (NOT `hover:cRed`).
|
|
296
|
-
4. Value capitalization: `bdN`, `dF`, `posA` (NOT `bdn`, `df`, `posa`).
|
|
297
|
-
5. Use aliases from DICTIONARY.md (e.g., `m` for margin, `d` for display).
|
|
298
|
-
```
|
|
442
|
+
1. Dictionary đầy đủ: [DICTIONARY.md](./DICTIONARY.md)
|
|
443
|
+
2. Source code: [https://github.com/dwork-dev/fwkui](https://github.com/dwork-dev/fwkui)
|
|
444
|
+
|
|
445
|
+
## License
|
|
299
446
|
|
|
300
|
-
|
|
447
|
+
Licensed under MIT. See [LICENSE](./LICENSE).
|
|
301
448
|
|
|
302
|
-
|
|
449
|
+
Updated: 2026-03-02
|