Skip to content
This repository was archived by the owner on Dec 25, 2025. It is now read-only.

Commit 6a7bb01

Browse files
authored
Add unstyled packages (#157)
* Initial commit * Update tsconfig.json * Add tsup.config.ts * Removed env.d.ts * Expose UnstyledRegistry * Updated type-check command * Fix lockfile and install unstyled package in example app * Added checkbox and selectbox * Add support for Date and DateTime scalar * Add classNames * Removed unstyled package * Reset version * Add changeset * Fix lockfile
1 parent 9450a34 commit 6a7bb01

9 files changed

Lines changed: 367 additions & 0 deletions

File tree

.changeset/dirty-snakes-own.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
"@fabrix-framework/unstyled": minor
3+
---
4+
5+
Introduced unstyled component package
6+
7+
Supported GraphQL types are as follows:
8+
9+
* String
10+
* Int
11+
* Float
12+
* Boolean
13+
* Date
14+
* DateTime

packages/unstyled/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# `@fabrix-framework/unstyled`
2+
3+
Unstyled components for Fabrix
4+
5+
## Install
6+
7+
```bash
8+
npm install --save @fabrix-framework/unstyled
9+
```
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const config = require("@fabrix-framework/eslint-config");
2+
3+
module.exports = config;

packages/unstyled/package.json

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"name": "@fabrix-framework/unstyled",
3+
"private": false,
4+
"version": "0.0.0",
5+
"description": "Unstyled components for Fabrix",
6+
"publishConfig": {
7+
"provenance": true
8+
},
9+
"homepage": "https://fabrix-framework.github.io/docs/",
10+
"repository": {
11+
"type": "git",
12+
"url": "https://github.com/fabrix-framework/fabrix.git"
13+
},
14+
"exports": {
15+
".": {
16+
"types": "./dist/index.d.mts",
17+
"default": "./dist/index.mjs"
18+
}
19+
},
20+
"files": [
21+
"dist/**"
22+
],
23+
"scripts": {
24+
"dev": "NODE_ENV=development tsup --watch",
25+
"build": "tsup",
26+
"lint": "eslint '**/*.{ts,tsx}' --ignore-pattern 'dist/*' --max-warnings=0",
27+
"type-check": "tsc --incremental",
28+
"test": "exit 0"
29+
},
30+
"keywords": [],
31+
"author": "",
32+
"license": "MIT",
33+
"dependencies": {
34+
"@fabrix-framework/fabrix": "workspace:*",
35+
"es-toolkit": "^1.30.1",
36+
"react-hook-form": "^7.53.1"
37+
},
38+
"peerDependencies": {
39+
"react": ">=18",
40+
"react-dom": ">=18"
41+
},
42+
"devDependencies": {
43+
"@fabrix-framework/eslint-config": "workspace:*",
44+
"@fabrix-framework/prettier-config": "workspace:*",
45+
"@types/node": "^22.7.5",
46+
"@types/react": "^19.0.7",
47+
"@types/react-dom": "^19.0.3",
48+
"eslint": "^9.18.0",
49+
"prettier": "^3.4.2",
50+
"tsup": "^8.3.5",
51+
"typescript": "^5.7.3"
52+
},
53+
"prettier": "@fabrix-framework/prettier-config"
54+
}
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import {
2+
FieldComponentProps,
3+
ComponentRegistry,
4+
FormComponentProps,
5+
FormFieldComponentProps,
6+
TableComponentProps,
7+
} from "@fabrix-framework/fabrix";
8+
import { ReactNode } from "react";
9+
import { FieldErrors, useController } from "react-hook-form";
10+
import { get } from "es-toolkit/compat";
11+
12+
const fieldView = (props: FieldComponentProps) => {
13+
const { value, type, name } = props;
14+
15+
if (type?.type === "Object" || type?.type === "List") {
16+
return null;
17+
}
18+
19+
return (
20+
<div role="region">
21+
<label>{name}:</label>
22+
<span>{value as ReactNode}</span>
23+
</div>
24+
);
25+
};
26+
27+
const tableView = (props: TableComponentProps) => {
28+
const headers = props.headers.flatMap((header) => {
29+
if (header.type?.type === "Object" || header.type?.type === "List") {
30+
return [];
31+
}
32+
33+
return [
34+
{
35+
key: header.key,
36+
label: `${header.label}`,
37+
},
38+
];
39+
});
40+
41+
return (
42+
<table className={props.className}>
43+
<thead>
44+
<tr>
45+
{headers.map((header) => (
46+
<th key={header.key}>{header.label}</th>
47+
))}
48+
</tr>
49+
</thead>
50+
<tbody>
51+
{props.values.map((item, index) => (
52+
<tr key={index}>
53+
{headers.map((header) => (
54+
<td key={header.key}>{get(item, header.key) as ReactNode}</td>
55+
))}
56+
</tr>
57+
))}
58+
</tbody>
59+
</table>
60+
);
61+
};
62+
63+
const formView = (props: FormComponentProps) => {
64+
return (
65+
<div role="form">
66+
{props.renderFields()}
67+
{props.renderSubmit(({ submit }) => (
68+
<button onClick={() => submit()}>Submit</button>
69+
))}
70+
</div>
71+
);
72+
};
73+
74+
const FormFieldWrapper = (
75+
props: React.PropsWithChildren<
76+
FormFieldComponentProps & {
77+
error?: FieldErrors[string];
78+
}
79+
>,
80+
) => (
81+
<div
82+
role="group"
83+
aria-label={props.name}
84+
className={props.attributes.className}
85+
>
86+
<label htmlFor={props.name}>{props.attributes.label}</label>
87+
{props.children}
88+
{props.error && <div role="alert">{props.error.message?.toString()}</div>}
89+
</div>
90+
);
91+
92+
const formFieldView = (props: FormFieldComponentProps) => {
93+
const { field, formState } = useController({
94+
name: props.name,
95+
defaultValue: () => {
96+
if (props.type?.type === "Scalar" && props.type.name === "Boolean") {
97+
return false;
98+
}
99+
return "";
100+
},
101+
});
102+
const error = formState.errors[props.name];
103+
104+
switch (props.type?.type) {
105+
case "Enum": {
106+
return (
107+
<FormFieldWrapper {...props} error={error}>
108+
<select
109+
{...field}
110+
onChange={(e) => {
111+
field.onChange(e.target.value);
112+
}}
113+
>
114+
{props.type.meta.values.map((value) => (
115+
<option key={value} value={value}>
116+
{value}
117+
</option>
118+
))}
119+
</select>
120+
</FormFieldWrapper>
121+
);
122+
}
123+
124+
case "Scalar": {
125+
switch (props.type.name) {
126+
case "Boolean": {
127+
return (
128+
<FormFieldWrapper {...props}>
129+
<input
130+
{...field}
131+
type="checkbox"
132+
onChange={(e) => {
133+
field.onChange(e.target.checked);
134+
}}
135+
/>
136+
</FormFieldWrapper>
137+
);
138+
}
139+
140+
case "Int":
141+
case "Float": {
142+
return (
143+
<FormFieldWrapper {...props}>
144+
<input
145+
{...field}
146+
onChange={(e) => {
147+
field.onChange(parseFloat(e.target.value));
148+
}}
149+
/>
150+
</FormFieldWrapper>
151+
);
152+
}
153+
154+
case "Date": {
155+
return (
156+
<FormFieldWrapper {...props}>
157+
<input {...field} type="date" />
158+
</FormFieldWrapper>
159+
);
160+
}
161+
162+
case "DateTime": {
163+
return (
164+
<FormFieldWrapper {...props}>
165+
<input {...field} type="datetime-local" />
166+
</FormFieldWrapper>
167+
);
168+
}
169+
170+
default: {
171+
return (
172+
<FormFieldWrapper {...props}>
173+
<input {...field} />
174+
</FormFieldWrapper>
175+
);
176+
}
177+
}
178+
}
179+
180+
default:
181+
return null;
182+
}
183+
};
184+
185+
export const UnstyledRegistry = new ComponentRegistry({
186+
default: {
187+
field: fieldView,
188+
form: formView,
189+
formField: formFieldView,
190+
table: tableView,
191+
},
192+
});

