@brickflow/ui 0.0.23 → 0.0.25

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/module.d.mts CHANGED
@@ -2,6 +2,7 @@ import * as _nuxt_schema from '@nuxt/schema';
2
2
 
3
3
  interface ModuleOptions {
4
4
  componentPrefix?: string;
5
+ configPath?: string;
5
6
  target?: string;
6
7
  }
7
8
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  },
5
5
  "configKey": "brickflowUi",
6
6
  "name": "@brickflow/ui",
7
- "version": "0.0.23",
7
+ "version": "0.0.25",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,9 +1,164 @@
1
- import { defineNuxtModule, createResolver, addImportsDir, addComponentsDir } from '@nuxt/kit';
1
+ import { defineNuxtModule, createResolver, resolvePath, addTemplate, addImportsDir, addComponentsDir } from '@nuxt/kit';
2
2
  import tailwindcss from '@tailwindcss/vite';
3
+ import { createJiti } from 'jiti';
4
+ import { readFile, mkdir, writeFile, readdir } from 'node:fs/promises';
5
+ import { resolve, basename, extname, relative, join } from 'node:path';
6
+ import { defineBrickflowUiConfig } from '../dist/runtime/tailwind.js';
3
7
 
8
+ const UI_STYLE_PATTERN = /\bUI_STYLE((?:\.[A-Za-z_$][\w$]*)+)/g;
9
+ const SCRIPT_FILE_PATTERN = /\.(?:[cm]?[jt]sx?|vue)(?:\?.*)?$/;
10
+ const TYPE_INDENT = " ";
11
+ const refreshUiStyleModules = (server) => {
12
+ server.moduleGraph.invalidateAll();
13
+ server.ws.send({ type: "full-reload" });
14
+ };
15
+ const normalizePath = (path) => resolve(path).replaceAll("\\", "/");
16
+ const toCamelCase = (value) => {
17
+ const normalized = value.replace(/^[^a-z_$]+/iu, "").replace(/[^\w$]+([\w$])/gu, (_, letter) => letter.toUpperCase());
18
+ return normalized ? `${normalized[0]?.toLowerCase()}${normalized.slice(1)}` : "";
19
+ };
20
+ const toStringLiteral = (value) => `'${value.replaceAll("\\", "\\\\").replaceAll("'", "\\'").replaceAll("\r", "\\r").replaceAll("\n", "\\n").replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029")}'`;
21
+ const readPath = (source, path) => path.reduce((value, key) => {
22
+ if (!value || typeof value !== "object") {
23
+ return void 0;
24
+ }
25
+ return value[key];
26
+ }, source);
27
+ const resolveUiStyleConfigPath = (id, path) => {
28
+ const cleanId = id.split("?")[0] ?? id;
29
+ if (!cleanId.endsWith(".vue")) {
30
+ return path;
31
+ }
32
+ return [toCamelCase(basename(cleanId, extname(cleanId))), ...path];
33
+ };
34
+ const resolveStyleValue = (styles, id, path) => {
35
+ const configPathValue = readPath(styles, resolveUiStyleConfigPath(id, path));
36
+ if (typeof configPathValue === "string") {
37
+ return configPathValue;
38
+ }
39
+ const globalValue = readPath(styles, path);
40
+ if (typeof globalValue === "string") {
41
+ return globalValue;
42
+ }
43
+ return configPathValue ?? globalValue;
44
+ };
45
+ const createUiStylePathTree = () => ({
46
+ children: /* @__PURE__ */ new Map()
47
+ });
48
+ const addPathToTree = (tree, path) => {
49
+ let currentTree = tree;
50
+ for (const segment of path) {
51
+ const childTree = currentTree.children.get(segment) ?? createUiStylePathTree();
52
+ currentTree.children.set(segment, childTree);
53
+ currentTree = childTree;
54
+ }
55
+ };
56
+ const renderTypeTree = (tree, level, objectType) => {
57
+ const lines = [];
58
+ const indent = TYPE_INDENT.repeat(level);
59
+ for (const [key, childTree] of [...tree.children.entries()].sort(([a], [b]) => a.localeCompare(b))) {
60
+ if (childTree.children.size === 0) {
61
+ lines.push(`${indent}readonly ${key}: string`);
62
+ continue;
63
+ }
64
+ lines.push(objectType ? `${indent}readonly ${key}: ${objectType} & {` : `${indent}readonly ${key}: {`);
65
+ lines.push(...renderTypeTree(childTree, level + 1, objectType));
66
+ lines.push(`${indent}}`);
67
+ }
68
+ return lines;
69
+ };
70
+ const collectUiStylePathsFromCode = (code) => {
71
+ const paths = [];
72
+ for (const match of code.matchAll(UI_STYLE_PATTERN)) {
73
+ paths.push(match[1]?.slice(1).split(".") ?? []);
74
+ }
75
+ return paths.filter((path) => path.length > 0);
76
+ };
77
+ const collectUiStyleConfigPathsFromCode = (code, id) => collectUiStylePathsFromCode(code).map((path) => resolveUiStyleConfigPath(id, path));
78
+ const renderInterfaceDeclaration = (name, paths, objectType) => {
79
+ const tree = createUiStylePathTree();
80
+ for (const path of paths) {
81
+ addPathToTree(tree, path);
82
+ }
83
+ return [` interface ${name} {`, ...renderTypeTree(tree, 2, objectType), " }"];
84
+ };
85
+ const createUiStyleTypeDeclaration = (options) => {
86
+ return [
87
+ "declare global {",
88
+ ...renderInterfaceDeclaration("BrickflowUiConfigStylePaths", options.configPaths),
89
+ "",
90
+ ...renderInterfaceDeclaration("BrickflowUiStylePaths", options.paths, "BrickflowUiStyleValue"),
91
+ "}",
92
+ "",
93
+ "export {}",
94
+ ""
95
+ ].join("\n");
96
+ };
97
+ const brickflowUiStylePlugin = (options) => ({
98
+ buildStart: {
99
+ handler() {
100
+ this.addWatchFile?.(options.configPath);
101
+ }
102
+ },
103
+ configureServer(server) {
104
+ server.watcher.add(options.configPath);
105
+ server.httpServer?.once("listening", () => {
106
+ setTimeout(() => refreshUiStyleModules(server), 0);
107
+ });
108
+ },
109
+ enforce: "pre",
110
+ handleHotUpdate(context) {
111
+ if (normalizePath(context.file) !== normalizePath(options.configPath)) {
112
+ return;
113
+ }
114
+ refreshUiStyleModules(context.server);
115
+ return [];
116
+ },
117
+ name: "brickflow-ui-style",
118
+ async transform(code, id) {
119
+ if (!SCRIPT_FILE_PATTERN.test(id) || !code.includes("UI_STYLE.")) {
120
+ return null;
121
+ }
122
+ this.addWatchFile?.(options.configPath);
123
+ const styles = await options.getStyles();
124
+ let changed = false;
125
+ const transformedCode = code.replace(UI_STYLE_PATTERN, (match, rawPath) => {
126
+ const path = rawPath.slice(1).split(".");
127
+ const value = resolveStyleValue(styles, id, path);
128
+ if (typeof value === "string") {
129
+ changed = true;
130
+ return toStringLiteral(value);
131
+ }
132
+ if (value === void 0) {
133
+ changed = true;
134
+ return "''";
135
+ }
136
+ this.error(`UI_STYLE path "${match}" in ${id} points to an object. Use a full string path.`);
137
+ });
138
+ return changed ? { code: transformedCode, map: null } : null;
139
+ }
140
+ });
141
+
142
+ const UI_STYLE_FILE_PATTERN = /\.(?:[cm]?[jt]sx?|vue)$/;
143
+ const scanUiStyleFiles = async (directory) => {
144
+ const entries = await readdir(directory, { withFileTypes: true }).catch(() => []);
145
+ const files = [];
146
+ for (const entry of entries) {
147
+ const path = join(directory, entry.name);
148
+ if (entry.isDirectory()) {
149
+ files.push(...await scanUiStyleFiles(path));
150
+ continue;
151
+ }
152
+ if (UI_STYLE_FILE_PATTERN.test(entry.name)) {
153
+ files.push(path);
154
+ }
155
+ }
156
+ return files;
157
+ };
4
158
  const module$1 = defineNuxtModule({
5
159
  defaults: {
6
160
  componentPrefix: "Brick",
161
+ configPath: "~/ui.config.ts",
7
162
  target: "world"
8
163
  },
9
164
  meta: {
@@ -13,18 +168,88 @@ const module$1 = defineNuxtModule({
13
168
  configKey: "brickflowUi",
14
169
  name: "@brickflow/ui"
15
170
  },
16
- setup(options, nuxt) {
171
+ async setup(options, nuxt) {
17
172
  const resolver = createResolver(import.meta.url);
18
173
  const currentConfig = nuxt.options.runtimeConfig.public.brickflowUi ?? {};
174
+ const defaultConfigPath = resolver.resolve("./runtime/tailwind");
175
+ const runtimePath = resolver.resolve("./runtime");
176
+ const resolvedConfigPath = await resolvePath(options.configPath ?? defaultConfigPath).catch(
177
+ () => defaultConfigPath
178
+ );
179
+ const loadConfig = createJiti(import.meta.url, {
180
+ interopDefault: true,
181
+ moduleCache: false
182
+ });
183
+ const loadUiConfig = async () => {
184
+ const validateConfig = defineBrickflowUiConfig;
185
+ return validateConfig(
186
+ await loadConfig.import(resolvedConfigPath).catch(() => ({}))
187
+ );
188
+ };
189
+ await loadUiConfig();
190
+ const uiStyleTypePath = resolve(nuxt.options.buildDir, "types/brickflow-ui-style.d.ts");
191
+ const generateUiStyleTypes = async () => {
192
+ const files = await scanUiStyleFiles(runtimePath);
193
+ const fileContents = await Promise.all(
194
+ files.map(async (file) => ({
195
+ content: await readFile(file, "utf8"),
196
+ file
197
+ }))
198
+ );
199
+ const paths = fileContents.flatMap(({ content }) => collectUiStylePathsFromCode(content));
200
+ const configPaths = fileContents.flatMap(
201
+ ({ content, file }) => collectUiStyleConfigPathsFromCode(content, file)
202
+ );
203
+ await mkdir(resolve(nuxt.options.buildDir, "types"), { recursive: true });
204
+ await writeFile(
205
+ uiStyleTypePath,
206
+ createUiStyleTypeDeclaration({
207
+ configPaths,
208
+ paths
209
+ })
210
+ );
211
+ };
212
+ const uiConfigTemplate = addTemplate({
213
+ filename: "brickflow/brickflow-ui-config.mjs",
214
+ getContents: () => [
215
+ `import rawConfig from ${JSON.stringify(resolvedConfigPath.replaceAll("\\", "/"))}`,
216
+ `import { defineBrickflowUiConfig } from ${JSON.stringify(resolver.resolve("./runtime/tailwind").replaceAll("\\", "/"))}`,
217
+ "",
218
+ "const config = defineBrickflowUiConfig(rawConfig)",
219
+ "",
220
+ "export const UI_STYLE = config.uiStyles",
221
+ "export const uiStyles = config.uiStyles",
222
+ "export default config",
223
+ ""
224
+ ].join("\n")
225
+ });
19
226
  nuxt.options.runtimeConfig.public.brickflowUi = {
20
227
  ...currentConfig,
21
228
  message: "world",
22
229
  target: options.target ?? "world"
23
230
  };
24
- nuxt.options.css.push(resolver.resolve("./runtime/assets/css/main.css"));
231
+ nuxt.options.alias["#brickflow-ui-config"] = uiConfigTemplate.dst;
232
+ await generateUiStyleTypes();
233
+ nuxt.hook("prepare:types", async ({ references }) => {
234
+ await generateUiStyleTypes();
235
+ references.push({ path: uiStyleTypePath });
236
+ });
237
+ nuxt.hook("builder:watch", async (_event, path) => {
238
+ const absolutePath = resolve(nuxt.options.srcDir, path);
239
+ if (relative(runtimePath, absolutePath).startsWith("..") || !UI_STYLE_FILE_PATTERN.test(path)) {
240
+ return;
241
+ }
242
+ await generateUiStyleTypes();
243
+ });
25
244
  nuxt.hook("vite:extendConfig", (config) => {
26
245
  const viteConfig = config;
27
246
  viteConfig.plugins ??= [];
247
+ viteConfig.plugins.push(
248
+ brickflowUiStylePlugin({
249
+ configPath: resolvedConfigPath,
250
+ getStyles: async () => (await loadUiConfig()).uiStyles
251
+ })
252
+ );
28
253
  viteConfig.plugins.push(tailwindcss());
29
254
  });
30
255
  addImportsDir(resolver.resolve("./runtime/composables"));
@@ -1 +0,0 @@
1
- @import "tailwindcss";@source "../../components";@theme{--font-sans:"Manrope","Inter",ui-sans-serif,system-ui,sans-serif;--color-brick-50:oklch(0.98 0.01 30);--color-brick-100:oklch(0.95 0.02 30);--color-brick-200:oklch(0.9 0.04 30);--color-brick-400:oklch(0.72 0.15 28);--color-brick-500:oklch(0.64 0.18 28);--color-brick-600:oklch(0.57 0.17 28);--color-ink-950:oklch(0.18 0.02 260);--shadow-brick:0 18px 40px -24px rgba(145,63,43,.55)}
@@ -5,19 +5,25 @@ const props = defineProps({
5
5
  type: { type: String, required: false, default: "button" },
6
6
  variant: { type: String, required: false, default: "primary" }
7
7
  });
8
- const sizeClass = computed(() => props.size === "sm" ? "min-h-10 px-4 text-sm" : "min-h-12 px-5 text-sm/6");
9
- const variantClass = computed(
10
- () => props.variant === "secondary" ? "border border-brick-200/80 bg-white/70 text-ink-950 shadow-[inset_0_1px_0_rgba(255,255,255,0.85)] hover:border-brick-400/50 hover:bg-brick-50" : "border border-brick-500/80 bg-linear-to-r from-brick-500 to-brick-600 text-white shadow-brick hover:from-brick-400 hover:to-brick-500"
11
- );
8
+ const sizeClasses = {
9
+ md: UI_STYLE.size.md,
10
+ sm: UI_STYLE.size.lol
11
+ };
12
+ const variantClasses = {
13
+ primary: UI_STYLE.variant.primary,
14
+ secondary: UI_STYLE.variant.secondary
15
+ };
16
+ const sizeClass = computed(() => sizeClasses[props.size]);
17
+ const variantClass = computed(() => variantClasses[props.variant]);
12
18
  </script>
13
19
 
14
20
  <template>
15
21
  <button
16
22
  :type="props.type"
17
- class="inline-flex items-center justify-center gap-2 rounded-full font-semibold tracking-[-0.01em] transition-all duration-200 ease-out focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brick-500 disabled:pointer-events-none disabled:opacity-50"
18
- :class="[sizeClass, variantClass]"
23
+ :class="[UI_STYLE.base, sizeClass, variantClass]"
19
24
  data-testid="brick-button"
20
25
  >
26
+ <span :class="UI_STYLE.size.kio">LOL</span>
21
27
  <slot />
22
28
  </button>
23
29
  </template>
@@ -5,7 +5,7 @@ export const usebrickflow = () => {
5
5
  const brickflowConfig = computed(() => config.public.brickflowUi ?? {});
6
6
  const target = computed(() => brickflowConfig.value.target ?? "world");
7
7
  const message = computed(() => brickflowConfig.value.message ?? `Hello ${target.value}`);
8
- const className = computed(() => target.value === "world" ? "ring-1 ring-brick-100/70" : "");
8
+ const className = computed(() => target.value === "world" ? UI_STYLE.state.world : "");
9
9
  return {
10
10
  className,
11
11
  message,
@@ -0,0 +1,30 @@
1
+ export {}
2
+
3
+ declare module '#brickflow-ui-config' {
4
+ import type { BrickflowUiConfig } from './tailwind'
5
+
6
+ export const UI_STYLE: BrickflowUiConfig['uiStyles']
7
+ export const uiStyles: BrickflowUiConfig['uiStyles']
8
+
9
+ const config: BrickflowUiConfig
10
+
11
+ export default config
12
+ }
13
+
14
+ declare global {
15
+ interface BrickflowUiStylePaths {}
16
+
17
+ type BrickflowUiStyleType = BrickflowUiStylePaths & BrickflowUiStyleValue
18
+
19
+ type BrickflowUiStyleValue = string & {
20
+ readonly [key: string]: BrickflowUiStyleValue
21
+ }
22
+
23
+ const UI_STYLE: BrickflowUiStyleType
24
+ }
25
+
26
+ declare module '@vue/runtime-core' {
27
+ interface ComponentCustomProperties {
28
+ readonly UI_STYLE: BrickflowUiStyleType
29
+ }
30
+ }
@@ -0,0 +1,25 @@
1
+ export interface BrickflowUiConfig<TStyles = BrickflowUiStyles> {
2
+ uiStyles: TStyles;
3
+ }
4
+ declare global {
5
+ interface BrickflowUiConfigStylePaths {
6
+ }
7
+ type BrickflowUiStyleObject = {
8
+ readonly [key: string]: BrickflowUiStyleObject | string;
9
+ };
10
+ }
11
+ export type BrickflowUiConfigInput<TStyles extends BrickflowUiStyleObject = BrickflowUiStyles> = {
12
+ readonly uiStyles?: TStyles;
13
+ };
14
+ export type BrickflowUiNoExtraKeys<TValue, TShape> = TValue extends string ? TShape extends string ? TValue : never : TShape extends string ? never : {
15
+ readonly [K in keyof TValue]: K extends keyof TShape ? BrickflowUiNoExtraKeys<TValue[K], TShape[K]> : never;
16
+ };
17
+ export type BrickflowUiStrictStyles<TStyles extends BrickflowUiConfigStylePaths> = BrickflowUiNoExtraKeys<TStyles, BrickflowUiConfigStylePaths> & TStyles;
18
+ export type BrickflowUiStyles = BrickflowUiStyleObject;
19
+ export declare const emptyUiStyles: {};
20
+ export declare const emptyBrickflowUiConfig: BrickflowUiConfig<typeof emptyUiStyles>;
21
+ export declare function defineBrickflowUiConfig(): BrickflowUiConfig<typeof emptyUiStyles>;
22
+ export declare function defineBrickflowUiConfig<const TStyles extends BrickflowUiConfigStylePaths>(config: BrickflowUiConfigInput<BrickflowUiStrictStyles<TStyles>>): BrickflowUiConfig<TStyles>;
23
+ export declare const uiStyles: {};
24
+ export default emptyBrickflowUiConfig;
25
+ //# sourceMappingURL=tailwind.d.ts.map
@@ -0,0 +1,30 @@
1
+ export const emptyUiStyles = {};
2
+ export const emptyBrickflowUiConfig = {
3
+ uiStyles: emptyUiStyles
4
+ };
5
+ export function defineBrickflowUiConfig(config = {}) {
6
+ const uiStyles2 = config.uiStyles ?? emptyUiStyles;
7
+ validateUiStyles(uiStyles2);
8
+ return {
9
+ uiStyles: uiStyles2
10
+ };
11
+ }
12
+ export const uiStyles = emptyBrickflowUiConfig.uiStyles;
13
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
14
+ function validateUiStyles(value, path = "uiStyles") {
15
+ if (!isRecord(value)) {
16
+ throw new TypeError(`${path} must be an object.`);
17
+ }
18
+ for (const [key, childValue] of Object.entries(value)) {
19
+ const childPath = `${path}.${key}`;
20
+ if (typeof childValue === "string") {
21
+ continue;
22
+ }
23
+ if (isRecord(childValue)) {
24
+ validateUiStyles(childValue, childPath);
25
+ continue;
26
+ }
27
+ throw new TypeError(`${childPath} must be a string or an object.`);
28
+ }
29
+ }
30
+ export default emptyBrickflowUiConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brickflow/ui",
3
- "version": "0.0.23",
3
+ "version": "0.0.25",
4
4
  "type": "module",
5
5
  "description": "brickflow UI Nuxt module.",
6
6
  "files": [
@@ -11,10 +11,14 @@
11
11
  ".": {
12
12
  "types": "./dist/module.d.mts",
13
13
  "import": "./dist/module.mjs"
14
+ },
15
+ "./tailwind": {
16
+ "types": "./dist/runtime/tailwind.d.ts",
17
+ "import": "./dist/runtime/tailwind.js"
14
18
  }
15
19
  },
16
20
  "scripts": {
17
- "build": "nuxt-module-build",
21
+ "build": "nuxt-module-build build",
18
22
  "typecheck": "vue-tsc --project tsconfig.json --noEmit",
19
23
  "lint": "pnpm typecheck && eslint -v && NODE_ENV=deploy eslint --cache .",
20
24
  "lint:fix": "pnpm exec eslint . --fix",
@@ -27,6 +31,7 @@
27
31
  "dependencies": {
28
32
  "@brickflow/utils": "workspace:*",
29
33
  "@tailwindcss/vite": "4.3.2",
34
+ "jiti": "^2.7.0",
30
35
  "tailwindcss": "4.3.2"
31
36
  },
32
37
  "devDependencies": {
@@ -1,4 +0,0 @@
1
- declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
- declare const _default: typeof __VLS_export;
3
- export default _default;
4
- //# sourceMappingURL=Demo.vue.d.ts.map
@@ -1,24 +0,0 @@
1
- <script setup>
2
- import { usebrickflow } from "../composables/useBrickme";
3
- import Button from "./Button.vue";
4
- const brickflow = usebrickflow();
5
- </script>
6
-
7
- <template>
8
- <section
9
- class="grid gap-4 rounded-3xl border border-brick-200/70 bg-white/75 p-6 shadow-brick backdrop-blur"
10
- :class="brickflow.className"
11
- data-testid="brick-demo"
12
- >
13
- <div class="grid gap-1">
14
- <strong class="text-base font-semibold tracking-[-0.02em] text-ink-950">
15
- {{ brickflow.message }}
16
- </strong>
17
- <span class="text-sm text-zinc-600">Component from @brickflow/ui</span>
18
- </div>
19
- <div class="flex flex-wrap gap-3">
20
- <Button>Primary action</Button>
21
- <Button variant="secondary">Secondary action</Button>
22
- </div>
23
- </section>
24
- </template>
@@ -1,4 +0,0 @@
1
- declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
- declare const _default: typeof __VLS_export;
3
- export default _default;
4
- //# sourceMappingURL=Demo.vue.d.ts.map