@g4rcez/components 0.0.1 → 0.0.2
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/package.json +11 -2
- package/.idea/bigweld.iml +0 -12
- package/.idea/codeStyles/Project.xml +0 -72
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/.idea/inspectionProfiles/Project_Default.xml +0 -30
- package/.idea/jsLibraryMappings.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/prettier.xml +0 -7
- package/.idea/reason.xml +0 -6
- package/.idea/vcs.xml +0 -6
- package/.prettierrc.json +0 -13
- package/app/client-table.tsx +0 -35
- package/app/favicon.ico +0 -0
- package/app/layout.tsx +0 -39
- package/app/page.tsx +0 -72
- package/docs/README.md +0 -36
- package/docs/next.config.mjs +0 -4
- package/docs/package.json +0 -28
- package/docs/pnpm-lock.yaml +0 -1030
- package/docs/postcss.config.mjs +0 -8
- package/docs/public/next.svg +0 -1
- package/docs/public/vercel.svg +0 -1
- package/docs/src/app/favicon.ico +0 -0
- package/docs/src/app/globals.css +0 -33
- package/docs/src/app/layout.tsx +0 -22
- package/docs/src/app/page.tsx +0 -10
- package/docs/tailwind.config.ts +0 -15
- package/docs/tsconfig.json +0 -26
- package/next-env.d.ts +0 -5
- package/next.config.mjs +0 -4
- package/postcss.config.mjs +0 -8
- package/public/next.svg +0 -1
- package/public/vercel.svg +0 -1
- package/src/components/core/button.tsx +0 -91
- package/src/components/core/polymorph.tsx +0 -17
- package/src/components/display/card.tsx +0 -8
- package/src/components/floating/dropdown.tsx +0 -93
- package/src/components/floating/tooltip.tsx +0 -67
- package/src/components/form/autocomplete.tsx +0 -222
- package/src/components/form/file-upload.tsx +0 -129
- package/src/components/form/form.tsx +0 -28
- package/src/components/form/input-field.tsx +0 -105
- package/src/components/form/input.tsx +0 -73
- package/src/components/form/select.tsx +0 -58
- package/src/components/form/switch.tsx +0 -40
- package/src/components/index.ts +0 -14
- package/src/components/table/filter.tsx +0 -186
- package/src/components/table/group.tsx +0 -123
- package/src/components/table/index.tsx +0 -207
- package/src/components/table/metadata.tsx +0 -55
- package/src/components/table/sort.tsx +0 -141
- package/src/components/table/table-lib.ts +0 -130
- package/src/components/table/thead.tsx +0 -108
- package/src/hooks/use-form.ts +0 -155
- package/src/hooks/use-previous.ts +0 -9
- package/src/hooks/use-reactive.ts +0 -10
- package/src/index.css +0 -37
- package/src/index.ts +0 -6
- package/src/lib/dom.ts +0 -27
- package/src/lib/fns.ts +0 -23
- package/src/styles/dark.json +0 -66
- package/src/styles/design-tokens.ts +0 -57
- package/src/styles/light.json +0 -49
- package/src/types.ts +0 -11
- package/styles.config.ts +0 -42
- package/tailwind.config.ts +0 -11
- package/tsconfig.json +0 -55
- package/tsconfig.lib.json +0 -50
- package/tsconfig.lib.tsbuildinfo +0 -1
- package/tsconfig.tailwind.json +0 -32
- package/tsconfig.tsbuildinfo +0 -1
- package/vite.config.mts +0 -39
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { FileIcon, Trash2Icon, UploadIcon } from "lucide-react";
|
|
2
|
-
import prettyBytes from "pretty-bytes";
|
|
3
|
-
import React, { Fragment, useEffect, useState } from "react";
|
|
4
|
-
import { DropzoneProps, useDropzone } from "react-dropzone";
|
|
5
|
-
import { Override } from "sidekicker";
|
|
6
|
-
import { Button } from "~/components/core/button";
|
|
7
|
-
|
|
8
|
-
type Props = Override<React.ComponentProps<"input">, DropzoneProps> & {
|
|
9
|
-
onDeleteFile?: (file: File) => void;
|
|
10
|
-
files?: File[];
|
|
11
|
-
idle?: React.ReactElement;
|
|
12
|
-
onDrop?: (file: File[]) => void;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const mime = {
|
|
16
|
-
isImage: (file: File) => file.type.includes("image"),
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const FileViewer = (props: { file: File; onDeleteFile?: (file: File) => void }) => {
|
|
20
|
-
const [info, setInfo] = useState({ url: "", type: "", size: "" });
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
if (mime.isImage(props.file)) {
|
|
23
|
-
const url = URL.createObjectURL(props.file);
|
|
24
|
-
setInfo({ url, type: "img", size: prettyBytes(props.file.size) });
|
|
25
|
-
return () => {
|
|
26
|
-
URL.revokeObjectURL(url);
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
setInfo({ url: "", type: props.file.type, size: prettyBytes(props.file.size) });
|
|
30
|
-
}, [props.file]);
|
|
31
|
-
|
|
32
|
-
if (info.type === "img") {
|
|
33
|
-
return (
|
|
34
|
-
<div className="flex flex-row gap-jade-200 items-center justify-between w-full">
|
|
35
|
-
<header className="flex flex-row gap-jade-200 items-center">
|
|
36
|
-
<img src={info.url} className="size-jade-500 rounded-jade-xsmall" alt={`Miniatura do arquivo ${props.file.name}`} />
|
|
37
|
-
<div className="flex flex-col">
|
|
38
|
-
<span>{props.file.name}</span>
|
|
39
|
-
<span>{info.size}</span>
|
|
40
|
-
</div>
|
|
41
|
-
</header>
|
|
42
|
-
<Button
|
|
43
|
-
className="isolate"
|
|
44
|
-
type="button"
|
|
45
|
-
theme="raw"
|
|
46
|
-
onClick={(e) => {
|
|
47
|
-
e.stopPropagation();
|
|
48
|
-
props.onDeleteFile?.(props.file);
|
|
49
|
-
}}
|
|
50
|
-
>
|
|
51
|
-
<Trash2Icon />
|
|
52
|
-
</Button>
|
|
53
|
-
</div>
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
return (
|
|
57
|
-
<div className="flex flex-row gap-jade-200 items-center justify-between w-full">
|
|
58
|
-
<header className="flex flex-row gap-4 items-center">
|
|
59
|
-
<FileIcon size={48} />
|
|
60
|
-
<div className="flex flex-col text-left justify-start items-start">
|
|
61
|
-
<span>{props.file.name}</span>
|
|
62
|
-
<span>{info.size}</span>
|
|
63
|
-
</div>
|
|
64
|
-
</header>
|
|
65
|
-
<Button
|
|
66
|
-
className="isolate"
|
|
67
|
-
type="button"
|
|
68
|
-
theme="raw"
|
|
69
|
-
onClick={(e) => {
|
|
70
|
-
e.stopPropagation();
|
|
71
|
-
props.onDeleteFile?.(props.file);
|
|
72
|
-
}}
|
|
73
|
-
>
|
|
74
|
-
<Trash2Icon className="text-danger" />
|
|
75
|
-
</Button>
|
|
76
|
-
</div>
|
|
77
|
-
);
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const DefaultViewer = (props: { files: File[]; onDeleteFile?: (file: File) => void }) => {
|
|
81
|
-
return (
|
|
82
|
-
<ul className="w-full space-y-jade-200">
|
|
83
|
-
{props.files.map((file) => {
|
|
84
|
-
return <FileViewer onDeleteFile={props.onDeleteFile} key={file.name} file={file} />;
|
|
85
|
-
})}
|
|
86
|
-
</ul>
|
|
87
|
-
);
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const InteractiveArea = (props: { isDragActive: boolean; idle: React.ReactElement; files: File[]; onDeleteFile?: (file: File) => void }) => {
|
|
91
|
-
if (props.isDragActive) {
|
|
92
|
-
return <p>Solte os arquivos selecionados</p>;
|
|
93
|
-
}
|
|
94
|
-
if (props.files.length > 0) {
|
|
95
|
-
return <DefaultViewer onDeleteFile={props.onDeleteFile} files={props.files} />;
|
|
96
|
-
}
|
|
97
|
-
return <Fragment>{props.idle}</Fragment>;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const DefaultIdle = (
|
|
101
|
-
<div className="flex flex-col gap-4 justify-center items-center">
|
|
102
|
-
<UploadIcon size={64} />
|
|
103
|
-
<p>
|
|
104
|
-
You can drag your files or{" "}
|
|
105
|
-
<button className="text-primary underline" type="button">
|
|
106
|
-
drag to here
|
|
107
|
-
</button>
|
|
108
|
-
</p>
|
|
109
|
-
</div>
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
export const FileUpload = ({ idle = DefaultIdle, onDeleteFile, onDrop, ...props }: Props) => {
|
|
113
|
-
const [files, setFiles] = useState<File[]>([]);
|
|
114
|
-
const drop = (x: File[]) => {
|
|
115
|
-
onDrop?.(x);
|
|
116
|
-
setFiles(x);
|
|
117
|
-
};
|
|
118
|
-
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop: drop });
|
|
119
|
-
return (
|
|
120
|
-
<div
|
|
121
|
-
{...getRootProps()}
|
|
122
|
-
data-active={props.files?.length ? props.files.length > 0 : false}
|
|
123
|
-
className="flex text-foreground flex-col items-center justify-center border-2 rounded-lg p-6 border-card-border data-[active=true]:bg-card-background data-[active=true]:border-solid data-[active=false]:border-dashed"
|
|
124
|
-
>
|
|
125
|
-
<input {...getInputProps(props as any)} name={props.name} id={props.name} />
|
|
126
|
-
<InteractiveArea onDeleteFile={onDeleteFile} isDragActive={isDragActive} idle={idle} files={props.files ?? files} />
|
|
127
|
-
</div>
|
|
128
|
-
);
|
|
129
|
-
};
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import React from "react";
|
|
3
|
-
|
|
4
|
-
const inputFields = ["INPUT", "SELECT"];
|
|
5
|
-
|
|
6
|
-
export const formReset = (form?: HTMLFormElement | null) => {
|
|
7
|
-
if (!form) return;
|
|
8
|
-
const elements = Array.from(form.elements);
|
|
9
|
-
elements.forEach((field) => {
|
|
10
|
-
if (!inputFields.includes(field.tagName)) return;
|
|
11
|
-
if (field.tagName === "INPUT") {
|
|
12
|
-
(field as HTMLInputElement).value = (field as HTMLInputElement).defaultValue;
|
|
13
|
-
}
|
|
14
|
-
if (field.tagName === "SELECT") {
|
|
15
|
-
(field as HTMLSelectElement).value = "";
|
|
16
|
-
}
|
|
17
|
-
field.setAttribute("data-initialized", "false");
|
|
18
|
-
});
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export const Form = (props: React.ComponentProps<"form">) => {
|
|
22
|
-
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
|
23
|
-
e.persist();
|
|
24
|
-
e.preventDefault();
|
|
25
|
-
props.onSubmit?.(e);
|
|
26
|
-
};
|
|
27
|
-
return <form {...props} onSubmit={onSubmit} />;
|
|
28
|
-
};
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { CheckCircle, XCircle } from "lucide-react";
|
|
3
|
-
import React, { Fragment, PropsWithChildren } from "react";
|
|
4
|
-
import { PolymorphicProps } from "~/components/core/polymorph";
|
|
5
|
-
import { css } from "~/lib/dom";
|
|
6
|
-
import { Label } from "~/types";
|
|
7
|
-
|
|
8
|
-
export type FeedbackProps = React.PropsWithChildren<
|
|
9
|
-
Partial<{
|
|
10
|
-
title: string | React.ReactElement | React.ReactNode;
|
|
11
|
-
hideLeft?: boolean;
|
|
12
|
-
className?: string;
|
|
13
|
-
placeholder: string;
|
|
14
|
-
reportStatus: boolean;
|
|
15
|
-
}>
|
|
16
|
-
>;
|
|
17
|
-
export const InputFeedback = ({ reportStatus, hideLeft = false, className, children, title }: FeedbackProps) => (
|
|
18
|
-
<div className={css("w-full justify-between", hideLeft && children === null ? "hidden" : "flex", className)}>
|
|
19
|
-
{hideLeft ? null : (
|
|
20
|
-
<span className="flex items-center gap-1 group-hover:text-primary group-focus-within:text-primary transition-colors group-error:text-danger">
|
|
21
|
-
{title}
|
|
22
|
-
{reportStatus ? (
|
|
23
|
-
<span className="flex aspect-square h-4 w-4 items-center justify-center">
|
|
24
|
-
<CheckCircle
|
|
25
|
-
className="hidden aspect-square h-3 w-3 opacity-0 transition-opacity group-assert:block group-assert:text-success group-assert:opacity-100"
|
|
26
|
-
aria-hidden="true"
|
|
27
|
-
size={16}
|
|
28
|
-
strokeWidth={1}
|
|
29
|
-
absoluteStrokeWidth
|
|
30
|
-
/>
|
|
31
|
-
<XCircle
|
|
32
|
-
className="hidden aspect-square h-3 w-3 opacity-0 transition-opacity group-error:block group-error:opacity-100"
|
|
33
|
-
aria-hidden="true"
|
|
34
|
-
size={16}
|
|
35
|
-
strokeWidth={1}
|
|
36
|
-
absoluteStrokeWidth
|
|
37
|
-
/>
|
|
38
|
-
</span>
|
|
39
|
-
) : null}
|
|
40
|
-
</span>
|
|
41
|
-
)}
|
|
42
|
-
{children}
|
|
43
|
-
</div>
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
export type InputFieldProps<T extends "input" | "select"> = PolymorphicProps<
|
|
47
|
-
Partial<{
|
|
48
|
-
error?: string;
|
|
49
|
-
hideLeft: boolean;
|
|
50
|
-
container: string;
|
|
51
|
-
left: Label;
|
|
52
|
-
optionalText: string;
|
|
53
|
-
right: Label;
|
|
54
|
-
rightLabel: Label;
|
|
55
|
-
id: string;
|
|
56
|
-
name: string;
|
|
57
|
-
placeholder: string;
|
|
58
|
-
}>,
|
|
59
|
-
T
|
|
60
|
-
>;
|
|
61
|
-
|
|
62
|
-
export const InputField = <T extends "input" | "select">({
|
|
63
|
-
optionalText = "Optional",
|
|
64
|
-
left,
|
|
65
|
-
rightLabel,
|
|
66
|
-
container,
|
|
67
|
-
right,
|
|
68
|
-
children,
|
|
69
|
-
error,
|
|
70
|
-
form,
|
|
71
|
-
id,
|
|
72
|
-
name,
|
|
73
|
-
title,
|
|
74
|
-
placeholder,
|
|
75
|
-
hideLeft,
|
|
76
|
-
required,
|
|
77
|
-
}: PropsWithChildren<InputFieldProps<T>>) => {
|
|
78
|
-
const ID = id ?? name;
|
|
79
|
-
return (
|
|
80
|
-
<fieldset data-error={!!error} form={form} className={css("group inline-block w-full", container)}>
|
|
81
|
-
<label
|
|
82
|
-
form={form}
|
|
83
|
-
htmlFor={ID}
|
|
84
|
-
className="inline-flex w-full cursor-text flex-row flex-wrap justify-between gap-1 text-sm transition-colors empty:hidden group-error:text-danger group-hover:border-primary"
|
|
85
|
-
>
|
|
86
|
-
{!hideLeft && !rightLabel ? (
|
|
87
|
-
<InputFeedback hideLeft={hideLeft} reportStatus title={title} placeholder={placeholder}>
|
|
88
|
-
{optionalText || rightLabel ? (
|
|
89
|
-
<Fragment>
|
|
90
|
-
{!required ? <span className="text-opacity-70">{optionalText}</span> : null}
|
|
91
|
-
{rightLabel ? <Fragment>{rightLabel}</Fragment> : null}
|
|
92
|
-
</Fragment>
|
|
93
|
-
) : null}
|
|
94
|
-
</InputFeedback>
|
|
95
|
-
) : null}
|
|
96
|
-
<div className="relative group flex w-full flex-row flex-nowrap items-center gap-x-2 gap-y-1 rounded-md border border-input-border bg-transparent transition-colors group-focus-within:border-primary group-hover:border-primary group-error:border-danger">
|
|
97
|
-
{left ? <span className="absolute left-0 flex flex-nowrap gap-1 whitespace-nowrap pl-2">{left}</span> : null}
|
|
98
|
-
{children}
|
|
99
|
-
{right ? <span className="absolute right-0 flex flex-nowrap gap-2 whitespace-nowrap pr-1">{right}</span> : null}
|
|
100
|
-
</div>
|
|
101
|
-
</label>
|
|
102
|
-
<p className="mt-1 text-xs group-error:block group-error:text-danger">{error}</p>
|
|
103
|
-
</fieldset>
|
|
104
|
-
);
|
|
105
|
-
};
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import React, { forwardRef, useEffect, useRef } from "react";
|
|
3
|
-
import MaskInput, { TheMaskProps } from "the-mask-input";
|
|
4
|
-
import { FeedbackProps, InputField, InputFieldProps } from "~/components/form/input-field";
|
|
5
|
-
import { css, mergeRefs } from "~/lib/dom";
|
|
6
|
-
import { Override } from "~/types";
|
|
7
|
-
|
|
8
|
-
export type InputProps = Override<
|
|
9
|
-
InputFieldProps<"input">,
|
|
10
|
-
TheMaskProps &
|
|
11
|
-
FeedbackProps & {
|
|
12
|
-
next?: string;
|
|
13
|
-
}
|
|
14
|
-
>;
|
|
15
|
-
|
|
16
|
-
export const Input: React.FC<InputProps> = forwardRef<HTMLInputElement, InputProps>(
|
|
17
|
-
({ type = "text", container, next, rightLabel, optionalText, hideLeft = false, right, left, ...props }: InputProps, ref): any => {
|
|
18
|
-
const id = props.id ?? props.name;
|
|
19
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
if (inputRef.current === null) return;
|
|
23
|
-
const input = inputRef.current;
|
|
24
|
-
const focus = () => input.setAttribute("data-initialized", "true");
|
|
25
|
-
const goNextInputImpl = (e: Event) => {
|
|
26
|
-
const event = e as KeyboardEvent;
|
|
27
|
-
if (event.key === "Enter" && input.enterKeyHint === "next") {
|
|
28
|
-
const focusNext = input.getAttribute("data-next");
|
|
29
|
-
if (focusNext) {
|
|
30
|
-
const el = document.getElementById(focusNext);
|
|
31
|
-
if (el) {
|
|
32
|
-
el.focus();
|
|
33
|
-
return void event.preventDefault();
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
input.addEventListener("keydown", goNextInputImpl);
|
|
39
|
-
input.addEventListener("focus", focus);
|
|
40
|
-
return () => {
|
|
41
|
-
input.removeEventListener("keydown", goNextInputImpl);
|
|
42
|
-
input.removeEventListener("focus", focus);
|
|
43
|
-
};
|
|
44
|
-
}, []);
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
<InputField<"input">
|
|
48
|
-
{...(props as any)}
|
|
49
|
-
right={right}
|
|
50
|
-
left={left}
|
|
51
|
-
hideLeft={hideLeft}
|
|
52
|
-
rightLabel={rightLabel}
|
|
53
|
-
optionalText={optionalText}
|
|
54
|
-
container={css("group inline-block w-full", container)}
|
|
55
|
-
>
|
|
56
|
-
<MaskInput
|
|
57
|
-
{...props}
|
|
58
|
-
type={type}
|
|
59
|
-
data-next={next}
|
|
60
|
-
ref={mergeRefs(ref, inputRef)}
|
|
61
|
-
id={id}
|
|
62
|
-
name={id}
|
|
63
|
-
className={css(
|
|
64
|
-
"input text-foreground group h-10 w-full flex-1 rounded-md bg-transparent p-2 placeholder-input-mask outline-none transition-colors group-error:text-danger group-error:placeholder-input-mask-error",
|
|
65
|
-
!!right ? "pe-4" : "",
|
|
66
|
-
!!left ? "ps-4" : "",
|
|
67
|
-
props.className
|
|
68
|
-
)}
|
|
69
|
-
/>
|
|
70
|
-
</InputField>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
) as any;
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { ChevronDown } from "lucide-react";
|
|
3
|
-
import React, { forwardRef, useEffect, useRef } from "react";
|
|
4
|
-
import { InputField, InputFieldProps } from "~/components/form/input-field";
|
|
5
|
-
import { css, mergeRefs } from "~/lib/dom";
|
|
6
|
-
import { Override } from "~/types";
|
|
7
|
-
|
|
8
|
-
export type OptionProps = Override<React.ComponentProps<"option">, { value: string }>;
|
|
9
|
-
|
|
10
|
-
export type SelectProps = Override<InputFieldProps<"select">, { options: OptionProps[] }>;
|
|
11
|
-
|
|
12
|
-
export const Select = forwardRef<HTMLSelectElement, SelectProps>(({ container, required = true, options, ...props }: SelectProps, ref) => {
|
|
13
|
-
const inputRef = useRef<HTMLSelectElement>(null);
|
|
14
|
-
const id = props.id ?? props.name;
|
|
15
|
-
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
if (inputRef.current === null) return;
|
|
18
|
-
const input = inputRef.current;
|
|
19
|
-
const focus = () => input.setAttribute("data-initialized", "true");
|
|
20
|
-
const change = () => input.setAttribute("data-selected", "true");
|
|
21
|
-
input.addEventListener("focus", focus);
|
|
22
|
-
input.addEventListener("change", change);
|
|
23
|
-
return () => {
|
|
24
|
-
input.removeEventListener("focus", focus);
|
|
25
|
-
input.removeEventListener("change", change);
|
|
26
|
-
};
|
|
27
|
-
}, []);
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<InputField<"select">
|
|
31
|
-
{...(props as any)}
|
|
32
|
-
required={required}
|
|
33
|
-
container={css("group inline-block w-full", container)}
|
|
34
|
-
right={<ChevronDown size={20} />}
|
|
35
|
-
>
|
|
36
|
-
<select
|
|
37
|
-
{...props}
|
|
38
|
-
ref={mergeRefs(ref, inputRef)}
|
|
39
|
-
id={id}
|
|
40
|
-
name={id}
|
|
41
|
-
required={required}
|
|
42
|
-
data-selected={!!props.value || false}
|
|
43
|
-
className={css(
|
|
44
|
-
"input bg-transparent text-foreground select group h-10 w-full flex-1 rounded-md p-2 placeholder-input-placeholder outline-none transition-colors group-error:text-danger group-error:placeholder-input-mask-error",
|
|
45
|
-
"data-[selected=false]:text-input-placeholder",
|
|
46
|
-
props.className
|
|
47
|
-
)}
|
|
48
|
-
>
|
|
49
|
-
<option value="" hidden disabled>
|
|
50
|
-
{props.placeholder}
|
|
51
|
-
</option>
|
|
52
|
-
{options.map((option) => (
|
|
53
|
-
<option key={`${id}-select-option-${option.value}`} {...option} children={option.label ?? option.value} />
|
|
54
|
-
))}
|
|
55
|
-
</select>
|
|
56
|
-
</InputField>
|
|
57
|
-
);
|
|
58
|
-
});
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import React, { useId, useState } from "react";
|
|
2
|
-
|
|
3
|
-
type SwitchProps = Omit<React.ComponentProps<"input">, "onChange"> & { onCheck?: (nextValue: boolean) => void };
|
|
4
|
-
|
|
5
|
-
export const Switch = ({ children, ...props }: SwitchProps) => {
|
|
6
|
-
const id = useId();
|
|
7
|
-
const [innerChecked, setInnerChecked] = useState(false);
|
|
8
|
-
const checked = props.checked ?? innerChecked;
|
|
9
|
-
|
|
10
|
-
const onCheck = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
11
|
-
const button = e.target as HTMLButtonElement;
|
|
12
|
-
const checked = !(button.dataset.checked === "true");
|
|
13
|
-
setInnerChecked(checked);
|
|
14
|
-
props?.onCheck?.(checked);
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
return (
|
|
18
|
-
<div className="flex items-center">
|
|
19
|
-
<input {...props} hidden type="checkbox" checked={checked} onChange={(e) => setInnerChecked(e.target.checked)} />
|
|
20
|
-
<button
|
|
21
|
-
type="button"
|
|
22
|
-
role="switch"
|
|
23
|
-
onClick={onCheck}
|
|
24
|
-
aria-checked={checked}
|
|
25
|
-
data-checked={checked}
|
|
26
|
-
aria-labelledby={`${id}-label`}
|
|
27
|
-
className="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent data-[checked=false]:bg-input-switch-bg data-[checked=true]:bg-primary transition-colors ease-in-out focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
|
|
28
|
-
>
|
|
29
|
-
<span
|
|
30
|
-
aria-hidden="true"
|
|
31
|
-
data-checked={checked}
|
|
32
|
-
className="data-[checked=false]:bg-disabled data-[checked=true]:bg-input-switch pointer-events-none inline-block size-5 aspect-square data-[checked=false]translate-x-0 data-[checked=true]:translate-x-5 transform rounded-full shadow ring-0 transition ease-in-out"
|
|
33
|
-
/>
|
|
34
|
-
</button>
|
|
35
|
-
<span className="ml-3 text-sm" id={`${id}-label`}>
|
|
36
|
-
<span className="font-medium text-foreground">{children}</span>
|
|
37
|
-
</span>
|
|
38
|
-
</div>
|
|
39
|
-
);
|
|
40
|
-
};
|
package/src/components/index.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export * from "./form/autocomplete";
|
|
2
|
-
export * from "./form/input";
|
|
3
|
-
export * from "./form/select";
|
|
4
|
-
export * from "./form/file-upload";
|
|
5
|
-
export * from "./form/form";
|
|
6
|
-
export * from "./form/select";
|
|
7
|
-
export * from "./form/switch";
|
|
8
|
-
export * from "./form/input-field";
|
|
9
|
-
export * from "./core/button";
|
|
10
|
-
export * from "./core/polymorph";
|
|
11
|
-
export * from "./display/card";
|
|
12
|
-
export * from "./floating/dropdown";
|
|
13
|
-
export * from "./floating/tooltip";
|
|
14
|
-
export * from "./table/index";
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { Symbols } from "linq-arrays";
|
|
2
|
-
import { PlusIcon, SearchIcon, Trash2Icon } from "lucide-react";
|
|
3
|
-
import React, { Fragment } from "react";
|
|
4
|
-
import { Dropdown } from "~/components/floating/dropdown";
|
|
5
|
-
import { Input } from "~/components/form/input";
|
|
6
|
-
import { OptionProps, Select } from "~/components/form/select";
|
|
7
|
-
import { uuid } from "~/lib/fns";
|
|
8
|
-
import { Label } from "~/types";
|
|
9
|
-
import { Col, ColType, getLabel, TableConfiguration, valueFromType } from "./table-lib";
|
|
10
|
-
|
|
11
|
-
const operators = {
|
|
12
|
-
contains: { value: "contains", label: "Contains", symbol: "includes" },
|
|
13
|
-
is: { value: "is", label: "Is", symbol: "is" },
|
|
14
|
-
isNot: { value: "isNot", label: "Is not", symbol: "!==" },
|
|
15
|
-
notContains: { value: "notContains", label: "Does not contains", symbol: "notIncludes" },
|
|
16
|
-
lessThan: { value: "lessThan", label: "Less Than", symbol: "<=" },
|
|
17
|
-
greaterThan: { value: "greaterThan", label: "Greater than", symbol: ">=" },
|
|
18
|
-
startsWith: { value: "startsWith", label: "Starts with", symbol: "startsWith" },
|
|
19
|
-
endsWith: { value: "endsWith", label: "Ends with", symbol: "endsWith" },
|
|
20
|
-
} satisfies Record<string, OptionProps & { symbol: Symbols }>;
|
|
21
|
-
|
|
22
|
-
type Operator = keyof typeof operators;
|
|
23
|
-
|
|
24
|
-
type Operators = (typeof operators)[Operator];
|
|
25
|
-
|
|
26
|
-
const operatorOptions: Partial<Record<ColType, OptionProps[]>> = {
|
|
27
|
-
[ColType.Text]: [operators.is, operators.isNot, operators.contains, operators.notContains, operators.startsWith, operators.endsWith],
|
|
28
|
-
[ColType.Boolean]: [operators.is, operators.isNot],
|
|
29
|
-
[ColType.Number]: [operators.is, operators.isNot, operators.greaterThan, operators.lessThan],
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
type FilterValue = string | number | string[] | boolean;
|
|
33
|
-
|
|
34
|
-
export type FilterConfig<T extends {} = {}> = {
|
|
35
|
-
id: string;
|
|
36
|
-
label: Label;
|
|
37
|
-
name: keyof T;
|
|
38
|
-
type: ColType;
|
|
39
|
-
operation: Operators;
|
|
40
|
-
value: FilterValue;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
type Props<T extends {}> = TableConfiguration<
|
|
44
|
-
T,
|
|
45
|
-
{
|
|
46
|
-
cols: Col<T>[];
|
|
47
|
-
filters: FilterConfig<T>[];
|
|
48
|
-
set: React.Dispatch<React.SetStateAction<FilterConfig<T>[]>>;
|
|
49
|
-
}
|
|
50
|
-
>;
|
|
51
|
-
|
|
52
|
-
export const createFilterFromCol = <T extends {}>(f: Col<T>, rest: Partial<FilterConfig<T>> = {}): FilterConfig<T> => {
|
|
53
|
-
const name = f.id;
|
|
54
|
-
const type = f.type ?? ColType.Text;
|
|
55
|
-
const operatorId = operatorOptions[type]?.[0]!.value as Operator;
|
|
56
|
-
const operation = operators[operatorId];
|
|
57
|
-
return { id: uuid(), operation, label: getLabel(f), name, type, value: "", ...rest };
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export const Filter = <T extends {}>(props: Props<T>) => {
|
|
61
|
-
const onAddFilter = () => {
|
|
62
|
-
const col = props.cols.at(0)!;
|
|
63
|
-
props.set((prev) => [...prev, createFilterFromCol(col)]);
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const onSelectProperty = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
67
|
-
const changedId = e.target.dataset.id || "";
|
|
68
|
-
const newId = e.target.value;
|
|
69
|
-
props.set((prev) =>
|
|
70
|
-
prev.map((x) => {
|
|
71
|
-
if (changedId !== x.id) return x;
|
|
72
|
-
const col = props.cols.find((x) => newId === x.id)!;
|
|
73
|
-
return createFilterFromCol(col, { value: "" });
|
|
74
|
-
})
|
|
75
|
-
);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const onSelectOperation = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
79
|
-
const id = e.target.dataset.id || "";
|
|
80
|
-
const operator = e.target.value;
|
|
81
|
-
props.set((prev) => prev.map((x) => (x.id === id ? { ...x, operation: operators[operator as Operator] } : x)));
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const onDelete = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
85
|
-
const id = e.currentTarget.dataset.id || "";
|
|
86
|
-
console.log({ id, f: props.filters });
|
|
87
|
-
props.set((prev) => prev.filter((x) => x.id !== id));
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const onChangeValue = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
91
|
-
const id = e.target.dataset.id || "";
|
|
92
|
-
const value = valueFromType(e.target);
|
|
93
|
-
props.set((prev) => prev.map((x) => (x.id === id ? { ...x, value } : x)));
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
return (
|
|
97
|
-
<Fragment>
|
|
98
|
-
<Dropdown
|
|
99
|
-
arrow={false}
|
|
100
|
-
title="Filters"
|
|
101
|
-
trigger={
|
|
102
|
-
<span className="flex items-center gap-1 proportional-nums">
|
|
103
|
-
<SearchIcon size={14} />
|
|
104
|
-
Filtros {props.filters.length === 0 ? "" : ` (${props.filters.length})`}
|
|
105
|
-
</span>
|
|
106
|
-
}
|
|
107
|
-
>
|
|
108
|
-
<ul className="mt-4 space-y-2">
|
|
109
|
-
{props.filters.map((filter) => {
|
|
110
|
-
const operators = operatorOptions[filter.type]!;
|
|
111
|
-
return (
|
|
112
|
-
<li key={`filter-select-${filter.id}`} className="flex flex-nowrap gap-3">
|
|
113
|
-
<Select
|
|
114
|
-
title="Filtro"
|
|
115
|
-
options={props.options}
|
|
116
|
-
placeholder="Seleciona um campo..."
|
|
117
|
-
value={filter.name as string}
|
|
118
|
-
data-id={filter.id}
|
|
119
|
-
onChange={onSelectProperty}
|
|
120
|
-
/>
|
|
121
|
-
<Select
|
|
122
|
-
title="Tipo do filtro"
|
|
123
|
-
data-id={filter.id}
|
|
124
|
-
onChange={onSelectOperation}
|
|
125
|
-
value={filter.operation.value}
|
|
126
|
-
options={operators}
|
|
127
|
-
placeholder="Operação..."
|
|
128
|
-
/>
|
|
129
|
-
<Input
|
|
130
|
-
data-id={filter.id}
|
|
131
|
-
onChange={onChangeValue}
|
|
132
|
-
placeholder="Buscar por..."
|
|
133
|
-
title="Valor do filtro"
|
|
134
|
-
type={filter.type as any}
|
|
135
|
-
value={filter.value as string}
|
|
136
|
-
/>
|
|
137
|
-
<div className="flex items-center justify-center mt-5">
|
|
138
|
-
<button data-id={filter.id} type="button" onClick={onDelete}>
|
|
139
|
-
<Trash2Icon className="text-danger" size={16} />
|
|
140
|
-
</button>
|
|
141
|
-
</div>
|
|
142
|
-
</li>
|
|
143
|
-
);
|
|
144
|
-
})}
|
|
145
|
-
<li>
|
|
146
|
-
<button type="button" onClick={onAddFilter} className="text-primary flex items-center gap-1">
|
|
147
|
-
<PlusIcon size={14} /> Adicionar novo filtro
|
|
148
|
-
</button>
|
|
149
|
-
</li>
|
|
150
|
-
</ul>
|
|
151
|
-
</Dropdown>
|
|
152
|
-
</Fragment>
|
|
153
|
-
);
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
type ColumnHeaderFilterProps<T extends {}> = {
|
|
157
|
-
filter: FilterConfig<T>;
|
|
158
|
-
set: React.Dispatch<React.SetStateAction<FilterConfig<T>[]>>;
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
export const ColumnHeaderFilter = <T extends {}>({ filter, set }: ColumnHeaderFilterProps<T>) => {
|
|
162
|
-
const onSelectOperation = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
163
|
-
const operator = e.target.value;
|
|
164
|
-
const id = e.target.dataset.id || "";
|
|
165
|
-
set((prev) => prev.map((x) => (x.id === id ? { ...x, operation: operators[operator as Operator] } : x)));
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const onChangeValue = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
169
|
-
const id = e.target.dataset.id || "";
|
|
170
|
-
const value = valueFromType(e.target);
|
|
171
|
-
set((prev) => prev.map((x) => (x.id === id ? { ...x, value } : x)));
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
return (
|
|
175
|
-
<div className="flex flex-nowrap items-center gap-4 py-2">
|
|
176
|
-
<Select onChange={onSelectOperation} value={filter.operation.value} options={operatorOptions[filter.type]!} placeholder="Operation..." />
|
|
177
|
-
<Input
|
|
178
|
-
type={filter.type as any}
|
|
179
|
-
data-id={filter.id}
|
|
180
|
-
onChange={onChangeValue}
|
|
181
|
-
placeholder="Looking for..."
|
|
182
|
-
value={filter.value as string}
|
|
183
|
-
/>
|
|
184
|
-
</div>
|
|
185
|
-
);
|
|
186
|
-
};
|