packages/unstyled/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { UnstyledRegistry } from "./components";

packages/unstyled/tsconfig.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"compilerOptions": {
3+
"baseUrl": "./src",
4+
"paths": {
5+
"@*": ["./*"]
6+
},
7+
"target": "ES2020",
8+
"lib": ["ES2020", "DOM"],
9+
"module": "ESNext",
10+
"moduleResolution": "Bundler",
11+
"useDefineForClassFields": true,
12+
"skipLibCheck": true,
13+
"declaration": true,
14+
"declarationMap": true,
15+
"noEmit": true,
16+
"jsx": "react-jsx",
17+
18+
/* Linting */
19+
"strict": true,
20+
"noUnusedLocals": true,
21+
"noUnusedParameters": true,
22+
"noImplicitReturns": true,
23+
"noUncheckedIndexedAccess": true,
24+
"noFallthroughCasesInSwitch": true
25+
},
26+
"include": ["."],
27+
}

packages/unstyled/tsup.config.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference types="node" />
2+
import { defineConfig } from "tsup";
3+
4+
const devOpts =
5+
process.env.NODE_ENV === "development"
6+
? {
7+
minify: false,
8+
splitting: false,
9+
sourcemap: true,
10+
}
11+
: {};
12+
13+
export default defineConfig({
14+
format: ["esm"],
15+
entry: ["src/index.ts"],
16+
clean: true,
17+
minify: true,
18+
dts: true,
19+
external: ["react"],
20+
...devOpts,
21+
});

pnpm-lock.yaml

Lines changed: 46 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)