@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/dist/react.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const f=require("react/jsx-runtime");function t(e,a){if(a===void 0)return{};const s={};if(typeof a=="object"&&a!==null&&"base"in a){s[`data-${e}`]=String(a.base);const n=["xs","sm","md","lg","xl"];for(const i of n)a[i]!==void 0&&(s[`data-${e}-${i}`]=String(a[i]))}else s[`data-${e}`]=String(a);return s}function x({as:e="div",axis:a,gap:s,rowGap:n,columnGap:i,justify:r,align:o,alignContent:d,wrap:l,reverse:c,inline:m,className:p="",children:b,onClick:u}){const j={...t("axis",a),...t("gap",s),...t("row-gap",n),...t("column-gap",i),...t("justify",r),...t("align",o),...t("align-content",d),...t("wrap",l),...c?{"data-reverse":""}:{},...m?{"data-inline":""}:{}};return f.jsx(e,{"data-flex":"",className:p||void 0,...j,onClick:u,children:b})}x.displayName="Flex";function g({as:e="div",grow:a,shrink:s,basis:n,alignSelf:i,order:r,className:o="",children:d,onClick:l}){const c={...t("grow",a),...t("shrink",s),...t("basis",n),...t("align-self",i),...t("order",r)};return f.jsx(e,{"data-flex-item":"",className:o||void 0,...c,onClick:l,children:d})}g.displayName="FlexItem";exports.Flex=x;exports.FlexItem=g;
@@ -0,0 +1,95 @@
1
+ import { JSX } from 'react/jsx-runtime';
2
+
3
+ declare type AlignContentValue = "start" | "end" | "center" | "stretch" | "between" | "around" | "evenly";
4
+
5
+ declare type AlignSelfValue = "auto" | "start" | "end" | "center" | "stretch" | "baseline";
6
+
7
+ declare type AlignValue = "start" | "end" | "center" | "stretch" | "baseline";
8
+
9
+ declare type AxisValue = "horizontal" | "vertical";
10
+
11
+ declare type Breakpoint = keyof typeof breakpoints;
12
+
13
+ declare const breakpoints: {
14
+ readonly xs: 480;
15
+ readonly sm: 600;
16
+ readonly md: 720;
17
+ readonly lg: 960;
18
+ readonly xl: 1200;
19
+ };
20
+
21
+ export declare function Flex({ as: Component, axis, gap, rowGap, columnGap, justify, align, alignContent, wrap, reverse, inline, className, children, onClick, }: FlexProps): JSX.Element;
22
+
23
+ export declare namespace Flex {
24
+ var displayName: string;
25
+ }
26
+
27
+ export declare function FlexItem({ as: Component, grow, shrink, basis, alignSelf, order, className, children, onClick, }: FlexItemProps): JSX.Element;
28
+
29
+ export declare namespace FlexItem {
30
+ var displayName: string;
31
+ }
32
+
33
+ export declare interface FlexItemProps {
34
+ /** HTML element to render as */
35
+ as?: keyof React.JSX.IntrinsicElements;
36
+ /** Flex grow factor */
37
+ grow?: ResponsiveValue<number>;
38
+ /** Flex shrink factor */
39
+ shrink?: ResponsiveValue<number>;
40
+ /** Flex basis */
41
+ basis?: ResponsiveValue<string>;
42
+ /** Align self */
43
+ alignSelf?: ResponsiveValue<AlignSelfValue>;
44
+ /** Order */
45
+ order?: ResponsiveValue<number | "first" | "last" | "none">;
46
+ /** Additional CSS classes */
47
+ className?: string;
48
+ /** Child elements */
49
+ children: React.ReactNode;
50
+ /** Click handler */
51
+ onClick?: () => void;
52
+ }
53
+
54
+ export declare interface FlexProps {
55
+ /** HTML element to render as */
56
+ as?: keyof React.JSX.IntrinsicElements;
57
+ /** Flex axis direction */
58
+ axis?: ResponsiveValue<AxisValue>;
59
+ /** Gap between items */
60
+ gap?: ResponsiveValue<SpacingValue>;
61
+ /** Row gap (overrides gap for rows) */
62
+ rowGap?: ResponsiveValue<SpacingValue>;
63
+ /** Column gap (overrides gap for columns) */
64
+ columnGap?: ResponsiveValue<SpacingValue>;
65
+ /** Main axis alignment */
66
+ justify?: ResponsiveValue<JustifyValue>;
67
+ /** Cross axis alignment */
68
+ align?: ResponsiveValue<AlignValue>;
69
+ /** Multi-line alignment */
70
+ alignContent?: ResponsiveValue<AlignContentValue>;
71
+ /** Wrap behavior */
72
+ wrap?: ResponsiveValue<WrapValue>;
73
+ /** Reverse direction */
74
+ reverse?: boolean;
75
+ /** Use inline-flex */
76
+ inline?: boolean;
77
+ /** Additional CSS classes */
78
+ className?: string;
79
+ /** Child elements */
80
+ children: React.ReactNode;
81
+ /** Click handler */
82
+ onClick?: () => void;
83
+ }
84
+
85
+ declare type JustifyValue = "start" | "end" | "center" | "between" | "around" | "evenly";
86
+
87
+ declare type ResponsiveValue<T> = T | ({
88
+ base: T;
89
+ } & Partial<Record<Breakpoint, T>>);
90
+
91
+ declare type SpacingValue = number | `${number}` | "half" | "fourth";
92
+
93
+ declare type WrapValue = "nowrap" | "wrap" | "wrap-reverse";
94
+
95
+ export { }
package/dist/react.js ADDED
@@ -0,0 +1,87 @@
1
+ import { jsx as c } from "react/jsx-runtime";
2
+ function t(i, a) {
3
+ if (a === void 0) return {};
4
+ const s = {};
5
+ if (typeof a == "object" && a !== null && "base" in a) {
6
+ s[`data-${i}`] = String(a.base);
7
+ const r = ["xs", "sm", "md", "lg", "xl"];
8
+ for (const n of r)
9
+ a[n] !== void 0 && (s[`data-${i}-${n}`] = String(a[n]));
10
+ } else
11
+ s[`data-${i}`] = String(a);
12
+ return s;
13
+ }
14
+ function y({
15
+ as: i = "div",
16
+ axis: a,
17
+ gap: s,
18
+ rowGap: r,
19
+ columnGap: n,
20
+ justify: o,
21
+ align: e,
22
+ alignContent: d,
23
+ wrap: f,
24
+ reverse: l,
25
+ inline: g,
26
+ className: p = "",
27
+ children: x,
28
+ onClick: m
29
+ }) {
30
+ const b = {
31
+ ...t("axis", a),
32
+ ...t("gap", s),
33
+ ...t("row-gap", r),
34
+ ...t("column-gap", n),
35
+ ...t("justify", o),
36
+ ...t("align", e),
37
+ ...t("align-content", d),
38
+ ...t("wrap", f),
39
+ ...l ? { "data-reverse": "" } : {},
40
+ ...g ? { "data-inline": "" } : {}
41
+ };
42
+ return /* @__PURE__ */ c(
43
+ i,
44
+ {
45
+ "data-flex": "",
46
+ className: p || void 0,
47
+ ...b,
48
+ onClick: m,
49
+ children: x
50
+ }
51
+ );
52
+ }
53
+ y.displayName = "Flex";
54
+ function F({
55
+ as: i = "div",
56
+ grow: a,
57
+ shrink: s,
58
+ basis: r,
59
+ alignSelf: n,
60
+ order: o,
61
+ className: e = "",
62
+ children: d,
63
+ onClick: f
64
+ }) {
65
+ const l = {
66
+ ...t("grow", a),
67
+ ...t("shrink", s),
68
+ ...t("basis", r),
69
+ ...t("align-self", n),
70
+ ...t("order", o)
71
+ };
72
+ return /* @__PURE__ */ c(
73
+ i,
74
+ {
75
+ "data-flex-item": "",
76
+ className: e || void 0,
77
+ ...l,
78
+ onClick: f,
79
+ children: d
80
+ }
81
+ );
82
+ }
83
+ F.displayName = "FlexItem";
84
+ export {
85
+ y as Flex,
86
+ F as FlexItem
87
+ };
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@joshwilkerson/flex-ui",
3
+ "version": "1.0.0-alpha.0",
4
+ "type": "module",
5
+ "description": "CSS-first responsive flexbox layouts with data attributes and optional React components",
6
+ "bin": {
7
+ "flex-ui": "./bin/flex-ui.ts"
8
+ },
9
+ "main": "./dist/flex-ui.css",
10
+ "exports": {
11
+ ".": "./dist/flex-ui.css",
12
+ "./react": {
13
+ "types": "./dist/react.d.ts",
14
+ "import": "./dist/react.js",
15
+ "require": "./dist/react.cjs"
16
+ },
17
+ "./config": {
18
+ "types": "./dist/config.d.ts",
19
+ "import": "./dist/config.js",
20
+ "require": "./dist/config.cjs"
21
+ }
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "bin",
26
+ "scripts",
27
+ "src/tokens",
28
+ "src/config.ts"
29
+ ],
30
+ "scripts": {
31
+ "build": "vite build",
32
+ "test:unit": "jest",
33
+ "test:visual": "playwright test",
34
+ "test": "npm run test:unit && npm run test:visual",
35
+ "lint": "eslint ."
36
+ },
37
+ "dependencies": {
38
+ "figlet": "^1.11.0",
39
+ "inquirer": "^12.11.1",
40
+ "tsx": "^4.21.0"
41
+ },
42
+ "devDependencies": {
43
+ "@eslint/js": "^9.39.1",
44
+ "@playwright/test": "^1.59.1",
45
+ "@testing-library/jest-dom": "^6.9.1",
46
+ "@testing-library/react": "^16.3.2",
47
+ "@types/figlet": "^1.7.0",
48
+ "@types/inquirer": "^9.0.9",
49
+ "@types/jest": "^30.0.0",
50
+ "@types/node": "^24.10.1",
51
+ "@types/react": "^19.2.5",
52
+ "@types/react-dom": "^19.2.3",
53
+ "@vitejs/plugin-react": "^5.1.1",
54
+ "eslint": "^9.39.1",
55
+ "eslint-plugin-react-hooks": "^7.0.1",
56
+ "eslint-plugin-react-refresh": "^0.4.24",
57
+ "globals": "^16.5.0",
58
+ "jest": "^30.3.0",
59
+ "jest-environment-jsdom": "^30.3.0",
60
+ "react": "^19.2.4",
61
+ "react-dom": "^19.2.4",
62
+ "ts-jest": "^29.4.9",
63
+ "typescript": "~5.9.3",
64
+ "typescript-eslint": "^8.46.4",
65
+ "vite": "^7.2.4",
66
+ "vite-plugin-dts": "^4.5.4"
67
+ },
68
+ "peerDependencies": {
69
+ "react": "^18.0.0 || ^19.0.0"
70
+ },
71
+ "peerDependenciesMeta": {
72
+ "react": {
73
+ "optional": true
74
+ }
75
+ },
76
+ "keywords": [
77
+ "css",
78
+ "flexbox",
79
+ "layout",
80
+ "responsive",
81
+ "data-attributes",
82
+ "react",
83
+ "flex"
84
+ ],
85
+ "license": "MIT"
86
+ }
package/scripts/cli.ts ADDED
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Flex UI CLI
4
+ *
5
+ * Usage:
6
+ * npx flex-ui Interactive customization + CSS generation
7
+ * npx flex-ui --help Show help
8
+ */
9
+
10
+ import inquirer from "inquirer"
11
+ import figlet from "figlet"
12
+
13
+ const DEFAULTS = {
14
+ breakpoints: { xs: 480, sm: 600, md: 720, lg: 960, xl: 1200 },
15
+ unit: "8px",
16
+ scale: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
17
+ named: { fourth: 0.25, half: 0.5 },
18
+ output: "./flex-ui.css",
19
+ }
20
+
21
+ function parseUnit(unit: string): { value: number; suffix: string } | null {
22
+ const match = unit.match(/^([\d.]+)(.+)$/)
23
+ if (!match) return null
24
+ return { value: parseFloat(match[1]), suffix: match[2] }
25
+ }
26
+
27
+ function deriveSpacing(unit: string): Record<string | number, string> {
28
+ const parsed = parseUnit(unit)
29
+ if (!parsed) throw new Error(`Invalid unit: ${unit}`)
30
+
31
+ const spacing: Record<string | number, string> = {}
32
+
33
+ for (const multiplier of DEFAULTS.scale) {
34
+ const val = parsed.value * multiplier
35
+ spacing[multiplier] = val === 0 ? "0" : `${val}${parsed.suffix}`
36
+ }
37
+
38
+ for (const [name, multiplier] of Object.entries(DEFAULTS.named)) {
39
+ const val = parsed.value * multiplier
40
+ spacing[name] = val === 0 ? "0" : `${val}${parsed.suffix}`
41
+ }
42
+
43
+ return spacing
44
+ }
45
+
46
+ function printBanner(): void {
47
+ const banner = figlet.textSync("FLEX-UI", {
48
+ font: "ANSI Shadow",
49
+ })
50
+ console.log()
51
+ console.log(banner)
52
+ console.log()
53
+ }
54
+
55
+ async function run(): Promise<void> {
56
+ printBanner()
57
+
58
+ // Breakpoints
59
+ console.log("šŸ“± Breakpoints\n")
60
+ const bpAnswers = await inquirer.prompt(
61
+ Object.entries(DEFAULTS.breakpoints).map(([name, defaultVal]) => ({
62
+ type: "input" as const,
63
+ name,
64
+ message: `${name} (px)`,
65
+ default: String(defaultVal),
66
+ validate: (val: string) => {
67
+ const n = parseInt(val, 10)
68
+ return isNaN(n) || n < 0 ? "Enter a valid positive number" : true
69
+ },
70
+ })),
71
+ )
72
+
73
+ const breakpoints: Record<string, number> = {}
74
+ for (const [name, val] of Object.entries(bpAnswers)) {
75
+ breakpoints[name] = parseInt(val as string, 10)
76
+ }
77
+
78
+ // Breakpoints summary table
79
+ console.log()
80
+ const bpTable: Record<string, string> = {}
81
+ for (const [name, val] of Object.entries(breakpoints)) {
82
+ bpTable[name] = `${val}px`
83
+ }
84
+ console.table(bpTable)
85
+
86
+ // Spacing
87
+ console.log("\nšŸ“ Spacing\n")
88
+ const { unit } = await inquirer.prompt([
89
+ {
90
+ type: "input",
91
+ name: "unit",
92
+ message: "Base unit",
93
+ default: DEFAULTS.unit,
94
+ validate: (val: string) => {
95
+ return parseUnit(val) ? true : 'Use a format like "8px" or "0.5rem"'
96
+ },
97
+ },
98
+ ])
99
+
100
+ // Preview derived scale
101
+ const spacing = deriveSpacing(unit)
102
+ console.log()
103
+ const scaleTable: Record<string, string> = {}
104
+ for (const [key, val] of Object.entries(spacing)) {
105
+ scaleTable[key] = val
106
+ }
107
+ console.table(scaleTable)
108
+
109
+ // Output
110
+ console.log()
111
+ const { output } = await inquirer.prompt([
112
+ {
113
+ type: "input",
114
+ name: "output",
115
+ message: "Output path",
116
+ default: DEFAULTS.output,
117
+ },
118
+ ])
119
+
120
+ // Generate
121
+ console.log()
122
+ const { generate } = await import("./generate-flex-css.js")
123
+ await generate(process.cwd(), {
124
+ breakpoints,
125
+ spacing,
126
+ output,
127
+ })
128
+ }
129
+
130
+ function printHelp(): void {
131
+ printBanner()
132
+ console.log(` Usage:`)
133
+ console.log(
134
+ ` npx flex-ui Interactive customization + CSS generation`,
135
+ )
136
+ console.log(` npx flex-ui --help Show this help`)
137
+ console.log()
138
+ console.log(` Walks you through customizing breakpoints and spacing,`)
139
+ console.log(` then generates a CSS file for both React and CSS-only usage.`)
140
+ console.log()
141
+ }
142
+
143
+ async function main(): Promise<void> {
144
+ const args = process.argv.slice(2)
145
+ const command = args[0]
146
+
147
+ if (command === "help" || command === "--help" || command === "-h") {
148
+ printHelp()
149
+ return
150
+ }
151
+
152
+ if (command) {
153
+ console.error(`Unknown command: ${command}`)
154
+ printHelp()
155
+ process.exit(1)
156
+ }
157
+
158
+ await run()
159
+ }
160
+
161
+ main().catch((err) => {
162
+ console.error("Error:", err.message)
163
+ process.exit(1)
164
+ })