Skip to content

Commit e394888

Browse files
committed
directory fixes for components
1 parent af5c475 commit e394888

File tree

16 files changed

+461
-4
lines changed

16 files changed

+461
-4
lines changed
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { createDynamicPrimitive } from "$/components/primitives";
2+
import { cva, cx } from "$/styles/css";
3+
import { RecipeVariantProps } from "$/styles/types";
4+
5+
const Component = createDynamicPrimitive<"a", RecipeVariantProps<typeof cn.root>>(
6+
({ href }) => (href ? "a" : "span"),
7+
(Element, { asChild, children, color, className, ...delegated }) => {
8+
return (
9+
<Element className={cx(cn.root(), className)} style={{ backgroundColor: color }} {...delegated}>
10+
{children}
11+
</Element>
12+
);
13+
}
14+
);
15+
16+
const cn = {
17+
root: cva({
18+
base: {
19+
fontWeight: "bold",
20+
backgroundColor: "container",
21+
color: "text",
22+
paddingY: 1,
23+
paddingX: 4,
24+
"&[href]": {
25+
backgroundColor: "link",
26+
transition: "background-color 0.25s",
27+
"&:not([disabled]):hover": {
28+
backgroundColor: "indigo.400",
29+
},
30+
},
31+
},
32+
}),
33+
};
34+
35+
export { Component };
+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { ECharts, EChartsOption, SetOptionOpts, getInstanceByDom, init } from "echarts";
2+
import { CSSProperties, useEffect, useRef } from "react";
3+
4+
interface Props {
5+
option: EChartsOption;
6+
style?: CSSProperties;
7+
settings?: SetOptionOpts;
8+
show?: boolean;
9+
loading?: boolean;
10+
theme?: string;
11+
}
12+
13+
const Component = ({ option, style, settings = { notMerge: true }, show = true, loading, theme }: Props): JSX.Element | null => {
14+
const ref = useRef<HTMLDivElement>(null);
15+
16+
useEffect(() => {
17+
let chart: ECharts | undefined;
18+
if (ref.current !== null) {
19+
chart = init(ref.current, theme);
20+
}
21+
22+
function resizeChart() {
23+
chart?.resize();
24+
}
25+
window.addEventListener("resize", resizeChart);
26+
27+
return () => {
28+
chart?.dispose();
29+
window.removeEventListener("resize", resizeChart);
30+
};
31+
}, [theme]);
32+
33+
useEffect(() => {
34+
if (ref.current !== null) {
35+
const chart = getInstanceByDom(ref.current);
36+
chart!.setOption(option, settings);
37+
}
38+
}, [option, settings, theme]);
39+
40+
useEffect(() => {
41+
if (ref.current !== null) {
42+
const chart = getInstanceByDom(ref.current);
43+
loading === true ? chart!.showLoading() : chart!.hideLoading();
44+
}
45+
}, [loading, theme]);
46+
47+
if (!show) return null;
48+
return <div ref={ref} style={{ width: "100%", height: "100%", ...style }} />;
49+
};
50+
51+
export { Component };
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { createPrimitive } from "$/components/primitives";
2+
import { cva, cx } from "$/styles/css";
3+
import { RecipeVariantProps } from "$/styles/types";
4+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5+
6+
const Component = createPrimitive<typeof FontAwesomeIcon, RecipeVariantProps<typeof cn.root>>(FontAwesomeIcon, (Element, { asChild, tabIndex, variant, className, ...delegated }) => {
7+
return <Element tabIndex={tabIndex ?? -1} className={cx(cn.root({ variant }), className)} {...delegated} />;
8+
});
9+
10+
const cn = {
11+
root: cva({
12+
base: {
13+
cursor: "pointer",
14+
userSelect: "none",
15+
"&:focus": {
16+
outline: "none",
17+
},
18+
},
19+
variants: {
20+
variant: {
21+
default: { color: "inherit" },
22+
primary: { color: "primary" },
23+
danger: { color: "danger" },
24+
link: {
25+
color: "link",
26+
"&:not([disabled]):hover": {
27+
color: "indigo.400",
28+
},
29+
},
30+
},
31+
},
32+
defaultVariants: { variant: "default" },
33+
}),
34+
};
35+
36+
export { Component };
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { cx } from "$/styles/css";
2+
import { interactable } from "$/styles/patterns";
3+
import { AsChildProps } from "$/types";
4+
import { Slot } from "@radix-ui/react-slot";
5+
import { useControllableState } from "@radix-ui/react-use-controllable-state";
6+
import { ComponentPropsWithoutRef, ElementRef, Fragment, forwardRef, useRef } from "react";
7+
8+
interface Props<T extends string | number> {
9+
value?: T;
10+
defaultValue?: T;
11+
onValueChange?: (value: T) => void;
12+
delay?: number;
13+
}
14+
15+
const Component = forwardRef<ElementRef<"input">, Omit<ComponentPropsWithoutRef<"input">, "value" | "defaultValue"> & AsChildProps & Props<string | number>>(({ asChild, defaultValue: defaultProp = "", value: prop, onValueChange: onChange, delay = 0, children, className, ...rest }, ref) => {
16+
const [value, setValue] = useControllableState<string | number>({ prop, defaultProp, onChange });
17+
const target = useRef<HTMLInputElement | null>(null);
18+
if (asChild) {
19+
return (
20+
<Fragment>
21+
<Slot ref={ref} onClick={() => target.current?.click()} className={className}>
22+
{children}
23+
</Slot>
24+
<input ref={target} tabIndex={-1} value={value} onChange={(e) => setValue(e.target.value)} style={{ display: "none" }} {...rest} />
25+
</Fragment>
26+
);
27+
}
28+
return <input ref={target} value={value} onChange={(e) => setValue(e.target.value)} onClick={() => target.current?.click()} className={cx(cn.root, className)} {...rest} />;
29+
});
30+
31+
const cn = {
32+
root: interactable({ cursor: "text" }),
33+
};
34+
35+
export { Component };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Icon } from "$/components/ui/atoms";
2+
import { Checkbox as Builder } from "$/components/ui/builders";
3+
import { css, cx } from "$/styles/css";
4+
import { center, interactable } from "$/styles/patterns";
5+
import { faCheck } from "@fortawesome/free-solid-svg-icons";
6+
7+
function Component({ className, ...rest }: Builder.CheckboxProps) {
8+
return (
9+
<Builder.Root className={cx(cn.root, className)} {...rest}>
10+
<Builder.Indicator asChild className={cn.indicator}>
11+
<Icon icon={faCheck} />
12+
</Builder.Indicator>
13+
</Builder.Root>
14+
);
15+
}
16+
17+
const cn = {
18+
root: css(
19+
interactable.raw(),
20+
center.raw({
21+
width: "1em",
22+
height: "1em",
23+
backgroundColor: "light",
24+
})
25+
),
26+
indicator: css({
27+
position: "relative",
28+
color: "container",
29+
fontSize: "sm",
30+
}),
31+
};
32+
33+
export { Component };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./variants";
2+
export { Input, Component as Wrapper } from "./wrapper";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { Select, TextArea } from "$/components/ui/atoms";
2+
import { Field } from "$/components/ui/builders";
3+
import { Tags } from "$/components/ui/molecules";
4+
import { DeepKeys, DeepValue } from "@tanstack/react-form";
5+
import { Children, PropsWithChildren, useEffect, useState } from "react";
6+
import { Props, Component as Wrapper } from "./wrapper";
7+
8+
const String = <T, K extends DeepKeys<T>>({ field, disabled, ...wrapper }: Props<T, K>) => {
9+
const [value, setValue] = useState(field?.state.value ?? "");
10+
useEffect(() => {
11+
field?.handleChange(() => value as DeepValue<T, K>);
12+
}, [field, value]);
13+
return (
14+
<Wrapper field={field} {...wrapper}>
15+
<Field.Input disabled={disabled} value={value} onChange={(e) => setValue(e.target.value)} />
16+
</Wrapper>
17+
);
18+
};
19+
const NumberField = <T, K extends DeepKeys<T>>({ field, disabled, ...wrapper }: Props<T, K>) => {
20+
const [value, setValue] = useState(field?.state.value ?? "");
21+
useEffect(() => {
22+
console.log();
23+
field?.handleChange(() => (value !== "" ? Number(value) : value) as DeepValue<T, K>);
24+
}, [field, value]);
25+
return (
26+
<Wrapper field={field} {...wrapper}>
27+
<Field.Input disabled={disabled} value={value} onChange={(e) => setValue(e.target.value)} />
28+
</Wrapper>
29+
);
30+
};
31+
const Text = <T, K extends DeepKeys<T>>({ field, disabled, ...wrapper }: Props<T, K>) => {
32+
const [value, setValue] = useState(field?.state.value ?? "");
33+
useEffect(() => {
34+
field?.handleChange(() => value as DeepValue<T, K>);
35+
}, [field, value]);
36+
return (
37+
<Wrapper field={field} {...wrapper}>
38+
<Field.Input asChild disabled={disabled}>
39+
<TextArea value={value} onChange={(e) => setValue(e.target.value)} />
40+
</Field.Input>
41+
</Wrapper>
42+
);
43+
};
44+
const Enum = <T, K extends DeepKeys<T>>({ field, disabled, children, ...wrapper }: PropsWithChildren<Props<T, K>>) => {
45+
const [value, setValue] = useState(field?.state.value ?? "");
46+
useEffect(() => {
47+
field?.handleChange(() => value as DeepValue<T, K>);
48+
}, [field, value]);
49+
return (
50+
<Wrapper field={field} {...wrapper}>
51+
<Field.Input asChild disabled={disabled}>
52+
<Select value={value} onChange={(e) => setValue(e.target.value as DeepValue<T, K>)}>
53+
{Children.map(children, (child) => (
54+
<option>{child}</option>
55+
))}
56+
</Select>
57+
</Field.Input>
58+
</Wrapper>
59+
);
60+
};
61+
const Array = <T, K extends DeepKeys<T>>({ field, disabled, ...wrapper }: Props<T, K>) => {
62+
const [value, setValue] = useState(field?.getValue());
63+
useEffect(() => {
64+
field?.handleChange(() => value as DeepValue<T, K>);
65+
}, [field, value]);
66+
return (
67+
<Wrapper field={field} {...wrapper}>
68+
<Field.Input asChild disabled={disabled}>
69+
<Tags value={value} onValueChange={(value) => setValue(value as DeepValue<T, K>)} />
70+
</Field.Input>
71+
</Wrapper>
72+
);
73+
};
74+
75+
export { Array, Enum, NumberField as Number, String, Text };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { Icon } from "$/components/ui/atoms";
2+
import { Field as Builder } from "$/components/ui/builders";
3+
import { Tooltip } from "$/components/ui/molecules";
4+
import { css, cva, cx } from "$/styles/css";
5+
import { hstack, scrollable, vstack } from "$/styles/patterns";
6+
import { faQuestionCircle, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
7+
import { DeepKeys, DeepValue, FieldApi } from "@tanstack/react-form";
8+
import { PropsWithChildren, ReactNode } from "react";
9+
10+
export interface Props<T, K extends DeepKeys<T>> {
11+
id?: string;
12+
field?: FieldApi<T, K, any, any, DeepValue<T, K>>;
13+
heading: ReactNode;
14+
subheading?: string;
15+
center?: boolean;
16+
disabled?: boolean;
17+
tooltip?: () => ReactNode;
18+
}
19+
20+
function Component<T, K extends DeepKeys<T>>({ id, field, heading, subheading, center, tooltip, children }: PropsWithChildren<Props<T, K>>) {
21+
return (
22+
<Builder.Root id={(field?.name as string) ?? id} className={cx(cn.root)}>
23+
<Builder.Heading className={cn.heading({ center })}>
24+
{heading}
25+
{subheading && <Builder.Subheading className={cn.subheading}>{subheading}</Builder.Subheading>}
26+
{tooltip && (
27+
<Builder.Help asChild>
28+
<Tooltip render={tooltip}>
29+
<Icon icon={faQuestionCircle} className={cx(cn.tooltip)} />
30+
</Tooltip>
31+
</Builder.Help>
32+
)}
33+
{field && field.state.meta.errors.length > 0 && (
34+
<Builder.Message asChild>
35+
<Tooltip render={() => field?.state.meta.errors.join(", ")}>
36+
<Icon icon={faTriangleExclamation} className={cx(cn.error)} />
37+
</Tooltip>
38+
</Builder.Message>
39+
)}
40+
</Builder.Heading>
41+
<Builder.Control className={cx(cn.input)}>{children}</Builder.Control>
42+
</Builder.Root>
43+
);
44+
}
45+
46+
const Input = Builder.Input;
47+
48+
const cn = {
49+
root: vstack({
50+
gap: 1,
51+
width: "inherit",
52+
"& > * > input, select, textarea": {
53+
width: "full",
54+
hideWebkit: true,
55+
},
56+
}),
57+
heading: cva({
58+
base: scrollable.raw({
59+
fontWeight: "bold",
60+
direction: "horizontal",
61+
display: "inline-flex",
62+
gap: 2,
63+
alignItems: "baseline",
64+
width: "full",
65+
hideWebkit: true,
66+
}),
67+
variants: {
68+
center: {
69+
true: { justifyContent: "center" },
70+
},
71+
},
72+
}),
73+
subheading: css({
74+
fontWeight: "normal",
75+
color: "subtext",
76+
}),
77+
tooltip: css({
78+
paddingRight: 0.5,
79+
color: "subtext",
80+
}),
81+
input: hstack({
82+
gap: 2,
83+
width: "full",
84+
}),
85+
error: css({
86+
color: "danger",
87+
}),
88+
};
89+
90+
export { Component, Input };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Icon } from "$/components/ui/atoms";
2+
import { css, cx } from "$/styles/css";
3+
import { faSpinner } from "@fortawesome/free-solid-svg-icons";
4+
import { ComponentProps } from "react";
5+
6+
function Component({ className }: ComponentProps<"i">) {
7+
return <Icon icon={faSpinner} className={cx(cn.root, className)} />;
8+
}
9+
10+
const cn = {
11+
root: css({
12+
animation: "spin 1s linear infinite",
13+
}),
14+
};
15+
16+
export { Component };

0 commit comments

Comments
 (0)