@bouko/react 1.9.5 → 1.9.6
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/components/badge.d.ts +1 -1
- package/dist/components/badge.js +1 -1
- package/dist/components/layout/heading.d.ts +3 -16
- package/dist/components/layout/heading.js +6 -37
- package/dist/core/format.d.ts +0 -2
- package/dist/core/format.js +0 -2
- package/dist/next/src/components/layouts/locked.d.ts +12 -0
- package/dist/next/src/components/layouts/locked.js +15 -0
- package/dist/react/src/components/animate/configs.d.ts +136 -0
- package/dist/react/src/components/animate/configs.js +62 -0
- package/dist/react/src/components/animate/index.d.ts +12 -0
- package/dist/react/src/components/animate/index.js +7 -0
- package/dist/react/src/components/attachment.d.ts +2 -0
- package/dist/react/src/components/attachment.js +19 -0
- package/dist/react/src/components/button.d.ts +10 -0
- package/dist/react/src/components/button.js +22 -0
- package/dist/react/src/components/checkbox.d.ts +8 -0
- package/dist/react/src/components/checkbox.js +13 -0
- package/dist/react/src/components/dropdown/normal.d.ts +10 -0
- package/dist/react/src/components/dropdown/normal.js +19 -0
- package/dist/react/src/components/fade-carousel.d.ts +4 -0
- package/dist/react/src/components/fade-carousel.js +14 -0
- package/dist/react/src/components/field.d.ts +10 -0
- package/dist/react/src/components/field.js +10 -0
- package/dist/react/src/components/form/footer.d.ts +9 -0
- package/dist/react/src/components/form/footer.js +17 -0
- package/dist/react/src/components/form/functions.d.ts +8 -0
- package/dist/react/src/components/form/functions.js +27 -0
- package/dist/react/src/components/form/index.d.ts +26 -0
- package/dist/react/src/components/form/index.js +37 -0
- package/dist/react/src/components/form/types.d.ts +29 -0
- package/dist/react/src/components/form/types.js +1 -0
- package/dist/react/src/components/index.d.ts +8 -0
- package/dist/react/src/components/index.js +8 -0
- package/dist/react/src/components/input.d.ts +11 -0
- package/dist/react/src/components/input.js +7 -0
- package/dist/react/src/components/layout/flex.d.ts +23 -0
- package/dist/react/src/components/layout/flex.js +34 -0
- package/dist/react/src/components/layout/heading.d.ts +24 -0
- package/dist/react/src/components/layout/heading.js +45 -0
- package/dist/react/src/components/layout/separator.d.ts +3 -0
- package/dist/react/src/components/layout/separator.js +5 -0
- package/dist/react/src/components/list/index.d.ts +2 -0
- package/dist/react/src/components/list/index.js +2 -0
- package/dist/react/src/components/list/item.d.ts +9 -0
- package/dist/react/src/components/list/item.js +7 -0
- package/dist/react/src/components/list/variants/bullet.d.ts +9 -0
- package/dist/react/src/components/list/variants/bullet.js +16 -0
- package/dist/react/src/components/list/variants/number.d.ts +10 -0
- package/dist/react/src/components/list/variants/number.js +18 -0
- package/dist/react/src/components/multiple-choice.d.ts +2 -0
- package/dist/react/src/components/multiple-choice.js +13 -0
- package/dist/react/src/components/search-bar.d.ts +9 -0
- package/dist/react/src/components/search-bar.js +14 -0
- package/dist/react/src/components/select.d.ts +6 -0
- package/dist/react/src/components/select.js +24 -0
- package/dist/react/src/components/text/badge.d.ts +9 -0
- package/dist/react/src/components/text/badge.js +16 -0
- package/dist/react/src/components/textarea.d.ts +7 -0
- package/dist/react/src/components/textarea.js +12 -0
- package/dist/react/src/components/upload/file.d.ts +8 -0
- package/dist/react/src/components/upload/file.js +15 -0
- package/dist/react/src/core/format.d.ts +3 -0
- package/dist/react/src/core/format.js +34 -0
- package/dist/react/src/core/functions.d.ts +3 -0
- package/dist/react/src/core/functions.js +41 -0
- package/dist/react/src/core/types.d.ts +10 -0
- package/dist/react/src/core/types.js +1 -0
- package/dist/react/src/hooks/element/container.d.ts +4 -0
- package/dist/react/src/hooks/element/container.js +7 -0
- package/dist/react/src/hooks/element/resize.d.ts +1 -0
- package/dist/react/src/hooks/element/resize.js +12 -0
- package/dist/react/src/hooks/index.d.ts +2 -0
- package/dist/react/src/hooks/index.js +2 -0
- package/dist/react/src/index.d.ts +14 -0
- package/dist/react/src/index.js +14 -0
- package/package.json +1 -1
- package/dist/components/search/index.d.ts +0 -6
- package/dist/components/search/index.js +0 -12
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { cn } from "@bouko/style";
|
|
5
|
+
import CheckCircle from "../../assets/icons/check-circle.svg";
|
|
6
|
+
import Spinner from "../../assets/icons/spinner.svg";
|
|
7
|
+
import XCircle from "../../assets/icons/x-circle.svg";
|
|
8
|
+
export * from "./functions";
|
|
9
|
+
export * from "./types";
|
|
10
|
+
import { RowBox } from "../layout/flex";
|
|
11
|
+
import Input from "../input";
|
|
12
|
+
import Select from "../select";
|
|
13
|
+
import TextArea from "../textarea";
|
|
14
|
+
import MultipleChoice from "../multiple-choice";
|
|
15
|
+
import { Button } from "../button";
|
|
16
|
+
import Attachment from "../attachment";
|
|
17
|
+
export function FormBuilder({ fields, validator, data, styles, update, submit, clear }) {
|
|
18
|
+
const isValid = validator.safeParse(data).success;
|
|
19
|
+
const [isLoading, setLoading] = useState(false);
|
|
20
|
+
const handleSubmit = async () => {
|
|
21
|
+
setLoading(true);
|
|
22
|
+
await submit.action(data);
|
|
23
|
+
setLoading(false);
|
|
24
|
+
};
|
|
25
|
+
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 }) => {
|
|
26
|
+
if (element === "input")
|
|
27
|
+
return (_jsx(Input, { id: id, styles: { container: "flex-1" }, label: label, placeholder: placeholder, value: data[id], update: update, disabled: disabled, note: note, required: required }, id));
|
|
28
|
+
else if (element === "select")
|
|
29
|
+
return (_jsx(Select, { id: id, label: label, placeholder: placeholder, options: options || [], value: data[id], update: update, note: note, required: required }, id));
|
|
30
|
+
else if (element === "textarea")
|
|
31
|
+
return (_jsx(TextArea, { id: id, label: label, placeholder: placeholder, rows: rows, value: data[id], update: update, note: note, required: required }, id));
|
|
32
|
+
else if (element === "multiple-choice")
|
|
33
|
+
return (_jsx(MultipleChoice, { id: id, label: label, options: options || [], value: data[id], update: update, note: note, required: required }, id));
|
|
34
|
+
else if (element === "attachment")
|
|
35
|
+
return (_jsx(Attachment, { id: id, label: label, value: data[id], update: update, note: note, required: required }, id));
|
|
36
|
+
}) }, i))), _jsxs(RowBox, { style: "items-center gap-2 mt-2 w-full", children: [_jsxs(Button, { style: cn("text-sm", styles?.submit), onClick: handleSubmit, disabled: !isValid, children: [_jsx(Spinner, { className: isLoading ? "animate-spin" : "hidden" }), _jsx("div", { className: isLoading ? "hidden" : "", children: submit.icon || _jsx(CheckCircle, {}) }), submit.label || "Submit"] }), 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"] }))] })] }));
|
|
37
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ReactNode, Dispatch, SetStateAction } from "react";
|
|
2
|
+
export type SetState<T> = Dispatch<SetStateAction<T>>;
|
|
3
|
+
export type FormSection<T> = {
|
|
4
|
+
data: T;
|
|
5
|
+
update: SetState<T>;
|
|
6
|
+
clear: () => void;
|
|
7
|
+
};
|
|
8
|
+
export type Field<T, K = string> = {
|
|
9
|
+
id: string;
|
|
10
|
+
label?: string;
|
|
11
|
+
style?: string;
|
|
12
|
+
value?: K;
|
|
13
|
+
update: SetState<T>;
|
|
14
|
+
required?: boolean;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
note?: ReactNode;
|
|
17
|
+
};
|
|
18
|
+
export type Option = {
|
|
19
|
+
id: string;
|
|
20
|
+
label?: string;
|
|
21
|
+
active?: boolean;
|
|
22
|
+
select?: () => void;
|
|
23
|
+
};
|
|
24
|
+
export type OptionField<T> = Field<T> & {
|
|
25
|
+
options: Option[];
|
|
26
|
+
};
|
|
27
|
+
export type Form<T> = {
|
|
28
|
+
data: T;
|
|
29
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { default as Heading } from "./layout/heading";
|
|
2
|
+
export { default as Separator } from "./layout/separator";
|
|
3
|
+
export * from "./layout/flex";
|
|
4
|
+
export { default as Badge } from "./text/badge";
|
|
5
|
+
export { default as Dropdown } from "./dropdown/normal";
|
|
6
|
+
export { default as FileUploader } from "./upload/file";
|
|
7
|
+
export * from "./list";
|
|
8
|
+
export * from "./form";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { default as Heading } from "./layout/heading";
|
|
2
|
+
export { default as Separator } from "./layout/separator";
|
|
3
|
+
export * from "./layout/flex";
|
|
4
|
+
export { default as Badge } from "./text/badge";
|
|
5
|
+
export { default as Dropdown } from "./dropdown/normal";
|
|
6
|
+
export { default as FileUploader } from "./upload/file";
|
|
7
|
+
export * from "./list";
|
|
8
|
+
export * from "./form";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type Field as FieldProps } from "./form";
|
|
2
|
+
type Props<T> = FieldProps<T> & {
|
|
3
|
+
placeholder?: string;
|
|
4
|
+
styles?: {
|
|
5
|
+
container?: string;
|
|
6
|
+
input?: string;
|
|
7
|
+
};
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export default function Input<T>({ id, styles, style, label, required, note, update, ...props }: Props<T>): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import Field from "./field";
|
|
3
|
+
import { cn } from "@bouko/style";
|
|
4
|
+
import { setField } from "./form";
|
|
5
|
+
export default function Input({ id, styles, style, label, required = true, note, update, ...props }) {
|
|
6
|
+
return (_jsx(Field, { style: styles?.container, label: label, required: required, note: note, children: _jsx("input", { className: cn("px-3 py-2 bg-input border border-outline duration-200 focus:border-outline-light outline-none rounded text-sm disabled:brightness-90 disabled:cursor-not-allowed", styles?.input), onChange: ({ target: { value } }) => setField(update, id, value), ...props }) }));
|
|
7
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Component } from "../../core/types";
|
|
2
|
+
type Props = Component & {
|
|
3
|
+
center?: boolean;
|
|
4
|
+
centerX?: boolean;
|
|
5
|
+
centerY?: boolean;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Flex containers with configurable alignment and styling.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} style - Additional styles for container.
|
|
11
|
+
* @param {ReactNode} children - Child elements to render inside the row.
|
|
12
|
+
* @param {Props} opts - Variant options for styling.
|
|
13
|
+
**/
|
|
14
|
+
export declare const RowBox: ({ style, children, ...opts }: Props) => import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export declare const ColumnBox: ({ style, children, ...opts }: Props) => import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export {};
|
|
17
|
+
/**
|
|
18
|
+
* Problems
|
|
19
|
+
*
|
|
20
|
+
* - Perfect `Component`
|
|
21
|
+
* - Perfect `cn`
|
|
22
|
+
* - Perfect `tv`
|
|
23
|
+
**/
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn, tv } from "@bouko/style";
|
|
3
|
+
/**
|
|
4
|
+
* Flex containers with configurable alignment and styling.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} style - Additional styles for container.
|
|
7
|
+
* @param {ReactNode} children - Child elements to render inside the row.
|
|
8
|
+
* @param {Props} opts - Variant options for styling.
|
|
9
|
+
**/
|
|
10
|
+
export const RowBox = ({ style, children, ...opts }) => (_jsx("div", { className: cn(row(opts), style), children: children }));
|
|
11
|
+
export const ColumnBox = ({ style, children, ...opts }) => (_jsx("div", { className: cn(column(opts), style), children: children }));
|
|
12
|
+
/**
|
|
13
|
+
* Styling variants for flex options.
|
|
14
|
+
*
|
|
15
|
+
* @param {boolean} centerX - center children horizontally
|
|
16
|
+
* @param {boolean} centerY - center children vertically
|
|
17
|
+
* @param {boolean} center - center children on both axis
|
|
18
|
+
**/
|
|
19
|
+
const row = tv({
|
|
20
|
+
base: "flex",
|
|
21
|
+
variants: {
|
|
22
|
+
centerX: { true: "justify-center" },
|
|
23
|
+
centerY: { true: "items-center" },
|
|
24
|
+
center: { true: "items-center justify-center" }
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
const column = tv({
|
|
28
|
+
base: "flex flex-col",
|
|
29
|
+
variants: {
|
|
30
|
+
centerX: { true: "items-center" },
|
|
31
|
+
centerY: { true: "justify-center" },
|
|
32
|
+
center: { true: "items-center justify-center" }
|
|
33
|
+
}
|
|
34
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
type Options = {
|
|
3
|
+
size?: "md" | "lg";
|
|
4
|
+
align?: "left" | "center";
|
|
5
|
+
};
|
|
6
|
+
type Styles = {
|
|
7
|
+
container?: string;
|
|
8
|
+
text?: string;
|
|
9
|
+
title?: string;
|
|
10
|
+
subtitle?: string;
|
|
11
|
+
};
|
|
12
|
+
type Props = {
|
|
13
|
+
options?: Options;
|
|
14
|
+
styles?: Styles;
|
|
15
|
+
badge?: string;
|
|
16
|
+
icon?: ReactNode;
|
|
17
|
+
title: ReactNode;
|
|
18
|
+
size?: "md" | "lg";
|
|
19
|
+
align?: "left" | "center";
|
|
20
|
+
subtitle?: ReactNode;
|
|
21
|
+
reverse?: boolean;
|
|
22
|
+
};
|
|
23
|
+
export default function Heading({ styles, options, badge, icon, title, subtitle, reverse }: Props): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn, tv } from "@bouko/style";
|
|
3
|
+
import { RowBox, ColumnBox } from "..";
|
|
4
|
+
import Badge from "../text/badge";
|
|
5
|
+
export default function Heading({ styles = {}, options = {}, badge, icon, title, subtitle, reverse }) {
|
|
6
|
+
const custom = test(options);
|
|
7
|
+
return (_jsxs(RowBox, { style: cn(base.container, styles.container), children: [icon, _jsxs(ColumnBox, { style: cn(custom.subcontainer(), reverse && "flex-col-reverse"), children: [badge && _jsx(Badge, { style: base.badge, children: badge }), _jsx(RowBox, { style: cn(custom.title(), styles.title, styles.text), children: title }), subtitle && _jsx(RowBox, { style: cn(custom.subtitle(), styles.subtitle, styles.text), children: subtitle })] })] }));
|
|
8
|
+
}
|
|
9
|
+
const base = {
|
|
10
|
+
container: "gap-2 items-center",
|
|
11
|
+
badge: "mb-2",
|
|
12
|
+
};
|
|
13
|
+
const test = tv({
|
|
14
|
+
slots: {
|
|
15
|
+
subcontainer: "grow",
|
|
16
|
+
title: "items-center gap-2 text-xl font-bold text-primary",
|
|
17
|
+
subtitle: "items-center gap-2 font-semibold text-primary-light dark:text-primary-dark"
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
size: "md",
|
|
21
|
+
align: "center"
|
|
22
|
+
},
|
|
23
|
+
variants: {
|
|
24
|
+
size: {
|
|
25
|
+
md: {
|
|
26
|
+
title: "text-xl 2xl:text-2xl",
|
|
27
|
+
subtitle: "text-base 2xl:text-lg"
|
|
28
|
+
},
|
|
29
|
+
lg: {
|
|
30
|
+
subcontainer: "gap-3",
|
|
31
|
+
title: "text-5xl 2xl:text-6xl",
|
|
32
|
+
subtitle: "text-sm sm:text-base 2xl:text-lg"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
align: {
|
|
36
|
+
left: {
|
|
37
|
+
subcontainer: "items-start"
|
|
38
|
+
},
|
|
39
|
+
center: {
|
|
40
|
+
subcontainer: "items-center",
|
|
41
|
+
subtitle: "justify-center text-center"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
interface Props {
|
|
3
|
+
marker: ReactNode;
|
|
4
|
+
content: ReactNode;
|
|
5
|
+
index: number;
|
|
6
|
+
centerMarker?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export default function Item({ marker, content, centerMarker, index }: Props): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Animation } from "../animate";
|
|
3
|
+
import { cn } from "@bouko/style";
|
|
4
|
+
;
|
|
5
|
+
export default function Item({ marker, content, centerMarker, index }) {
|
|
6
|
+
return (_jsxs(Animation, { style: cn("flex items-start text-sm", centerMarker && "items-center"), variant: "fadeList", children: [marker, content] }, index));
|
|
7
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
interface Props {
|
|
3
|
+
style?: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
items: ReactNode[];
|
|
6
|
+
centerMarker?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export default function BulletList({ style, title, items, centerMarker }: Props): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import Item from "../item";
|
|
4
|
+
import { Animation } from "../../animate/";
|
|
5
|
+
import { cn } from "@bouko/style";
|
|
6
|
+
;
|
|
7
|
+
export default function BulletList({ style, title, items, centerMarker }) {
|
|
8
|
+
return (_jsxs(Animation, { style: cn(styles.base, style), variant: "fadeList", children: [!!title && _jsx("span", { className: styles.title, children: title }), _jsx(Animation, { style: cn(styles.base, style, !!title && "ml-2"), variant: "fadeList", children: items.map((x, i) => (_jsx(Item, { marker: _jsx(Marker, {}), content: x, index: i, centerMarker: centerMarker }, i))) })] }, title));
|
|
9
|
+
}
|
|
10
|
+
function Marker() {
|
|
11
|
+
return (_jsx("span", { className: "mr-3 text-accent", children: "\u2022" }));
|
|
12
|
+
}
|
|
13
|
+
const styles = {
|
|
14
|
+
base: "flex flex-col gap-1 w-full",
|
|
15
|
+
title: "mb-1 text-base"
|
|
16
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
interface Props {
|
|
3
|
+
style?: string;
|
|
4
|
+
marker?: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
items: ReactNode[];
|
|
7
|
+
centerMarker?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export default function NumberList({ style, marker, title, items, centerMarker }: Props): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import Item from "../item";
|
|
4
|
+
import { Animation } from "../../animate";
|
|
5
|
+
import { cn } from "@bouko/style";
|
|
6
|
+
;
|
|
7
|
+
export default function NumberList({ style, marker, title, items, centerMarker }) {
|
|
8
|
+
return (_jsxs(Animation, { style: cn(styles.base, style), variant: "fadeList", children: [!!title && _jsx("span", { className: styles.title, children: title }), _jsx(Animation, { style: cn(styles.base, style, !!title && "ml-2"), variant: "fadeList", children: items.map((x, i) => (_jsx(Item, { marker: _jsx(Marker, { style: marker, x: i + 1 }), content: x, index: i, centerMarker: centerMarker }, i))) }, "content")] }, "list"));
|
|
9
|
+
}
|
|
10
|
+
function Marker({ style, x }) {
|
|
11
|
+
return (_jsx("div", { className: cn(styles.marker, style), children: _jsx("span", { className: styles.label, children: x }) }));
|
|
12
|
+
}
|
|
13
|
+
const styles = {
|
|
14
|
+
base: "flex flex-col gap-6 w-full text-primary-dark",
|
|
15
|
+
title: "mb-1 text-base text-primary",
|
|
16
|
+
marker: "flex justify-center items-center mt-1 mr-6 min-w-7 w-7 aspect-square bg-radial-[at_50%_75%] from-accent-light to-accent to-accent-dark to-90% border border-accent-darker rounded-full shadow-glow-soft",
|
|
17
|
+
label: "font-bold text-background-lighter select-none"
|
|
18
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import Field from "./field";
|
|
3
|
+
import { setField } from "./form";
|
|
4
|
+
import { cn } from "@bouko/style";
|
|
5
|
+
export default function MultipleChoice({ id, label, options, style, value, update, required = true }) {
|
|
6
|
+
return (_jsx(Field, { style: style, label: label, required: required, children: _jsx("div", { className: styles.options, children: options.map((x, i) => (_jsxs("div", { className: styles.item, children: [_jsx(Dot, { ...x, active: x.id === value, select: () => setField(update, id, x.id) }), x.label] }, i))) }) }));
|
|
7
|
+
}
|
|
8
|
+
const Dot = ({ active, select }) => (_jsx("div", { className: cn(styles.dot, active && "bg-accent border-accent-dark"), onClick: select }));
|
|
9
|
+
const styles = {
|
|
10
|
+
options: "flex shrink-0 flex-col gap-1 mt-px w-full",
|
|
11
|
+
item: "flex items-center gap-3 text-sm",
|
|
12
|
+
dot: "size-3 bg-background-dark/50 hover:bg-background-dark duration-200 cursor-pointer border border-background-darker rounded-full"
|
|
13
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ReactElement } from "react";
|
|
2
|
+
export type SearchBarProps = {
|
|
3
|
+
button?: ReactElement<{
|
|
4
|
+
onClick: (query: string) => void;
|
|
5
|
+
}>;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
action: (query: string) => void;
|
|
8
|
+
};
|
|
9
|
+
export declare function SearchBar({ button, placeholder, action }: SearchBarProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cloneElement, useState } from "react";
|
|
4
|
+
import { RowBox } from "./layout/flex";
|
|
5
|
+
import { Button } from "./button";
|
|
6
|
+
export function SearchBar({ button, 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 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" })] }), button ? cloneElement(button, {
|
|
9
|
+
onClick: () => action(query)
|
|
10
|
+
}) : _jsx(Button, { size: "sm", style: "gap-[0.4rem] font-extrabold py-1 px-3 font-mono", onClick: () => action(query), children: "GO" })] }));
|
|
11
|
+
}
|
|
12
|
+
const styles = {
|
|
13
|
+
container: "items-center gap-6 w-xl max-w-full pl-5 pr-4 py-3 bg-slate-950 border border-border rounded-md"
|
|
14
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type OptionField } from "./form";
|
|
2
|
+
type Props<T> = OptionField<T> & {
|
|
3
|
+
placeholder?: string;
|
|
4
|
+
};
|
|
5
|
+
export default function Select<T>({ id, style, label, required, value, options, update, note }: Props<T>): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { AnimatePresence, motion } from "framer-motion";
|
|
5
|
+
import Chevron from "../assets/icons/chevron.svg";
|
|
6
|
+
import { setField } from "./form";
|
|
7
|
+
import { cn } from "@bouko/style";
|
|
8
|
+
export default function Select({ id, style, label, required = true, value, options, update, note }) {
|
|
9
|
+
const [isOpen, setOpen] = useState(false);
|
|
10
|
+
const active = options.find(x => x.id === value);
|
|
11
|
+
const select = (x) => {
|
|
12
|
+
setField(update, id, x);
|
|
13
|
+
setOpen(false);
|
|
14
|
+
};
|
|
15
|
+
return (_jsxs("div", { className: cn(styles.container, style), children: [_jsxs("div", { className: styles.subcontainer, children: [label && _jsxs("span", { className: styles.label, children: [label, " ", !required ? _jsx("span", { className: "italic text-slate-400", children: "(optional)" }) : ""] }), _jsxs("div", { className: cn(styles.trigger, !active?.label && "capitalize"), onClick: () => setOpen(x => !x), children: [active ? active.label ?? active.id : "None", _jsx(Chevron, { className: cn("text-xs text-slate-400 duration-200", isOpen && "rotate-180") })] }), note && !isOpen && _jsx("span", { className: styles.note, children: note })] }), _jsx(AnimatePresence, { children: isOpen && (_jsx(motion.div, { className: styles.dropdown, initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, children: options.map(({ id, label }) => (_jsx("span", { className: cn("w-full p-2 border-l-3 border-transparent hover:text-blue-600 duration-200 cursor-pointer", active?.id === id && "border-blue-600 bg-blue-500/10", !label && "capitalize"), onClick: () => select(id), children: label ?? id }, id))) }, "dropdown")) })] }));
|
|
16
|
+
}
|
|
17
|
+
const styles = {
|
|
18
|
+
container: "relative shrink-0 w-full",
|
|
19
|
+
subcontainer: "flex flex-col gap-1 w-full",
|
|
20
|
+
label: "text-xs text-slate-600",
|
|
21
|
+
trigger: "flex justify-between items-center px-3 py-2 bg-slate-200/50 hover:bg-slate-200/80 border border-slate-300 outline-blue-500 rounded text-sm duration-200 cursor-pointer",
|
|
22
|
+
dropdown: "absolute mt-2 z-50 flex flex-col gap-1 w-full bg-slate-100 rounded border border-slate-300 py-1 text-xs max-h-46 overflow-y-auto",
|
|
23
|
+
note: "mt-1 text-xs text-slate-500"
|
|
24
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
type Color = "accent" | "orange" | "dark";
|
|
3
|
+
type Props = {
|
|
4
|
+
style?: string;
|
|
5
|
+
color?: Color;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
export default function Badge({ style, color, children }: Props): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn, tv } from "@bouko/style";
|
|
3
|
+
export default function Badge({ style, color, children }) {
|
|
4
|
+
return (_jsx("span", { className: cn(styles({ color }), style), children: children }));
|
|
5
|
+
}
|
|
6
|
+
const styles = tv({
|
|
7
|
+
base: "w-min px-3 py-1 border rounded-full text-xs font-semibold",
|
|
8
|
+
defaultVariants: { color: "accent" },
|
|
9
|
+
variants: {
|
|
10
|
+
color: {
|
|
11
|
+
accent: "bg-accent/20 border-accent-dark text-accent-dark",
|
|
12
|
+
orange: "bg-orange-400/20 border-orange-500 text-orange-500",
|
|
13
|
+
dark: "bg-slate-600/20 border-slate-700 text-slate-700"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type Field as FieldProps } from "./form";
|
|
2
|
+
type Props<T> = FieldProps<T> & {
|
|
3
|
+
rows?: number;
|
|
4
|
+
placeholder?: string;
|
|
5
|
+
};
|
|
6
|
+
export default function TextArea<T>({ id, style, label, required, note, update, ...props }: Props<T>): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import Field from "./field";
|
|
3
|
+
import { setField } from "./form";
|
|
4
|
+
export default function TextArea({ id, style, label, required = true, note, update, ...props }) {
|
|
5
|
+
return (_jsx(Field, { style: style, label: label, required: required, note: note, children: _jsx("textarea", { className: styles.textarea, onChange: ({ target: { value } }) => setField(update, id, value), ...props }) }));
|
|
6
|
+
}
|
|
7
|
+
const styles = {
|
|
8
|
+
container: "flex flex-col gap-1 overflow-hidden",
|
|
9
|
+
label: "text-xs text-slate-600",
|
|
10
|
+
textarea: "px-3 py-2 bg-input border border-outline focus:border-outline-light rounded text-sm resize-none",
|
|
11
|
+
note: "mt-1 text-xs text-slate-500"
|
|
12
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef } from "react";
|
|
3
|
+
import { cn } from "@bouko/style";
|
|
4
|
+
import PaperClip from "../../assets/icons/paperclip.svg";
|
|
5
|
+
export default function FileUploader({ style, accept = [], update }) {
|
|
6
|
+
const ref = useRef(null);
|
|
7
|
+
const upload = () => ref.current?.click();
|
|
8
|
+
const save = (e) => update((e.target.files ?? [])[0]);
|
|
9
|
+
return (_jsxs("div", { className: cn(styles.container, style), onClick: upload, children: [_jsxs("span", { className: styles.title, children: [_jsx(PaperClip, {}), "Drag and drop a file, or"] }), _jsx("span", { className: styles.subtitle, children: "browse" }), _jsx("input", { type: "file", className: "hidden", onChange: save, accept: accept.join(","), multiple: false, ref: ref })] }));
|
|
10
|
+
}
|
|
11
|
+
const styles = {
|
|
12
|
+
container: "flex flex-col justify-center items-center gap-1 w-full py-3 hover:bg-background-light/40 border-2 border-dashed border-border hover:border-border-light rounded overflow-hidden duration-200 cursor-pointer",
|
|
13
|
+
title: "flex gap-2 items-center font-semibold text-sm",
|
|
14
|
+
subtitle: "text-xs text-primary-darker"
|
|
15
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment } from "react";
|
|
3
|
+
const delimiterConfig = [
|
|
4
|
+
{ delimiter: "**", render: (text, key) => _jsx("span", { className: "font-bold", children: text }, key) },
|
|
5
|
+
{ delimiter: "~~", render: (text, key) => _jsx("span", { className: "font-semibold", children: text }, key) },
|
|
6
|
+
{ delimiter: "^^", render: (text, key) => _jsx("span", { className: "text-accent", children: text }, key) },
|
|
7
|
+
];
|
|
8
|
+
const escapeRegex = (str) => str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
9
|
+
const delimiters = delimiterConfig.map(({ delimiter }) => escapeRegex(delimiter) + ".*?" + escapeRegex(delimiter)).join("|");
|
|
10
|
+
const WRAPPED_TEXT_REGEX = new RegExp(`(${delimiters})`, "g");
|
|
11
|
+
const isWrapped = (x, wrapper) => x.startsWith(wrapper) && x.endsWith(wrapper);
|
|
12
|
+
export const rn = (...elements) => (_jsx(Fragment, { children: elements.map((x, i) => (_jsx(Fragment, { children: x }, i))) }));
|
|
13
|
+
export const formatText = (text) => {
|
|
14
|
+
const parts = text
|
|
15
|
+
.split(WRAPPED_TEXT_REGEX)
|
|
16
|
+
.filter(Boolean);
|
|
17
|
+
const formatted = parts.map((x, i) => {
|
|
18
|
+
if (isWrapped(x, "**")) {
|
|
19
|
+
// Remove only the first and last occurrence of the delimiter
|
|
20
|
+
const content = x.slice(2, -2);
|
|
21
|
+
return _jsx("span", { className: "font-bold", children: content }, i);
|
|
22
|
+
}
|
|
23
|
+
else if (isWrapped(x, "~~")) {
|
|
24
|
+
const content = x.slice(2, -2);
|
|
25
|
+
return _jsx("span", { className: "font-semibold", children: content }, i);
|
|
26
|
+
}
|
|
27
|
+
else if (isWrapped(x, "^^")) {
|
|
28
|
+
const content = x.slice(2, -2);
|
|
29
|
+
return _jsx("span", { className: "text-accent", children: content }, i);
|
|
30
|
+
}
|
|
31
|
+
return x;
|
|
32
|
+
});
|
|
33
|
+
return _jsx("span", { children: formatted });
|
|
34
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export const getFileData = (files) => Promise.all(files.map((file) => {
|
|
2
|
+
return new Promise((resolve, reject) => {
|
|
3
|
+
const reader = new FileReader();
|
|
4
|
+
reader.onload = () => {
|
|
5
|
+
const base64 = reader.result?.toString().split(",")[1]; // Remove data prefix
|
|
6
|
+
resolve({
|
|
7
|
+
filename: file.name,
|
|
8
|
+
mimetype: file.type,
|
|
9
|
+
data: base64,
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
reader.onerror = reject;
|
|
13
|
+
reader.readAsDataURL(file);
|
|
14
|
+
});
|
|
15
|
+
}));
|
|
16
|
+
export const getEnv = (key) => {
|
|
17
|
+
const value = process.env[key];
|
|
18
|
+
if (!value)
|
|
19
|
+
throw new Error(`Missing required env: ${key}`);
|
|
20
|
+
return value;
|
|
21
|
+
};
|
|
22
|
+
export async function getBase64(image) {
|
|
23
|
+
if (image.startsWith("data:")) {
|
|
24
|
+
return image; // already a base64 data URL
|
|
25
|
+
}
|
|
26
|
+
const res = await fetch(image);
|
|
27
|
+
const blob = await res.blob();
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const reader = new FileReader();
|
|
30
|
+
reader.onloadend = () => {
|
|
31
|
+
if (typeof reader.result === "string") {
|
|
32
|
+
resolve(reader.result);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
reject(new Error("Failed to read image as base64"));
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
reader.onerror = reject;
|
|
39
|
+
reader.readAsDataURL(blob);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function useResizeObserver(element: HTMLElement | null | undefined, action: () => void): void;
|