@joshwilkerson/flex-ui 1.0.0-alpha.0
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 +141 -0
- package/bin/flex-ui.ts +10 -0
- package/dist/config.cjs +1 -0
- package/dist/config.d.ts +78 -0
- package/dist/config.js +42 -0
- package/dist/flex-ui.css +705 -0
- package/dist/react.cjs +1 -0
- package/dist/react.d.ts +95 -0
- package/dist/react.js +87 -0
- package/package.json +86 -0
- package/scripts/cli.ts +164 -0
- package/scripts/generate-flex-css.ts +316 -0
- package/src/config.ts +115 -0
- package/src/tokens/index.ts +123 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI wrapper for CSS generation
|
|
3
|
+
*
|
|
4
|
+
* Used by: npx flex-ui generate
|
|
5
|
+
* For consumers who want to generate CSS with custom config.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from "fs"
|
|
9
|
+
import * as path from "path"
|
|
10
|
+
import { fileURLToPath } from "url"
|
|
11
|
+
import { pathToFileURL } from "url"
|
|
12
|
+
import { type FlexConfig, defaultConfig, resolveSpacing } from "../src/config"
|
|
13
|
+
import {
|
|
14
|
+
axis,
|
|
15
|
+
axisToDirection,
|
|
16
|
+
justify,
|
|
17
|
+
justifyToCSS,
|
|
18
|
+
align,
|
|
19
|
+
alignToCSS,
|
|
20
|
+
wrap,
|
|
21
|
+
alignContent,
|
|
22
|
+
alignContentToCSS,
|
|
23
|
+
alignSelf,
|
|
24
|
+
alignSelfToCSS,
|
|
25
|
+
} from "../src/tokens"
|
|
26
|
+
|
|
27
|
+
const GENERATED_HEADER = `/**
|
|
28
|
+
* Flex UI - CSS-only flexbox layouts
|
|
29
|
+
*
|
|
30
|
+
* Usage:
|
|
31
|
+
* <div data-flex data-axis="horizontal" data-gap="2" data-align="center">
|
|
32
|
+
* <div>Item 1</div>
|
|
33
|
+
* <div>Item 2</div>
|
|
34
|
+
* </div>
|
|
35
|
+
*
|
|
36
|
+
* Customize spacing by overriding CSS custom properties:
|
|
37
|
+
* :root {
|
|
38
|
+
* --flex-spacing-1: 0.5rem;
|
|
39
|
+
* --flex-spacing-2: 1rem;
|
|
40
|
+
* }
|
|
41
|
+
*
|
|
42
|
+
* Generated by: npx flex-ui generate
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
`
|
|
46
|
+
|
|
47
|
+
const CONFIG_FILES = [
|
|
48
|
+
"flex.config.ts",
|
|
49
|
+
"flex.config.js",
|
|
50
|
+
"flex.config.mjs",
|
|
51
|
+
"flex.config.json",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
interface ResolvedConfig {
|
|
55
|
+
breakpoints: Record<string, number>
|
|
56
|
+
breakpointKeys: string[]
|
|
57
|
+
spacing: Record<string | number, string>
|
|
58
|
+
spacingKeys: (string | number)[]
|
|
59
|
+
output: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function loadConfig(cwd: string): Promise<FlexConfig> {
|
|
63
|
+
for (const filename of CONFIG_FILES) {
|
|
64
|
+
const configPath = path.join(cwd, filename)
|
|
65
|
+
|
|
66
|
+
if (!fs.existsSync(configPath)) {
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log(`Found config: ${filename}`)
|
|
71
|
+
|
|
72
|
+
if (filename.endsWith(".json")) {
|
|
73
|
+
const content = fs.readFileSync(configPath, "utf-8")
|
|
74
|
+
return JSON.parse(content) as FlexConfig
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const fileUrl = pathToFileURL(configPath).href
|
|
78
|
+
const module = await import(fileUrl)
|
|
79
|
+
return (module.default || module) as FlexConfig
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function mergeConfig(userConfig: FlexConfig): ResolvedConfig {
|
|
86
|
+
const breakpoints: Record<string, number> = userConfig.breakpoints ?? {
|
|
87
|
+
...defaultConfig.breakpoints,
|
|
88
|
+
}
|
|
89
|
+
const spacingConfig = userConfig.spacing ?? defaultConfig.spacing
|
|
90
|
+
const spacing = resolveSpacing(spacingConfig)
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
breakpoints,
|
|
94
|
+
breakpointKeys: Object.keys(breakpoints).sort(
|
|
95
|
+
(a, b) => breakpoints[a] - breakpoints[b],
|
|
96
|
+
),
|
|
97
|
+
spacing,
|
|
98
|
+
spacingKeys: Object.keys(spacing).map((k) =>
|
|
99
|
+
isNaN(Number(k)) ? k : Number(k),
|
|
100
|
+
),
|
|
101
|
+
output: userConfig.output ?? defaultConfig.output,
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function generateCSS(config: ResolvedConfig): string {
|
|
106
|
+
const { breakpoints, breakpointKeys, spacingKeys, spacing } = config
|
|
107
|
+
|
|
108
|
+
let css = GENERATED_HEADER
|
|
109
|
+
|
|
110
|
+
css += `/* ----- Spacing Tokens ----- */\n`
|
|
111
|
+
css += `:root {\n`
|
|
112
|
+
for (const key of spacingKeys) {
|
|
113
|
+
css += ` --flex-spacing-${key}: ${spacing[key]};\n`
|
|
114
|
+
}
|
|
115
|
+
css += `}\n\n`
|
|
116
|
+
|
|
117
|
+
css += `[data-flex] {\n display: flex;\n}\n\n`
|
|
118
|
+
css += `[data-flex][data-inline] {\n display: inline-flex;\n}\n\n`
|
|
119
|
+
|
|
120
|
+
css += `/* ----- Axis ----- */\n`
|
|
121
|
+
for (const value of axis) {
|
|
122
|
+
css += `[data-flex][data-axis="${value}"] { flex-direction: ${axisToDirection[value]}; }\n`
|
|
123
|
+
css += `[data-flex][data-axis="${value}"][data-reverse] { flex-direction: ${axisToDirection[value]}-reverse; }\n`
|
|
124
|
+
}
|
|
125
|
+
css += `\n`
|
|
126
|
+
|
|
127
|
+
css += `/* ----- Gap ----- */\n`
|
|
128
|
+
for (const key of spacingKeys) {
|
|
129
|
+
css += `[data-flex][data-gap="${key}"] { gap: var(--flex-spacing-${key}); }\n`
|
|
130
|
+
}
|
|
131
|
+
css += `\n`
|
|
132
|
+
|
|
133
|
+
css += `/* ----- Row Gap ----- */\n`
|
|
134
|
+
for (const key of spacingKeys) {
|
|
135
|
+
css += `[data-flex][data-row-gap="${key}"] { row-gap: var(--flex-spacing-${key}); }\n`
|
|
136
|
+
}
|
|
137
|
+
css += `\n`
|
|
138
|
+
|
|
139
|
+
css += `/* ----- Column Gap ----- */\n`
|
|
140
|
+
for (const key of spacingKeys) {
|
|
141
|
+
css += `[data-flex][data-column-gap="${key}"] { column-gap: var(--flex-spacing-${key}); }\n`
|
|
142
|
+
}
|
|
143
|
+
css += `\n`
|
|
144
|
+
|
|
145
|
+
css += `/* ----- Justify ----- */\n`
|
|
146
|
+
for (const value of justify) {
|
|
147
|
+
css += `[data-flex][data-justify="${value}"] { justify-content: ${justifyToCSS[value]}; }\n`
|
|
148
|
+
}
|
|
149
|
+
css += `\n`
|
|
150
|
+
|
|
151
|
+
css += `/* ----- Align ----- */\n`
|
|
152
|
+
for (const value of align) {
|
|
153
|
+
css += `[data-flex][data-align="${value}"] { align-items: ${alignToCSS[value]}; }\n`
|
|
154
|
+
}
|
|
155
|
+
css += `\n`
|
|
156
|
+
|
|
157
|
+
css += `/* ----- Align Content ----- */\n`
|
|
158
|
+
for (const value of alignContent) {
|
|
159
|
+
css += `[data-flex][data-align-content="${value}"] { align-content: ${alignContentToCSS[value]}; }\n`
|
|
160
|
+
}
|
|
161
|
+
css += `\n`
|
|
162
|
+
|
|
163
|
+
css += `/* ----- Wrap ----- */\n`
|
|
164
|
+
for (const value of wrap) {
|
|
165
|
+
css += `[data-flex][data-wrap="${value}"] { flex-wrap: ${value}; }\n`
|
|
166
|
+
}
|
|
167
|
+
css += `\n`
|
|
168
|
+
|
|
169
|
+
css += `/* ----- Flex Item: Grow ----- */\n`
|
|
170
|
+
for (let i = 0; i <= 5; i++) {
|
|
171
|
+
css += `[data-flex-item][data-grow="${i}"] { flex-grow: ${i}; }\n`
|
|
172
|
+
}
|
|
173
|
+
css += `\n`
|
|
174
|
+
|
|
175
|
+
css += `/* ----- Flex Item: Shrink ----- */\n`
|
|
176
|
+
for (let i = 0; i <= 3; i++) {
|
|
177
|
+
css += `[data-flex-item][data-shrink="${i}"] { flex-shrink: ${i}; }\n`
|
|
178
|
+
}
|
|
179
|
+
css += `\n`
|
|
180
|
+
|
|
181
|
+
css += `/* ----- Flex Item: Basis ----- */\n`
|
|
182
|
+
css += `[data-flex-item][data-basis="auto"] { flex-basis: auto; }\n`
|
|
183
|
+
css += `[data-flex-item][data-basis="0"] { flex-basis: 0; }\n`
|
|
184
|
+
css += `[data-flex-item][data-basis="full"] { flex-basis: 100%; }\n`
|
|
185
|
+
css += `[data-flex-item][data-basis="1/2"] { flex-basis: 50%; }\n`
|
|
186
|
+
css += `[data-flex-item][data-basis="1/3"] { flex-basis: 33.333333%; }\n`
|
|
187
|
+
css += `[data-flex-item][data-basis="2/3"] { flex-basis: 66.666667%; }\n`
|
|
188
|
+
css += `[data-flex-item][data-basis="1/4"] { flex-basis: 25%; }\n`
|
|
189
|
+
css += `[data-flex-item][data-basis="3/4"] { flex-basis: 75%; }\n`
|
|
190
|
+
css += `\n`
|
|
191
|
+
|
|
192
|
+
css += `/* ----- Flex Item: Align Self ----- */\n`
|
|
193
|
+
for (const value of alignSelf) {
|
|
194
|
+
css += `[data-flex-item][data-align-self="${value}"] { align-self: ${alignSelfToCSS[value]}; }\n`
|
|
195
|
+
}
|
|
196
|
+
css += `\n`
|
|
197
|
+
|
|
198
|
+
css += `/* ----- Flex Item: Order ----- */\n`
|
|
199
|
+
css += `[data-flex-item][data-order="first"] { order: -9999; }\n`
|
|
200
|
+
css += `[data-flex-item][data-order="last"] { order: 9999; }\n`
|
|
201
|
+
css += `[data-flex-item][data-order="none"] { order: 0; }\n`
|
|
202
|
+
for (let i = 1; i <= 12; i++) {
|
|
203
|
+
css += `[data-flex-item][data-order="${i}"] { order: ${i}; }\n`
|
|
204
|
+
}
|
|
205
|
+
css += `\n`
|
|
206
|
+
|
|
207
|
+
for (const bp of breakpointKeys) {
|
|
208
|
+
const minWidth = breakpoints[bp]
|
|
209
|
+
css += `/* ===== ${bp.toUpperCase()} (${minWidth}px) ===== */\n`
|
|
210
|
+
css += `@media screen and (min-width: ${minWidth}px) {\n`
|
|
211
|
+
|
|
212
|
+
for (const value of axis) {
|
|
213
|
+
css += ` [data-flex][data-axis-${bp}="${value}"] { flex-direction: ${axisToDirection[value]}; }\n`
|
|
214
|
+
css += ` [data-flex][data-axis-${bp}="${value}"][data-reverse] { flex-direction: ${axisToDirection[value]}-reverse; }\n`
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
for (const key of spacingKeys) {
|
|
218
|
+
css += ` [data-flex][data-gap-${bp}="${key}"] { gap: var(--flex-spacing-${key}); }\n`
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
for (const key of spacingKeys) {
|
|
222
|
+
css += ` [data-flex][data-row-gap-${bp}="${key}"] { row-gap: var(--flex-spacing-${key}); }\n`
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
for (const key of spacingKeys) {
|
|
226
|
+
css += ` [data-flex][data-column-gap-${bp}="${key}"] { column-gap: var(--flex-spacing-${key}); }\n`
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
for (const value of justify) {
|
|
230
|
+
css += ` [data-flex][data-justify-${bp}="${value}"] { justify-content: ${justifyToCSS[value]}; }\n`
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (const value of align) {
|
|
234
|
+
css += ` [data-flex][data-align-${bp}="${value}"] { align-items: ${alignToCSS[value]}; }\n`
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
for (const value of alignContent) {
|
|
238
|
+
css += ` [data-flex][data-align-content-${bp}="${value}"] { align-content: ${alignContentToCSS[value]}; }\n`
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
for (const value of wrap) {
|
|
242
|
+
css += ` [data-flex][data-wrap-${bp}="${value}"] { flex-wrap: ${value}; }\n`
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
for (let i = 0; i <= 5; i++) {
|
|
246
|
+
css += ` [data-flex-item][data-grow-${bp}="${i}"] { flex-grow: ${i}; }\n`
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
for (let i = 0; i <= 3; i++) {
|
|
250
|
+
css += ` [data-flex-item][data-shrink-${bp}="${i}"] { flex-shrink: ${i}; }\n`
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
css += ` [data-flex-item][data-basis-${bp}="auto"] { flex-basis: auto; }\n`
|
|
254
|
+
css += ` [data-flex-item][data-basis-${bp}="0"] { flex-basis: 0; }\n`
|
|
255
|
+
css += ` [data-flex-item][data-basis-${bp}="full"] { flex-basis: 100%; }\n`
|
|
256
|
+
css += ` [data-flex-item][data-basis-${bp}="1/2"] { flex-basis: 50%; }\n`
|
|
257
|
+
css += ` [data-flex-item][data-basis-${bp}="1/3"] { flex-basis: 33.333333%; }\n`
|
|
258
|
+
css += ` [data-flex-item][data-basis-${bp}="2/3"] { flex-basis: 66.666667%; }\n`
|
|
259
|
+
css += ` [data-flex-item][data-basis-${bp}="1/4"] { flex-basis: 25%; }\n`
|
|
260
|
+
css += ` [data-flex-item][data-basis-${bp}="3/4"] { flex-basis: 75%; }\n`
|
|
261
|
+
|
|
262
|
+
for (const value of alignSelf) {
|
|
263
|
+
css += ` [data-flex-item][data-align-self-${bp}="${value}"] { align-self: ${alignSelfToCSS[value]}; }\n`
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
css += ` [data-flex-item][data-order-${bp}="first"] { order: -9999; }\n`
|
|
267
|
+
css += ` [data-flex-item][data-order-${bp}="last"] { order: 9999; }\n`
|
|
268
|
+
css += ` [data-flex-item][data-order-${bp}="none"] { order: 0; }\n`
|
|
269
|
+
for (let i = 1; i <= 12; i++) {
|
|
270
|
+
css += ` [data-flex-item][data-order-${bp}="${i}"] { order: ${i}; }\n`
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
css += `}\n\n`
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return css
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function ensureDir(filePath: string): void {
|
|
280
|
+
const dir = path.dirname(filePath)
|
|
281
|
+
if (!fs.existsSync(dir)) {
|
|
282
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Main generation function
|
|
288
|
+
*
|
|
289
|
+
* @param cwd - Working directory for config file lookup and output
|
|
290
|
+
* @param inlineConfig - Optional config passed directly (skips file lookup)
|
|
291
|
+
*/
|
|
292
|
+
export async function generate(
|
|
293
|
+
cwd: string = process.cwd(),
|
|
294
|
+
inlineConfig?: FlexConfig,
|
|
295
|
+
): Promise<void> {
|
|
296
|
+
console.log("🛠️ Generating CSS...\n")
|
|
297
|
+
|
|
298
|
+
const userConfig = inlineConfig ?? (await loadConfig(cwd))
|
|
299
|
+
const config = mergeConfig(userConfig)
|
|
300
|
+
|
|
301
|
+
const cssContent = generateCSS(config)
|
|
302
|
+
const cssPath = path.resolve(cwd, config.output)
|
|
303
|
+
ensureDir(cssPath)
|
|
304
|
+
fs.writeFileSync(cssPath, cssContent)
|
|
305
|
+
console.log(`✅ Saved to ${config.output}\n`)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
309
|
+
const isMain = process.argv[1] === __filename
|
|
310
|
+
|
|
311
|
+
if (isMain) {
|
|
312
|
+
generate().catch((err) => {
|
|
313
|
+
console.error("Error:", err.message)
|
|
314
|
+
process.exit(1)
|
|
315
|
+
})
|
|
316
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flex UI Configuration
|
|
3
|
+
*
|
|
4
|
+
* Consumers can create a flex.config.ts file to customize
|
|
5
|
+
* breakpoints, spacing, and output path for CSS generation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface SpacingConfig {
|
|
9
|
+
/**
|
|
10
|
+
* Base spacing unit (e.g., "4px", "0.25rem", "1rem")
|
|
11
|
+
* Each spacing step will be a multiple of this value
|
|
12
|
+
*/
|
|
13
|
+
unit: string
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Multipliers for each spacing step
|
|
17
|
+
* @example [0, 1, 2, 3, 4, 6, 8, 12, 16] generates 0, 4px, 8px, 12px, ...
|
|
18
|
+
*/
|
|
19
|
+
scale: number[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface FlexConfig {
|
|
23
|
+
/**
|
|
24
|
+
* Custom breakpoints (min-width in pixels)
|
|
25
|
+
* @example { sm: 640, md: 768, lg: 1024, xl: 1280 }
|
|
26
|
+
*/
|
|
27
|
+
breakpoints?: Record<string, number>
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Custom spacing - either explicit values or unit + multipliers
|
|
31
|
+
* @example { unit: '4px', scale: [0, 1, 2, 3, 4, 6, 8] }
|
|
32
|
+
* @example { 0: '0', 1: '0.25rem', 2: '0.5rem', 4: '1rem' }
|
|
33
|
+
*/
|
|
34
|
+
spacing?: SpacingConfig | Record<string | number, string>
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Output file path for generated CSS (relative to cwd)
|
|
38
|
+
* @default "./flex-ui.css"
|
|
39
|
+
*/
|
|
40
|
+
output?: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if spacing config uses unit + scale format
|
|
45
|
+
*/
|
|
46
|
+
export function isSpacingWithUnit(
|
|
47
|
+
spacing: SpacingConfig | Record<string | number, string>,
|
|
48
|
+
): spacing is SpacingConfig {
|
|
49
|
+
return "unit" in spacing && "scale" in spacing
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolve spacing config to final key-value pairs
|
|
54
|
+
*/
|
|
55
|
+
export function resolveSpacing(
|
|
56
|
+
spacing: SpacingConfig | Record<string | number, string>,
|
|
57
|
+
): Record<string | number, string> {
|
|
58
|
+
if (!isSpacingWithUnit(spacing)) {
|
|
59
|
+
return spacing
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { unit, scale } = spacing
|
|
63
|
+
const unitMatch = unit.match(/^([\d.]+)(.+)$/)
|
|
64
|
+
|
|
65
|
+
if (!unitMatch) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Invalid spacing unit: ${unit}. Expected format like "4px" or "0.25rem"`,
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const unitValue = parseFloat(unitMatch[1])
|
|
72
|
+
const unitSuffix = unitMatch[2]
|
|
73
|
+
|
|
74
|
+
const resolved: Record<number, string> = {}
|
|
75
|
+
for (const multiplier of scale) {
|
|
76
|
+
const value = unitValue * multiplier
|
|
77
|
+
resolved[multiplier] = value === 0 ? "0" : `${value}${unitSuffix}`
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return resolved
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Default spacing using unit + scale format
|
|
85
|
+
*/
|
|
86
|
+
export const defaultSpacingConfig: SpacingConfig = {
|
|
87
|
+
unit: "8px",
|
|
88
|
+
scale: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Additional named spacing values
|
|
93
|
+
*/
|
|
94
|
+
export const namedSpacing: Record<string, string> = {
|
|
95
|
+
fourth: "2px",
|
|
96
|
+
half: "4px",
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Default configuration values
|
|
101
|
+
*/
|
|
102
|
+
export const defaultConfig = {
|
|
103
|
+
breakpoints: {
|
|
104
|
+
xs: 480,
|
|
105
|
+
sm: 600,
|
|
106
|
+
md: 720,
|
|
107
|
+
lg: 960,
|
|
108
|
+
xl: 1200,
|
|
109
|
+
},
|
|
110
|
+
spacing: defaultSpacingConfig,
|
|
111
|
+
output: "./flex-ui.css",
|
|
112
|
+
} as const satisfies Required<FlexConfig>
|
|
113
|
+
|
|
114
|
+
export type DefaultBreakpoint = keyof typeof defaultConfig.breakpoints
|
|
115
|
+
export type DefaultSpacing = (typeof defaultSpacingConfig.scale)[number]
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design Tokens - Single Source of Truth
|
|
3
|
+
*
|
|
4
|
+
* All breakpoints, spacing values, and property options are defined here.
|
|
5
|
+
* Both the React component and CSS-only version are generated from these values.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
defaultConfig,
|
|
10
|
+
defaultSpacingConfig,
|
|
11
|
+
namedSpacing,
|
|
12
|
+
resolveSpacing,
|
|
13
|
+
} from "../config"
|
|
14
|
+
|
|
15
|
+
// Breakpoints (min-width values in pixels)
|
|
16
|
+
export const breakpoints = defaultConfig.breakpoints
|
|
17
|
+
|
|
18
|
+
export type Breakpoint = keyof typeof breakpoints
|
|
19
|
+
export const breakpointKeys = Object.keys(breakpoints) as Breakpoint[]
|
|
20
|
+
|
|
21
|
+
// Spacing scale configuration
|
|
22
|
+
export const spacingConfig = defaultSpacingConfig
|
|
23
|
+
|
|
24
|
+
// Resolved spacing values (generated from unit + scale + named)
|
|
25
|
+
export const spacing: Record<string | number, string> = {
|
|
26
|
+
...resolveSpacing(spacingConfig),
|
|
27
|
+
...namedSpacing,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type SpacingValue =
|
|
31
|
+
| (typeof spacingConfig.scale)[number]
|
|
32
|
+
| keyof typeof namedSpacing
|
|
33
|
+
export const spacingKeys: (string | number)[] = [
|
|
34
|
+
...spacingConfig.scale,
|
|
35
|
+
...Object.keys(namedSpacing),
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
// Axis values (maps to flex-direction)
|
|
39
|
+
export const axis = ["horizontal", "vertical"] as const
|
|
40
|
+
export type AxisValue = (typeof axis)[number]
|
|
41
|
+
|
|
42
|
+
export const axisToDirection: Record<AxisValue, string> = {
|
|
43
|
+
horizontal: "row",
|
|
44
|
+
vertical: "column",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Justify values (maps to justify-content)
|
|
48
|
+
export const justify = [
|
|
49
|
+
"start",
|
|
50
|
+
"end",
|
|
51
|
+
"center",
|
|
52
|
+
"between",
|
|
53
|
+
"around",
|
|
54
|
+
"evenly",
|
|
55
|
+
] as const
|
|
56
|
+
export type JustifyValue = (typeof justify)[number]
|
|
57
|
+
|
|
58
|
+
export const justifyToCSS: Record<JustifyValue, string> = {
|
|
59
|
+
start: "flex-start",
|
|
60
|
+
end: "flex-end",
|
|
61
|
+
center: "center",
|
|
62
|
+
between: "space-between",
|
|
63
|
+
around: "space-around",
|
|
64
|
+
evenly: "space-evenly",
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Align values (maps to align-items)
|
|
68
|
+
export const align = ["start", "end", "center", "stretch", "baseline"] as const
|
|
69
|
+
export type AlignValue = (typeof align)[number]
|
|
70
|
+
|
|
71
|
+
export const alignToCSS: Record<AlignValue, string> = {
|
|
72
|
+
start: "flex-start",
|
|
73
|
+
end: "flex-end",
|
|
74
|
+
center: "center",
|
|
75
|
+
stretch: "stretch",
|
|
76
|
+
baseline: "baseline",
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Wrap values (maps to flex-wrap)
|
|
80
|
+
export const wrap = ["nowrap", "wrap", "wrap-reverse"] as const
|
|
81
|
+
export type WrapValue = (typeof wrap)[number]
|
|
82
|
+
|
|
83
|
+
// Align Content values (for multi-line flex containers)
|
|
84
|
+
export const alignContent = [
|
|
85
|
+
"start",
|
|
86
|
+
"end",
|
|
87
|
+
"center",
|
|
88
|
+
"stretch",
|
|
89
|
+
"between",
|
|
90
|
+
"around",
|
|
91
|
+
"evenly",
|
|
92
|
+
] as const
|
|
93
|
+
export type AlignContentValue = (typeof alignContent)[number]
|
|
94
|
+
|
|
95
|
+
export const alignContentToCSS: Record<AlignContentValue, string> = {
|
|
96
|
+
start: "flex-start",
|
|
97
|
+
end: "flex-end",
|
|
98
|
+
center: "center",
|
|
99
|
+
stretch: "stretch",
|
|
100
|
+
between: "space-between",
|
|
101
|
+
around: "space-around",
|
|
102
|
+
evenly: "space-evenly",
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Flex item values for StackItem
|
|
106
|
+
export const alignSelf = [
|
|
107
|
+
"auto",
|
|
108
|
+
"start",
|
|
109
|
+
"end",
|
|
110
|
+
"center",
|
|
111
|
+
"stretch",
|
|
112
|
+
"baseline",
|
|
113
|
+
] as const
|
|
114
|
+
export type AlignSelfValue = (typeof alignSelf)[number]
|
|
115
|
+
|
|
116
|
+
export const alignSelfToCSS: Record<AlignSelfValue, string> = {
|
|
117
|
+
auto: "auto",
|
|
118
|
+
start: "flex-start",
|
|
119
|
+
end: "flex-end",
|
|
120
|
+
center: "center",
|
|
121
|
+
stretch: "stretch",
|
|
122
|
+
baseline: "baseline",
|
|
123
|
+
}
|