@bouko/react 3.2.3 → 3.2.4
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/assets/icons/check copy.svg +6 -0
- package/dist/assets/icons/check-circle copy.svg +6 -0
- package/dist/assets/icons/chevron copy.svg +6 -0
- package/dist/assets/icons/paperclip copy.svg +6 -0
- package/dist/assets/icons/spinner copy.svg +6 -0
- package/dist/assets/icons/x-circle copy.svg +6 -0
- package/dist/components/badge.d.ts +5 -0
- package/dist/components/badge.js +4 -0
- package/dist/components/button/ghost.d.ts +7 -0
- package/dist/components/button/ghost.js +8 -0
- package/dist/components/button/icon.d.ts +3 -2
- package/dist/components/button/icon.js +8 -4
- package/dist/components/button/load.d.ts +10 -0
- package/dist/components/button/load.js +35 -0
- package/dist/components/button/normal.js +1 -1
- package/dist/components/button.d.ts +10 -0
- package/dist/components/button.js +22 -0
- package/dist/components/date/_.d.ts +1 -0
- package/dist/components/date/_.js +1 -0
- package/dist/components/date/time-ago.d.ts +7 -0
- package/dist/components/date/time-ago.js +7 -0
- package/dist/components/flex.d.ts +16 -0
- package/dist/components/flex.js +8 -0
- package/dist/components/form/builder.d.ts +24 -0
- package/dist/components/form/builder.js +61 -0
- package/dist/components/form/fields.d.ts +16 -0
- package/dist/components/form/fields.js +21 -0
- package/dist/components/form/footer.d.ts +9 -0
- package/dist/components/form/footer.js +17 -0
- package/dist/components/form/index.d.ts +3 -0
- package/dist/components/form/index.js +3 -0
- package/dist/components/form/loaders.d.ts +3 -0
- package/dist/components/form/loaders.js +11 -0
- package/dist/components/form/test-next.d.ts +1 -0
- package/dist/components/form/test-next.js +6 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/layout/heading.d.ts +31 -0
- package/dist/components/layout/heading.js +22 -0
- package/dist/components/search/index.d.ts +6 -0
- package/dist/components/search/index.js +12 -0
- package/dist/hooks/color.d.ts +7 -0
- package/dist/hooks/color.js +8 -0
- package/package.json +54 -2
- package/dist/assets/icons/trash.svg +0 -6
- package/dist/assets/icons/upload.svg +0 -6
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="1em" height="1em">
|
|
2
|
+
<path
|
|
3
|
+
d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"
|
|
4
|
+
fill="currentColor"
|
|
5
|
+
/>
|
|
6
|
+
</svg>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em">
|
|
2
|
+
<path
|
|
3
|
+
d="M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-111 111-47-47c-9.4-9.4-24.6-9.4-33.9 0s-9.4 24.6 0 33.9l64 64c9.4 9.4 24.6 9.4 33.9 0L369 209z"
|
|
4
|
+
fill="currentColor"
|
|
5
|
+
/>
|
|
6
|
+
</svg>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em">
|
|
2
|
+
<path
|
|
3
|
+
d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"
|
|
4
|
+
fill="currentColor"
|
|
5
|
+
/>
|
|
6
|
+
</svg>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="1em" height="1em">
|
|
2
|
+
<path
|
|
3
|
+
d="M364.2 83.8c-24.4-24.4-64-24.4-88.4 0l-184 184c-42.1 42.1-42.1 110.3 0 152.4s110.3 42.1 152.4 0l152-152c10.9-10.9 28.7-10.9 39.6 0s10.9 28.7 0 39.6l-152 152c-64 64-167.6 64-231.6 0s-64-167.6 0-231.6l184-184c46.3-46.3 121.3-46.3 167.6 0s46.3 121.3 0 167.6l-176 176c-28.6 28.6-75 28.6-103.6 0s-28.6-75 0-103.6l144-144c10.9-10.9 28.7-10.9 39.6 0s10.9 28.7 0 39.6l-144 144c-6.7 6.7-6.7 17.7 0 24.4s17.7 6.7 24.4 0l176-176c24.4-24.4 24.4-64 0-88.4z"
|
|
4
|
+
fill="currentColor"
|
|
5
|
+
/>
|
|
6
|
+
</svg>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em">
|
|
2
|
+
<path
|
|
3
|
+
d="M304 48a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zm0 416a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zM48 304a48 48 0 1 0 0-96 48 48 0 1 0 0 96zm464-48a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zM142.9 437A48 48 0 1 0 75 369.1 48 48 0 1 0 142.9 437zm0-294.2A48 48 0 1 0 75 75a48 48 0 1 0 67.9 67.9zM369.1 437A48 48 0 1 0 437 369.1 48 48 0 1 0 369.1 437z"
|
|
4
|
+
fill="currentColor"
|
|
5
|
+
/>
|
|
6
|
+
</svg>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em">
|
|
2
|
+
<path
|
|
3
|
+
d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"
|
|
4
|
+
fill="currentColor"
|
|
5
|
+
/>
|
|
6
|
+
</svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
export default function Badge({ style, children }) {
|
|
3
|
+
return (_jsx("span", { className: "w-min px-3 py-1 bg-accent/20 border border-accent-dark rounded-full text-xs text-accent-dark font-semibold", children: children }));
|
|
4
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from "./normal";
|
|
3
|
+
import { cn } from "@bouko/style";
|
|
4
|
+
export default function GhostButton({ style, action, children }) {
|
|
5
|
+
if (!children)
|
|
6
|
+
return null;
|
|
7
|
+
return (_jsx(Button, { variant: "ghost", style: cn("p-0 hover:brightness-110", style), onClick: action, children: children }));
|
|
8
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Clickable } from "../../core/types";
|
|
2
2
|
type Props = Clickable & {
|
|
3
3
|
variant?: "accent" | "primary" | "outline" | "ghost" | "secondary" | "error" | "future";
|
|
4
|
+
label?: React.ReactNode;
|
|
4
5
|
};
|
|
5
|
-
export declare function IconButton({ variant, style, icon, action, disabled }: Props): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export declare function IconButton({ label, variant, style, icon, action, disabled }: Props): import("react/jsx-runtime").JSX.Element;
|
|
6
7
|
export default IconButton;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { RowBox } from "../layout/flex";
|
|
3
3
|
import Check from "../../assets/icons/check.svg";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import { cn, tv } from "@bouko/style";
|
|
5
|
+
export function IconButton({ label, variant, style, icon, action, disabled = false }) {
|
|
6
|
+
const Button = () => (_jsx("button", { className: cn(styles({ variant }), style), onClick: action, disabled: !action || disabled, children: icon || _jsx(Check, {}) }));
|
|
7
|
+
if (label)
|
|
8
|
+
return (_jsxs(RowBox, { style: "items-center gap-1", children: [_jsx(Button, {}), _jsx("span", { className: "text-xs text-primary-darker", children: label })] }));
|
|
9
|
+
return _jsx(Button, {});
|
|
6
10
|
}
|
|
7
11
|
const styles = tv({
|
|
8
12
|
base: "flex justify-center items-center p-2 rounded-full outline-none border text-sm text-primary-dark duration-200 cursor-pointer disabled:cursor-not-allowed hover:brightness-115",
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type ButtonProps = {
|
|
3
|
+
variant?: "primary" | "outline" | "ghost";
|
|
4
|
+
size?: "xs" | "sm" | "md" | "lg";
|
|
5
|
+
style?: string;
|
|
6
|
+
onClick?: () => void;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
};
|
|
10
|
+
export default function LoadButton({ size, variant, style, onClick, children, ...props }: ButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use state";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { cn, tv } from "@bouko/style";
|
|
5
|
+
import CheckCircle from "../../assets/icons/check-circle.svg";
|
|
6
|
+
import Spinner from "../../assets/icons/spinner.svg";
|
|
7
|
+
export default function LoadButton({ size, variant, style, onClick, children, ...props }) {
|
|
8
|
+
const [isLoading, setLoading] = useState(false);
|
|
9
|
+
const submit = async () => {
|
|
10
|
+
setLoading(true);
|
|
11
|
+
try {
|
|
12
|
+
await onClick?.();
|
|
13
|
+
}
|
|
14
|
+
catch { }
|
|
15
|
+
setLoading(false);
|
|
16
|
+
};
|
|
17
|
+
return (_jsxs("button", { className: cn(styles({ variant, size }), style), onClick: submit, ...props, children: [isLoading ? _jsx(Spinner, { className: "animate-spin" }) : _jsx(CheckCircle, {}), children] }));
|
|
18
|
+
}
|
|
19
|
+
const styles = tv({
|
|
20
|
+
base: "flex justify-center items-center gap-2 bg-accent hover:bg-accent-dark border border-accent-dark rounded font-semibold text-background-light duration-200 cursor-pointer disabled:cursor-not-allowed",
|
|
21
|
+
defaultVariants: { size: "md" },
|
|
22
|
+
variants: {
|
|
23
|
+
variant: {
|
|
24
|
+
primary: "bg-primary hover:bg-primary-dark border-primary-dark",
|
|
25
|
+
outline: "!bg-transparent border-accent hover:border-accent-dark text-primary",
|
|
26
|
+
ghost: "!bg-transparent border-transparent text-accent hover:text-accent-dark"
|
|
27
|
+
},
|
|
28
|
+
size: {
|
|
29
|
+
xs: "px-3 py-1 text-xs",
|
|
30
|
+
sm: "px-4 py-2 text-xs sm:text-sm",
|
|
31
|
+
md: "px-4 py-2",
|
|
32
|
+
lg: "px-5 py-3 text-lg"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
@@ -13,7 +13,7 @@ export function Button({ size, variant, style, icon, action, disabled, children
|
|
|
13
13
|
catch { }
|
|
14
14
|
setLoading(false);
|
|
15
15
|
};
|
|
16
|
-
return (_jsxs("button", { className: cn(styles({ variant, size }),
|
|
16
|
+
return (_jsxs("button", { className: cn(styles({ variant, size }), style), onClick: submit, disabled: disabled || isLoading, children: [icon && isLoading
|
|
17
17
|
? _jsx(Spinner, { className: "animate-spin" })
|
|
18
18
|
: icon, children] }));
|
|
19
19
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type ButtonProps = {
|
|
3
|
+
variant?: "primary" | "outline" | "ghost";
|
|
4
|
+
size?: "xs" | "sm" | "md" | "lg";
|
|
5
|
+
style?: string;
|
|
6
|
+
onClick?: () => void;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
};
|
|
10
|
+
export declare function Button({ size, variant, style, ...props }: ButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn, tv } from "@bouko/style";
|
|
3
|
+
export function Button({ size, variant, style, ...props }) {
|
|
4
|
+
return (_jsx("button", { className: cn(styles({ variant, size }), style), ...props }));
|
|
5
|
+
}
|
|
6
|
+
const styles = tv({
|
|
7
|
+
base: "flex justify-center items-center gap-2 bg-accent hover:bg-accent-dark border border-accent-dark rounded font-semibold text-background-light duration-200 cursor-pointer disabled:cursor-not-allowed",
|
|
8
|
+
defaultVariants: { size: "md" },
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
primary: "bg-primary hover:bg-primary-dark border-primary-dark",
|
|
12
|
+
outline: "!bg-transparent border-accent hover:border-accent-dark text-primary",
|
|
13
|
+
ghost: "!bg-transparent border-transparent text-accent hover:text-accent-dark"
|
|
14
|
+
},
|
|
15
|
+
size: {
|
|
16
|
+
xs: "px-3 py-1 text-xs",
|
|
17
|
+
sm: "px-4 py-2 text-xs sm:text-sm",
|
|
18
|
+
md: "px-4 py-2",
|
|
19
|
+
lg: "px-5 py-3 text-lg"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as TimeAgo } from "./time-ago";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as TimeAgo } from "./time-ago";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import ReactTimeAgo from "react-time-ago";
|
|
3
|
+
export default function TimeAgo({ style, prefix, date }) {
|
|
4
|
+
if (!date)
|
|
5
|
+
return null;
|
|
6
|
+
return (_jsxs("span", { className: style, children: [_jsx("span", { className: "mr-1", children: prefix }), _jsx(ReactTimeAgo, { date: date, timeStyle: "mini-minute-now" })] }));
|
|
7
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
type Props = Position & {
|
|
3
|
+
style?: string;
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
};
|
|
6
|
+
type Position = {
|
|
7
|
+
center?: boolean;
|
|
8
|
+
centerX?: boolean;
|
|
9
|
+
centerY?: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function ColumnBox({ style, center, centerX, centerY, children }: Props): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare function RowBox({ style, children }: {
|
|
13
|
+
style?: string;
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "@bouko/style";
|
|
3
|
+
export function ColumnBox({ style, center, centerX, centerY, children }) {
|
|
4
|
+
return (_jsx("div", { className: cn("flex flex-col", center && "items-center justify-center", centerX && "items-center", centerY && "justify-center", style), children: children }));
|
|
5
|
+
}
|
|
6
|
+
export function RowBox({ style, children }) {
|
|
7
|
+
return (_jsx("div", { className: cn("flex", style), children: children }));
|
|
8
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { FormBuilderField } from "./types";
|
|
2
|
+
type Props<T> = {
|
|
3
|
+
styles?: {
|
|
4
|
+
container?: string;
|
|
5
|
+
submit?: string;
|
|
6
|
+
cancel?: string;
|
|
7
|
+
};
|
|
8
|
+
id?: string;
|
|
9
|
+
button?: string;
|
|
10
|
+
fields: FormBuilderField<T>;
|
|
11
|
+
validator?: (x: Partial<T>) => ({
|
|
12
|
+
success: boolean;
|
|
13
|
+
});
|
|
14
|
+
sound?: string;
|
|
15
|
+
cancel?: boolean;
|
|
16
|
+
submit: (data: T, clear?: () => void) => Promise<void | never>;
|
|
17
|
+
};
|
|
18
|
+
type Item = {
|
|
19
|
+
id: string;
|
|
20
|
+
initial?: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function mapIdToInitial<T>(arrays: Item[][]): Partial<T>;
|
|
23
|
+
export default function FormBuilder<T>({ fields, button, styles, submit, cancel }: Props<T>): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { useForm } from "./functions";
|
|
5
|
+
import { cn } from "@bouko/style";
|
|
6
|
+
import { RowBox } from "../layout/flex";
|
|
7
|
+
import Input from "../input";
|
|
8
|
+
import TextArea from "../textarea";
|
|
9
|
+
import Select from "../select";
|
|
10
|
+
import MultipleChoice from "../multiple-choice";
|
|
11
|
+
import Attachment from "../attachment";
|
|
12
|
+
import { Button } from "../button";
|
|
13
|
+
import CheckCircle from "../../assets/icons/check-circle.svg";
|
|
14
|
+
import XCircle from "../../assets/icons/x-circle.svg";
|
|
15
|
+
import Spinner from "../../assets/icons/spinner.svg";
|
|
16
|
+
export function mapIdToInitial(arrays) {
|
|
17
|
+
const result = {};
|
|
18
|
+
for (const inner of arrays) {
|
|
19
|
+
for (const obj of inner) {
|
|
20
|
+
if ("initial" in obj && obj["initial"]) {
|
|
21
|
+
result[obj.id] = obj.initial;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
export default function FormBuilder({ fields, button, styles, submit, cancel = false }) {
|
|
28
|
+
const initialShi = mapIdToInitial(fields);
|
|
29
|
+
const { data, setField, clear } = useForm(initialShi);
|
|
30
|
+
const [isLoading, setLoading] = useState(false);
|
|
31
|
+
const requiredFields = fields.flatMap(arr => arr.filter(item => item.required !== false));
|
|
32
|
+
const isValid = requiredFields.filter(x => {
|
|
33
|
+
const val = data[x.id];
|
|
34
|
+
return !val || val === "";
|
|
35
|
+
}).length === 0; // validator(data).success;
|
|
36
|
+
// const play = useSound(sound);
|
|
37
|
+
const handleSubmit = async () => {
|
|
38
|
+
setLoading(true);
|
|
39
|
+
try {
|
|
40
|
+
await submit(data, clear);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
let msg = err.message;
|
|
44
|
+
if (msg !== "NEXT_REDIRECT")
|
|
45
|
+
alert(msg);
|
|
46
|
+
}
|
|
47
|
+
setLoading(false);
|
|
48
|
+
};
|
|
49
|
+
return (_jsxs("div", { className: cn("flex flex-col w-full gap-4", styles?.container), children: [fields.map((row, i) => (_jsx(RowBox, { style: "w-full gap-5 overflow-hidden", children: row.map(({ element, id, rows, label, placeholder, disabled, options, required, note }) => {
|
|
50
|
+
if (element === "input")
|
|
51
|
+
return (_jsx(Input, { id: id, styles: { container: "flex-1" }, label: label, placeholder: placeholder, value: data[id], update: x => setField(id, x), disabled: disabled, note: note, required: required }, id));
|
|
52
|
+
else if (element === "select")
|
|
53
|
+
return (_jsx(Select, { id: id, label: label, placeholder: placeholder, options: options || [], value: data[id], update: x => setField(id, x), note: note, required: required }, id));
|
|
54
|
+
else if (element === "textarea")
|
|
55
|
+
return (_jsx(TextArea, { id: id, label: label, placeholder: placeholder, rows: rows, value: data[id], update: x => setField(id, x), note: note, required: required }, id));
|
|
56
|
+
else if (element === "multiple-choice")
|
|
57
|
+
return (_jsx(MultipleChoice, { id: id, label: label, options: options || [], value: data[id], update: x => setField(id, x), note: note, required: required }, id));
|
|
58
|
+
else if (element === "attachment")
|
|
59
|
+
return (_jsx(Attachment, { id: id, label: label, value: data[id], update: x => setField(id, x), note: note, required: required }, id));
|
|
60
|
+
}) }, i))), _jsxs(RowBox, { style: "items-center gap-2 mt-2 w-full", children: [_jsxs(Button, { style: cn("flex-1 text-sm", styles?.submit), onClick: handleSubmit, disabled: !isValid || isLoading, children: [_jsx("div", { className: cn(isLoading ? "hidden" : ""), children: _jsx(CheckCircle, {}) }), _jsx("div", { className: cn(isLoading ? "animate-spin" : "hidden"), children: _jsx(Spinner, {}) }), button || "Submit"] }), cancel !== false && (_jsxs(Button, { style: cn("text-sm text-error hover:text-error-light", styles?.cancel), variant: "ghost", onClick: clear, children: [_jsx(XCircle, {}), "Cancel"] }))] })] }));
|
|
61
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Field, Option } from "./types";
|
|
2
|
+
export type FormBuilderField<T = unknown> = (Omit<Field<T>, "value" | "update"> & {
|
|
3
|
+
id: string;
|
|
4
|
+
element: string;
|
|
5
|
+
rows?: number;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
options?: Option[];
|
|
8
|
+
})[][];
|
|
9
|
+
type Props<T> = {
|
|
10
|
+
id?: string;
|
|
11
|
+
fields: FormBuilderField<T>;
|
|
12
|
+
data: T;
|
|
13
|
+
setField: (x: string, y: unknown) => void;
|
|
14
|
+
};
|
|
15
|
+
export default function Fields<T>({ fields, data, setField }: Props<T>): Promise<import("react/jsx-runtime").JSX.Element[]>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { RowBox } from "../layout/flex";
|
|
3
|
+
import Input from "../input";
|
|
4
|
+
import Select from "../select";
|
|
5
|
+
import TextArea from "../textarea";
|
|
6
|
+
import MultipleChoice from "../multiple-choice";
|
|
7
|
+
import Attachment from "../attachment";
|
|
8
|
+
export default async function Fields({ fields, data, setField }) {
|
|
9
|
+
return fields.map((row, i) => (_jsx(RowBox, { style: "w-full gap-5 overflow-hidden", children: row.map(({ element, id, rows, label, placeholder, disabled, options, required, note }) => {
|
|
10
|
+
if (element === "input")
|
|
11
|
+
return (_jsx(Input, { id: id, styles: { container: "flex-1" }, label: label, placeholder: placeholder, value: data[id], update: x => setField(id, x), disabled: disabled, note: note, required: required }, id));
|
|
12
|
+
else if (element === "select")
|
|
13
|
+
return (_jsx(Select, { id: id, label: label, placeholder: placeholder, options: options || [], value: data[id], update: x => setField(id, x), note: note, required: required }, id));
|
|
14
|
+
else if (element === "textarea")
|
|
15
|
+
return (_jsx(TextArea, { id: id, label: label, placeholder: placeholder, rows: rows, value: data[id], update: x => setField(id, x), note: note, required: required }, id));
|
|
16
|
+
else if (element === "multiple-choice")
|
|
17
|
+
return (_jsx(MultipleChoice, { id: id, label: label, options: options || [], value: data[id], update: x => setField(id, x), note: note, required: required }, id));
|
|
18
|
+
else if (element === "attachment")
|
|
19
|
+
return (_jsx(Attachment, { id: id, label: label, value: data[id], update: x => setField(id, x), note: note, required: required }, id));
|
|
20
|
+
}) }, i)));
|
|
21
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ZodTypeAny } from "zod";
|
|
2
|
+
type Props<T> = {
|
|
3
|
+
data: T;
|
|
4
|
+
validator: ZodTypeAny;
|
|
5
|
+
submit: (data: T) => void;
|
|
6
|
+
clear: () => void;
|
|
7
|
+
};
|
|
8
|
+
export default function FormFooter<T>({ data, validator, submit, clear }: Props<T>): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Button } from "../button";
|
|
5
|
+
import { RowBox } from "../layout/flex";
|
|
6
|
+
import CheckCircle from "../../assets/icons/check-circle.svg";
|
|
7
|
+
import Spinner from "../../assets/icons/spinner.svg";
|
|
8
|
+
export default function FormFooter({ data, validator, submit, clear }) {
|
|
9
|
+
const isValid = validator.safeParse(data).success;
|
|
10
|
+
const [isLoading, setLoading] = useState(false);
|
|
11
|
+
const handleSubmit = async () => {
|
|
12
|
+
setLoading(true);
|
|
13
|
+
await submit(data);
|
|
14
|
+
setLoading(false);
|
|
15
|
+
};
|
|
16
|
+
return (_jsxs(RowBox, { style: "items-center gap-2 mt-2 w-full", children: [_jsxs(Button, { onClick: handleSubmit, disabled: !isValid || isLoading, children: [_jsx(Spinner, { className: isLoading ? "animate-spin" : "hidden" }), _jsx(CheckCircle, { className: isLoading ? "hidden" : "" }), "Create"] }), _jsx(Button, { variant: "ghost", onClick: clear, disabled: isLoading, children: "Cancel" })] }));
|
|
17
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
export async function loadJson(name) {
|
|
4
|
+
const file = await fs.readFile(process.cwd() + "/assets/" + name + ".json", "utf-8");
|
|
5
|
+
return JSON.parse(file);
|
|
6
|
+
}
|
|
7
|
+
export async function loadForm(name) {
|
|
8
|
+
if (!name)
|
|
9
|
+
throw new Error("Form not found");
|
|
10
|
+
return loadJson(`forms/${name}`);
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function loadJson<T>(name: string): Promise<T>;
|
package/dist/components/index.js
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
type Props = {
|
|
3
|
+
badge?: string;
|
|
4
|
+
title: ReactNode;
|
|
5
|
+
subtitle?: ReactNode;
|
|
6
|
+
style?: Styles;
|
|
7
|
+
};
|
|
8
|
+
type Styles = {
|
|
9
|
+
container?: string;
|
|
10
|
+
badge?: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
subtitle?: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Text heading with badge, title, subtitle.
|
|
16
|
+
*
|
|
17
|
+
* @param {string} badge - Little note above the title (optional).
|
|
18
|
+
* @param {ReactNode} title - Main title content.
|
|
19
|
+
* @param {ReactNode} subtitle - Subtitle content (optional).
|
|
20
|
+
* @param {Styles} style - Additional styles (optional).
|
|
21
|
+
**/
|
|
22
|
+
export default function Heading({ badge, title, subtitle, style }: Props): import("react/jsx-runtime").JSX.Element;
|
|
23
|
+
export {};
|
|
24
|
+
/**
|
|
25
|
+
* Problems
|
|
26
|
+
*
|
|
27
|
+
* - Perfect `mergeStyles`
|
|
28
|
+
* - Perfect `RowBox`
|
|
29
|
+
* - Perfect `ColumnBox`
|
|
30
|
+
* - Perfect `Badge`
|
|
31
|
+
**/
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { mergeStyles } from "@bouko/style";
|
|
3
|
+
import { RowBox, ColumnBox } from "./flex";
|
|
4
|
+
import { default as Badge } from "../text/badge";
|
|
5
|
+
/**
|
|
6
|
+
* Text heading with badge, title, subtitle.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} badge - Little note above the title (optional).
|
|
9
|
+
* @param {ReactNode} title - Main title content.
|
|
10
|
+
* @param {ReactNode} subtitle - Subtitle content (optional).
|
|
11
|
+
* @param {Styles} style - Additional styles (optional).
|
|
12
|
+
**/
|
|
13
|
+
export default function Heading({ badge, title, subtitle, style = {} }) {
|
|
14
|
+
const styles = mergeStyles(base, style);
|
|
15
|
+
return (_jsxs(ColumnBox, { style: styles.container, children: [_jsx(Badge, { style: styles.badge, children: badge }), _jsx(RowBox, { style: styles.title, children: title }), _jsx(RowBox, { style: styles.subtitle, children: subtitle })] }));
|
|
16
|
+
}
|
|
17
|
+
const base = {
|
|
18
|
+
container: "items-center",
|
|
19
|
+
badge: "mb-2",
|
|
20
|
+
title: "items-center gap-2 text-xl sm:text-2xl 2xl:text-3xl font-bold text-primary",
|
|
21
|
+
subtitle: "items-center gap-2 max-sm:text-sm 2xl:text-lg text-primary-light dark:text-primary-dark"
|
|
22
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { RowBox } from "../flex";
|
|
5
|
+
import { Button } from "../button";
|
|
6
|
+
export default function SearchBar({ placeholder, action }) {
|
|
7
|
+
const [query, search] = useState("");
|
|
8
|
+
return (_jsxs(RowBox, { style: styles.container, children: [_jsxs("div", { className: "relative grow", children: [_jsx("input", { className: "w-full outline-none lowercase text-lg placeholder-slate-500 tracking-wide", placeholder: placeholder, value: query, onChange: (e) => search(e.target.value), onKeyUp: (e) => e.key === "Enter" ? action(query) : null }), _jsx("div", { className: "absolute top-0 right-0 w-12 h-full bg-gradient-to-l from-slate-950" })] }), _jsx(Button, { size: "sm", style: "gap-[0.4rem] font-extrabold py-1 px-3 font-mono", onClick: () => action(query), children: "GO" })] }));
|
|
9
|
+
}
|
|
10
|
+
const styles = {
|
|
11
|
+
container: "items-center gap-6 w-xl pl-5 pr-4 py-3 bg-slate-950 border border-border rounded-md"
|
|
12
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
export default function useColor({ variable, color, original }) {
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
document.documentElement.style.setProperty(`--${variable}`, color);
|
|
6
|
+
return () => document.documentElement.style.setProperty(`--${variable}`, original);
|
|
7
|
+
}, []);
|
|
8
|
+
}
|
package/package.json
CHANGED
|
@@ -1,60 +1,112 @@
|
|
|
1
1
|
{
|
|
2
2
|
|
|
3
3
|
"name": "@bouko/react",
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
"version": "3.2.4",
|
|
6
|
+
|
|
5
7
|
"main": "./dist/index.js",
|
|
8
|
+
|
|
6
9
|
"types": "./dist/index.d.ts",
|
|
10
|
+
|
|
7
11
|
"license": "MIT",
|
|
12
|
+
|
|
8
13
|
"files": [
|
|
14
|
+
|
|
9
15
|
"dist"
|
|
16
|
+
|
|
10
17
|
],
|
|
18
|
+
|
|
11
19
|
"publishConfig": {
|
|
20
|
+
|
|
12
21
|
"access": "public"
|
|
22
|
+
|
|
13
23
|
},
|
|
24
|
+
|
|
14
25
|
"author": "",
|
|
26
|
+
|
|
15
27
|
"description": "",
|
|
28
|
+
|
|
16
29
|
"peerDependencies": {
|
|
30
|
+
|
|
17
31
|
"react-router-dom": "^7.12.0"
|
|
32
|
+
|
|
18
33
|
},
|
|
34
|
+
|
|
19
35
|
"exports": {
|
|
36
|
+
|
|
20
37
|
".": "./dist/index.js",
|
|
38
|
+
|
|
21
39
|
"./styles.css": "./dist/styles.css"
|
|
40
|
+
|
|
22
41
|
},
|
|
42
|
+
|
|
23
43
|
"engines": {},
|
|
24
44
|
|
|
25
45
|
"scripts": {
|
|
46
|
+
|
|
26
47
|
"build": "tsc",
|
|
48
|
+
|
|
27
49
|
"postbuild": "node ./scripts/copy-styles.mjs"
|
|
50
|
+
|
|
28
51
|
},
|
|
29
52
|
|
|
30
53
|
"dependencies": {
|
|
54
|
+
|
|
31
55
|
"@bouko/audio": "^0.1.0",
|
|
56
|
+
|
|
32
57
|
"@bouko/form": "^0.5.7",
|
|
58
|
+
|
|
33
59
|
"@bouko/notify": "^0.1.8",
|
|
60
|
+
|
|
34
61
|
"@bouko/style": "^0.1.7",
|
|
62
|
+
|
|
35
63
|
"@bouko/time": "^0.1.3",
|
|
64
|
+
|
|
36
65
|
"@bouko/ts": "^0.3.5",
|
|
66
|
+
|
|
37
67
|
"@supabase/supabase-js": "^2.90.1",
|
|
68
|
+
|
|
38
69
|
"@wavesurfer/react": "^1.0.11",
|
|
70
|
+
|
|
39
71
|
"clsx": "^2.1.1",
|
|
72
|
+
|
|
40
73
|
"file-type": "^21.0.0",
|
|
74
|
+
|
|
41
75
|
"framer-motion": "^12.23.6",
|
|
76
|
+
|
|
42
77
|
"ms": "^2.1.3",
|
|
78
|
+
|
|
43
79
|
"react-dropzone": "^14.3.8",
|
|
80
|
+
|
|
44
81
|
"react-rnd": "^10.5.2",
|
|
82
|
+
|
|
83
|
+
"react-time-ago": "^7.4.4",
|
|
84
|
+
|
|
45
85
|
"tailwind-merge": "^3.3.1",
|
|
86
|
+
|
|
46
87
|
"tailwind-variants": "^1.0.0",
|
|
88
|
+
|
|
47
89
|
"wavesurfer.js": "^7.10.1",
|
|
90
|
+
|
|
48
91
|
"zod": "^4.0.13"
|
|
92
|
+
|
|
49
93
|
},
|
|
50
94
|
|
|
51
95
|
"devDependencies": {
|
|
96
|
+
|
|
52
97
|
"@types/node": "^24.3.1",
|
|
98
|
+
|
|
53
99
|
"@types/react": "^19.1.10",
|
|
100
|
+
|
|
54
101
|
"dependency-cruiser": "^17.0.1",
|
|
102
|
+
|
|
55
103
|
"react": "^19.1.1",
|
|
104
|
+
|
|
56
105
|
"react-router-dom": "^7.12.0",
|
|
106
|
+
|
|
57
107
|
"typescript": "^5.9.2"
|
|
108
|
+
|
|
58
109
|
}
|
|
59
110
|
|
|
60
|
-
}
|
|
111
|
+
}
|
|
112
|
+
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" width="1em" height="1em">
|
|
2
|
-
<path
|
|
3
|
-
d="M232.7 69.9C237.1 56.8 249.3 48 263.1 48L377 48C390.8 48 403 56.8 407.4 69.9L416 96L512 96C529.7 96 544 110.3 544 128C544 145.7 529.7 160 512 160L128 160C110.3 160 96 145.7 96 128C96 110.3 110.3 96 128 96L224 96L232.7 69.9zM128 208L512 208L512 512C512 547.3 483.3 576 448 576L192 576C156.7 576 128 547.3 128 512L128 208zM216 272C202.7 272 192 282.7 192 296L192 488C192 501.3 202.7 512 216 512C229.3 512 240 501.3 240 488L240 296C240 282.7 229.3 272 216 272zM320 272C306.7 272 296 282.7 296 296L296 488C296 501.3 306.7 512 320 512C333.3 512 344 501.3 344 488L344 296C344 282.7 333.3 272 320 272zM424 272C410.7 272 400 282.7 400 296L400 488C400 501.3 410.7 512 424 512C437.3 512 448 501.3 448 488L448 296C448 282.7 437.3 272 424 272z"
|
|
4
|
-
fill="currentColor"
|
|
5
|
-
/>
|
|
6
|
-
</svg>
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" width="1em" height="1em">
|
|
2
|
-
<path
|
|
3
|
-
d="M352 173.3L352 384C352 401.7 337.7 416 320 416C302.3 416 288 401.7 288 384L288 173.3L246.6 214.7C234.1 227.2 213.8 227.2 201.3 214.7C188.8 202.2 188.8 181.9 201.3 169.4L297.3 73.4C309.8 60.9 330.1 60.9 342.6 73.4L438.6 169.4C451.1 181.9 451.1 202.2 438.6 214.7C426.1 227.2 405.8 227.2 393.3 214.7L352 173.3zM320 464C364.2 464 400 428.2 400 384L480 384C515.3 384 544 412.7 544 448L544 480C544 515.3 515.3 544 480 544L160 544C124.7 544 96 515.3 96 480L96 448C96 412.7 124.7 384 160 384L240 384C240 428.2 275.8 464 320 464zM464 488C477.3 488 488 477.3 488 464C488 450.7 477.3 440 464 440C450.7 440 440 450.7 440 464C440 477.3 450.7 488 464 488z"
|
|
4
|
-
fill="currentColor"
|
|
5
|
-
/>
|
|
6
|
-
</svg>
|