@bouko/react 1.7.5 → 1.7.7

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.
@@ -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="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,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 "../flex";
6
+ import CheckCircle from "./assets/check-circle.svg";
7
+ import Spinner from "./assets/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,8 @@
1
+ import type { SetState } from "./types";
2
+ export declare const useForm: <T>(init: T) => {
3
+ data: T;
4
+ update: import("react").Dispatch<import("react").SetStateAction<T>>;
5
+ clear: () => void;
6
+ };
7
+ export declare const setField: <T>(update: SetState<T>, id: string, value: unknown) => void;
8
+ export declare const parseData: <T extends Record<string, unknown>>(data: T) => T;
@@ -0,0 +1,27 @@
1
+ import { useState, useEffect, useMemo } from "react";
2
+ export const useForm = (init) => {
3
+ const initial = useMemo(() => init, [JSON.stringify(init)]); // deep compare
4
+ const [data, setData] = useState(initial);
5
+ useEffect(() => {
6
+ setData(initial);
7
+ }, [initial]);
8
+ return {
9
+ data,
10
+ update: setData,
11
+ clear: () => setData(initial)
12
+ };
13
+ };
14
+ export const setField = (update, id, value) => update(prev => ({
15
+ ...prev,
16
+ [id]: value
17
+ }));
18
+ export const parseData = (data) => {
19
+ const copy = JSON.parse(JSON.stringify(data));
20
+ for (const [key, value] of Object.entries(data)) {
21
+ if (key === "created_at")
22
+ copy[key] = new Date(value);
23
+ else if (key === "timestamp")
24
+ copy[key] = new Date(value.replaceAll("'", ""));
25
+ }
26
+ return copy;
27
+ };
@@ -0,0 +1,24 @@
1
+ import type { ZodTypeAny } from "zod";
2
+ import type { Field, Option, FormSection } from "./types";
3
+ export * from "./functions";
4
+ export * from "./types";
5
+ type FormBuilderField<T> = (Omit<Field<T>, "value" | "update"> & {
6
+ element: string;
7
+ rows?: number;
8
+ placeholder?: string;
9
+ options?: Option[];
10
+ })[][];
11
+ type Props<T> = FormSection<T> & {
12
+ styles?: {
13
+ submit?: string;
14
+ cancel?: string;
15
+ };
16
+ fields: FormBuilderField<T>;
17
+ validator: ZodTypeAny;
18
+ submit: {
19
+ label?: string;
20
+ icon?: React.ReactNode;
21
+ action: (data: T) => void;
22
+ };
23
+ };
24
+ export declare function FormBuilder<T>({ fields, validator, data, styles, update, submit, clear }: Props<T>): import("react/jsx-runtime").JSX.Element;
@@ -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 "../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: "flex flex-col w-full gap-4", children: [fields.map((row, i) => (_jsx(RowBox, { style: "w-full gap-5 overflow-hidden", children: row.map(({ element, id, rows, label, placeholder, 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, 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, children: [_jsx(Spinner, { className: isLoading ? "animate-spin" : "hidden" }), _jsx("div", { className: isLoading ? "hidden" : "", children: submit.icon || _jsx(CheckCircle, {}) }), submit.label || "Submit"] }), _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,28 @@
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
+ note?: ReactNode;
16
+ };
17
+ export type Option = {
18
+ id: string;
19
+ label?: string;
20
+ active?: boolean;
21
+ select?: () => void;
22
+ };
23
+ export type OptionField<T> = Field<T> & {
24
+ options: Option[];
25
+ };
26
+ export type Form<T> = {
27
+ data: T;
28
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,3 +1,4 @@
1
1
  export { default as Heading, HeadingProps } from "./layout/heading";
2
2
  export { default as Separator } from "./layout/separator";
3
3
  export * from "./list";
4
+ export * from "./form";
@@ -1,3 +1,4 @@
1
1
  export { default as Heading } from "./layout/heading";
2
2
  export { default as Separator } from "./layout/separator";
3
3
  export * from "./list";
4
+ export * from "./form";
@@ -1,2 +1,2 @@
1
- import { type OptionField } from "@bouko/form";
1
+ import { type OptionField } from "./form";
2
2
  export default function MultipleChoice<T>({ id, label, options, style, value, update, required }: OptionField<T>): import("react/jsx-runtime").JSX.Element;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import Field from "./field";
3
- import { setField } from "@bouko/form";
3
+ import { setField } from "./form";
4
4
  import { cn } from "@bouko/style";
5
5
  export default function MultipleChoice({ id, label, options, style, value, update, required = true }) {
6
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))) }) }));
@@ -1,4 +1,4 @@
1
- import { type OptionField } from "@bouko/form";
1
+ import { type OptionField } from "./form";
2
2
  type Props<T> = OptionField<T> & {
3
3
  placeholder?: string;
4
4
  };
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
4
  import { AnimatePresence, motion } from "framer-motion";
5
5
  import Chevron from "../assets/icons/chevron.svg";
6
- import { setField } from "@bouko/form";
6
+ import { setField } from "./form";
7
7
  import { cn } from "@bouko/style";
8
8
  export default function Select({ id, style, label, required = true, value, options, update, note }) {
9
9
  const [isOpen, setOpen] = useState(false);
@@ -1,21 +1,3 @@
1
- import type { Dispatch, SetStateAction } from "react";
2
- export type Field = {
3
- label?: string;
4
- style?: string;
5
- value?: string;
6
- update: Dispatch<SetStateAction<string>>;
7
- required?: boolean;
8
- note?: string;
9
- };
10
- export type Option = {
11
- id: string;
12
- label?: string;
13
- active?: boolean;
14
- select?: () => void;
15
- };
16
- export type OptionField = Field & {
17
- options: Option[];
18
- };
19
1
  export type Attachment = {
20
2
  filename: string;
21
3
  mimetype: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
 
3
3
  "name": "@bouko/react",
4
- "version": "1.7.5",
4
+ "version": "1.7.7",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "license": "MIT",
@@ -1,6 +0,0 @@
1
- type Props = {
2
- placeholder?: string;
3
- action: (query: string) => void;
4
- };
5
- export default function SearchBar({ placeholder, action }: Props): import("react/jsx-runtime").JSX.Element;
6
- export {};
@@ -1,12 +0,0 @@
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
- };