@meta-1/design 0.0.159
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/README.md +412 -0
- package/package.json +138 -0
- package/src/assets/icons/empty.svg +1 -0
- package/src/assets/icons/spin.svg +1 -0
- package/src/assets/locales/en-us.ts +74 -0
- package/src/assets/locales/zh-cn.ts +74 -0
- package/src/assets/locales/zh-tw.ts +74 -0
- package/src/assets/style/theme.css +173 -0
- package/src/components/icons/Empty.tsx +18 -0
- package/src/components/icons/Spin.tsx +16 -0
- package/src/components/icons/index.ts +2 -0
- package/src/components/ui/alert-dialog.tsx +111 -0
- package/src/components/ui/alert.tsx +49 -0
- package/src/components/ui/avatar.tsx +32 -0
- package/src/components/ui/badge.tsx +36 -0
- package/src/components/ui/breadcrumb.tsx +92 -0
- package/src/components/ui/button.tsx +52 -0
- package/src/components/ui/calendar.tsx +56 -0
- package/src/components/ui/card.tsx +56 -0
- package/src/components/ui/checkbox.tsx +28 -0
- package/src/components/ui/command.tsx +137 -0
- package/src/components/ui/dialog.tsx +127 -0
- package/src/components/ui/dropdown-menu.tsx +217 -0
- package/src/components/ui/form.tsx +138 -0
- package/src/components/ui/hover-card.tsx +36 -0
- package/src/components/ui/input-otp.tsx +66 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +21 -0
- package/src/components/ui/navigation-menu.tsx +142 -0
- package/src/components/ui/pagination.tsx +118 -0
- package/src/components/ui/popover.tsx +40 -0
- package/src/components/ui/progress.tsx +22 -0
- package/src/components/ui/radio-group.tsx +31 -0
- package/src/components/ui/resizable.tsx +46 -0
- package/src/components/ui/scroll-area.tsx +46 -0
- package/src/components/ui/select.tsx +158 -0
- package/src/components/ui/separator.tsx +26 -0
- package/src/components/ui/sheet.tsx +101 -0
- package/src/components/ui/skeleton.tsx +7 -0
- package/src/components/ui/sonner.tsx +23 -0
- package/src/components/ui/switch.tsx +26 -0
- package/src/components/ui/table.tsx +73 -0
- package/src/components/ui/tabs.tsx +40 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +46 -0
- package/src/components/uix/action/index.tsx +37 -0
- package/src/components/uix/alert/index.tsx +43 -0
- package/src/components/uix/alert-dialog/index.tsx +109 -0
- package/src/components/uix/avatar/index.tsx +25 -0
- package/src/components/uix/breadcrumbs/index.tsx +38 -0
- package/src/components/uix/broadcast-channel-context/index.tsx +28 -0
- package/src/components/uix/button/index.tsx +29 -0
- package/src/components/uix/card/index.tsx +32 -0
- package/src/components/uix/checkbox/index.tsx +79 -0
- package/src/components/uix/checkbox-group/index.tsx +60 -0
- package/src/components/uix/combo-select/index.tsx +364 -0
- package/src/components/uix/config-provider/index.tsx +31 -0
- package/src/components/uix/data-table/index.tsx +491 -0
- package/src/components/uix/data-table/style.css +40 -0
- package/src/components/uix/date-picker/index.tsx +88 -0
- package/src/components/uix/date-range-picker/index.tsx +71 -0
- package/src/components/uix/dialog/index.tsx +70 -0
- package/src/components/uix/divider/index.tsx +23 -0
- package/src/components/uix/dropdown/index.tsx +117 -0
- package/src/components/uix/empty/index.tsx +29 -0
- package/src/components/uix/filters/index.tsx +105 -0
- package/src/components/uix/form/index.tsx +274 -0
- package/src/components/uix/image/index.tsx +13 -0
- package/src/components/uix/loading/index.tsx +24 -0
- package/src/components/uix/message/index.tsx +21 -0
- package/src/components/uix/pagination/index.tsx +180 -0
- package/src/components/uix/radio-group/index.tsx +35 -0
- package/src/components/uix/result/index.tsx +45 -0
- package/src/components/uix/select/index.tsx +93 -0
- package/src/components/uix/space/index.tsx +24 -0
- package/src/components/uix/spin/index.tsx +12 -0
- package/src/components/uix/steps/index.tsx +67 -0
- package/src/components/uix/switch/index.tsx +33 -0
- package/src/components/uix/tooltip/index.tsx +29 -0
- package/src/components/uix/tree/index.tsx +39 -0
- package/src/components/uix/tree/style.css +75 -0
- package/src/components/uix/tree-select/index.tsx +137 -0
- package/src/components/uix/tree-table/action.tsx +24 -0
- package/src/components/uix/tree-table/config.ts +2 -0
- package/src/components/uix/tree-table/index.tsx +86 -0
- package/src/components/uix/tree-table/utils.tsx +63 -0
- package/src/components/uix/uploader/index.tsx +237 -0
- package/src/components/uix/uploader/type.ts +20 -0
- package/src/components/uix/uploader/utils.ts +41 -0
- package/src/components/uix/value-formatter/index.tsx +59 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/resize.ts +29 -0
- package/src/hooks/use.outside.ts +30 -0
- package/src/index.ts +159 -0
- package/src/lib/formatters.ts +13 -0
- package/src/lib/index.ts +4 -0
- package/src/lib/is.ts +6 -0
- package/src/lib/react-dom.ts +98 -0
- package/src/lib/utils.ts +39 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@meta-1/design/lib";
|
|
4
|
+
import { Alert as UIAlert, AlertDescription as UIAlertDescription, AlertTitle as UIAlertTitle } from "../../ui/alert";
|
|
5
|
+
|
|
6
|
+
export type AlertProps = ComponentProps<typeof UIAlert> & {
|
|
7
|
+
title?: ReactNode;
|
|
8
|
+
description?: ReactNode;
|
|
9
|
+
icon?: ReactNode;
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
titleClassName?: string;
|
|
12
|
+
descriptionClassName?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const Alert = (props: AlertProps) => {
|
|
16
|
+
const {
|
|
17
|
+
title,
|
|
18
|
+
description,
|
|
19
|
+
icon,
|
|
20
|
+
children: childrenProp,
|
|
21
|
+
titleClassName,
|
|
22
|
+
descriptionClassName,
|
|
23
|
+
variant,
|
|
24
|
+
...rest
|
|
25
|
+
} = props;
|
|
26
|
+
|
|
27
|
+
let className = props.className;
|
|
28
|
+
const children = childrenProp || description;
|
|
29
|
+
|
|
30
|
+
if (variant === "destructive") {
|
|
31
|
+
className = cn("border-destructive/20 bg-destructive/10 text-destructive", className);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<UIAlert {...rest} className={cn("flex gap-2", className)} variant={variant}>
|
|
36
|
+
{icon}
|
|
37
|
+
<div className="flex flex-1 flex-col gap-2">
|
|
38
|
+
{title && <UIAlertTitle className={titleClassName}>{title}</UIAlertTitle>}
|
|
39
|
+
{children && <UIAlertDescription className={descriptionClassName}>{children}</UIAlertDescription>}
|
|
40
|
+
</div>
|
|
41
|
+
</UIAlert>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { type FC, type ReactNode, useContext, useMemo, useState } from "react";
|
|
2
|
+
import get from "lodash/get";
|
|
3
|
+
|
|
4
|
+
import { render as ReactDOMRender } from "@meta-1/design/lib";
|
|
5
|
+
import {
|
|
6
|
+
AlertDialogContent,
|
|
7
|
+
AlertDialogDescription,
|
|
8
|
+
AlertDialogFooter,
|
|
9
|
+
AlertDialogHeader,
|
|
10
|
+
AlertDialogTitle,
|
|
11
|
+
AlertDialog as UIAlertDialog,
|
|
12
|
+
} from "../../ui/alert-dialog";
|
|
13
|
+
import { Button } from "../button";
|
|
14
|
+
import { UIXContext } from "../config-provider";
|
|
15
|
+
|
|
16
|
+
export interface ConfirmProps {
|
|
17
|
+
title: ReactNode;
|
|
18
|
+
description: ReactNode;
|
|
19
|
+
cancelText?: string;
|
|
20
|
+
okText?: string;
|
|
21
|
+
// biome-ignore lint/suspicious/noConfusingVoidType: <onOk>
|
|
22
|
+
onOk?: () => boolean | void | Promise<boolean | void>;
|
|
23
|
+
onCancel?: () => void;
|
|
24
|
+
open?: boolean;
|
|
25
|
+
confirmLoading?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const AlertDialog: FC<ConfirmProps> = (props) => {
|
|
29
|
+
const { description, title, onOk, onCancel } = props;
|
|
30
|
+
|
|
31
|
+
const config = useContext(UIXContext);
|
|
32
|
+
const okText = props.okText || get(config.locale, "Alert.okText");
|
|
33
|
+
const cancelText = props.cancelText || get(config.locale, "Alert.cancelText");
|
|
34
|
+
|
|
35
|
+
const [open, setOpen] = useState(props.open);
|
|
36
|
+
const [loading, setLoading] = useState(false);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<UIAlertDialog {...props} open={open}>
|
|
40
|
+
<AlertDialogContent>
|
|
41
|
+
<AlertDialogHeader>
|
|
42
|
+
<AlertDialogTitle>{title}</AlertDialogTitle>
|
|
43
|
+
<AlertDialogDescription>{description}</AlertDialogDescription>
|
|
44
|
+
</AlertDialogHeader>
|
|
45
|
+
<AlertDialogFooter>
|
|
46
|
+
<Button
|
|
47
|
+
onClick={() => {
|
|
48
|
+
setOpen(false);
|
|
49
|
+
onCancel?.();
|
|
50
|
+
}}
|
|
51
|
+
variant="outline"
|
|
52
|
+
>
|
|
53
|
+
{cancelText}
|
|
54
|
+
</Button>
|
|
55
|
+
<Button
|
|
56
|
+
loading={loading}
|
|
57
|
+
onClick={async () => {
|
|
58
|
+
setLoading(true);
|
|
59
|
+
const result = onOk?.();
|
|
60
|
+
let value: boolean | undefined;
|
|
61
|
+
if (result instanceof Promise) {
|
|
62
|
+
value = (await result) as boolean | undefined;
|
|
63
|
+
if (typeof value === "undefined") {
|
|
64
|
+
value = true;
|
|
65
|
+
}
|
|
66
|
+
} else if (typeof result === "undefined") {
|
|
67
|
+
value = true;
|
|
68
|
+
} else {
|
|
69
|
+
value = result;
|
|
70
|
+
}
|
|
71
|
+
setLoading(false);
|
|
72
|
+
if (value) {
|
|
73
|
+
setOpen(false);
|
|
74
|
+
}
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
{okText}
|
|
78
|
+
</Button>
|
|
79
|
+
</AlertDialogFooter>
|
|
80
|
+
</AlertDialogContent>
|
|
81
|
+
</UIAlertDialog>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const useAlert = () => {
|
|
86
|
+
return useMemo(() => {
|
|
87
|
+
return {
|
|
88
|
+
confirm: (props: ConfirmProps) => {
|
|
89
|
+
// biome-ignore lint/suspicious/noExplicitAny: <root>
|
|
90
|
+
let root: any;
|
|
91
|
+
const div = document.createElement("div");
|
|
92
|
+
document.body.appendChild(div);
|
|
93
|
+
const close = () => {
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
root = root?._unmount();
|
|
96
|
+
if (div.parentNode) {
|
|
97
|
+
div.parentNode.removeChild(div);
|
|
98
|
+
}
|
|
99
|
+
}, 200);
|
|
100
|
+
};
|
|
101
|
+
const onCancel = () => {
|
|
102
|
+
close();
|
|
103
|
+
props.onCancel?.();
|
|
104
|
+
};
|
|
105
|
+
root = ReactDOMRender(<AlertDialog {...props} onCancel={onCancel} open={true} />, div);
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}, []);
|
|
109
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
|
|
4
|
+
import { AvatarFallback, AvatarImage, Avatar as UIAvatar } from "@meta-1/design/components/ui/avatar";
|
|
5
|
+
|
|
6
|
+
export interface AvatarProps extends React.PropsWithChildren {
|
|
7
|
+
src?: string;
|
|
8
|
+
fallback?: string;
|
|
9
|
+
alt?: string;
|
|
10
|
+
className?: string;
|
|
11
|
+
fallbackClassName?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const Avatar: React.FC<AvatarProps> = (props) => {
|
|
15
|
+
const { fallback } = props;
|
|
16
|
+
const fall = useMemo(() => {
|
|
17
|
+
return fallback?.charAt(0).toUpperCase();
|
|
18
|
+
}, [fallback]);
|
|
19
|
+
return (
|
|
20
|
+
<UIAvatar className={props.className}>
|
|
21
|
+
<AvatarImage alt={props.alt} src={props.src} />
|
|
22
|
+
<AvatarFallback className={props.fallbackClassName}>{fall}</AvatarFallback>
|
|
23
|
+
</UIAvatar>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Children, cloneElement, type FC, type PropsWithChildren, type ReactElement } from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@meta-1/design/lib";
|
|
4
|
+
|
|
5
|
+
export interface BreadcrumbsItemProps extends PropsWithChildren {
|
|
6
|
+
last?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const BreadcrumbsItem: FC<BreadcrumbsItemProps> = (props) => {
|
|
10
|
+
const { last = false } = props;
|
|
11
|
+
return (
|
|
12
|
+
<div className="text-muted-foreground">
|
|
13
|
+
{props.children}
|
|
14
|
+
{last ? null : <span className="mx-2">/</span>}
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export interface BreadcrumbsProps extends PropsWithChildren {
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const Breadcrumbs: FC<BreadcrumbsProps> = (props) => {
|
|
24
|
+
const { children, className } = props;
|
|
25
|
+
|
|
26
|
+
const size = Children.count(children);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className={cn("my-2 flex items-center justify-start", className)}>
|
|
30
|
+
{Children.map(children, (child, index) => {
|
|
31
|
+
const ele = child as ReactElement;
|
|
32
|
+
if (!ele) return null;
|
|
33
|
+
// biome-ignore lint/suspicious/noExplicitAny: <cloneElement>
|
|
34
|
+
return cloneElement<any>(ele, { ...(ele as any).props, last: index === size - 1 });
|
|
35
|
+
})}
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createContext, type FC, type PropsWithChildren, useContext, useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export const BroadcastChannelContext = createContext<BroadcastChannel | null>(null);
|
|
4
|
+
|
|
5
|
+
export type BroadcastChannelProviderProps = PropsWithChildren<{
|
|
6
|
+
channelName: string;
|
|
7
|
+
onMessage: ((this: BroadcastChannel, ev: MessageEvent) => unknown) | null;
|
|
8
|
+
}>;
|
|
9
|
+
|
|
10
|
+
export const BroadcastChannelProvider: FC<BroadcastChannelProviderProps> = (props) => {
|
|
11
|
+
const { channelName, onMessage } = props;
|
|
12
|
+
const [channel, setChannel] = useState<BroadcastChannel | null>(null);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (typeof BroadcastChannel === "undefined") {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const channel = new BroadcastChannel(channelName);
|
|
19
|
+
channel.onmessage = onMessage;
|
|
20
|
+
setChannel(channel);
|
|
21
|
+
}, [channelName, onMessage]);
|
|
22
|
+
|
|
23
|
+
return <BroadcastChannelContext.Provider value={channel}>{props.children}</BroadcastChannelContext.Provider>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const useBroadcastChannel = () => {
|
|
27
|
+
return useContext(BroadcastChannelContext);
|
|
28
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { Button as UIButton } from "@meta-1/design/components/ui/button";
|
|
4
|
+
import { Spin } from "@meta-1/design/components/uix/spin";
|
|
5
|
+
import { cn } from "@meta-1/design/lib";
|
|
6
|
+
|
|
7
|
+
export interface ButtonProps extends React.ComponentProps<typeof UIButton> {
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
long?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, forwardedRef) => {
|
|
13
|
+
const { loading = false, long = false, disabled = false, className, ...rest } = props;
|
|
14
|
+
const elementRef = forwardedRef;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<UIButton
|
|
18
|
+
{...rest}
|
|
19
|
+
className={cn("gap-0.5", className, long ? "w-full" : null)}
|
|
20
|
+
disabled={loading || disabled}
|
|
21
|
+
ref={elementRef}
|
|
22
|
+
>
|
|
23
|
+
{loading ? <Spin /> : null}
|
|
24
|
+
{props.children}
|
|
25
|
+
</UIButton>
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
Button.displayName = "Button";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { FC, PropsWithChildren, ReactNode } from "react";
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
|
|
4
|
+
import { cn } from "@meta-1/design/lib";
|
|
5
|
+
import { CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Card as UICard } from "../../ui/card";
|
|
6
|
+
|
|
7
|
+
export interface CardProps extends PropsWithChildren {
|
|
8
|
+
footer?: ReactNode;
|
|
9
|
+
title?: ReactNode;
|
|
10
|
+
description?: ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
contentClassName?: string;
|
|
13
|
+
onClick?: () => void;
|
|
14
|
+
shadow?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const Card: FC<CardProps> = (props) => {
|
|
18
|
+
const { title = "", description = "", className, contentClassName, footer, shadow = false } = props;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<UICard className={classNames(shadow ? null : "shadow-none", className)} onClick={props.onClick}>
|
|
22
|
+
{title || description ? (
|
|
23
|
+
<CardHeader className="pb-0">
|
|
24
|
+
{title ? <CardTitle>{title}</CardTitle> : null}
|
|
25
|
+
{description ? <CardDescription>{description}</CardDescription> : null}
|
|
26
|
+
</CardHeader>
|
|
27
|
+
) : null}
|
|
28
|
+
<CardContent className={cn("px-6", contentClassName)}>{props.children}</CardContent>
|
|
29
|
+
{footer ? <CardFooter>{footer}</CardFooter> : null}
|
|
30
|
+
</UICard>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ComponentPropsWithoutRef,
|
|
3
|
+
type ComponentRef,
|
|
4
|
+
forwardRef,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
useCallback,
|
|
7
|
+
useId,
|
|
8
|
+
useMemo,
|
|
9
|
+
} from "react";
|
|
10
|
+
import type * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
11
|
+
import { MinusIcon } from "@radix-ui/react-icons";
|
|
12
|
+
import classNames from "classnames";
|
|
13
|
+
|
|
14
|
+
import { Checkbox as UICheckbox } from "@meta-1/design/components/ui/checkbox";
|
|
15
|
+
import { cn } from "@meta-1/design/lib";
|
|
16
|
+
|
|
17
|
+
export type CheckboxProps = Omit<ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>, "onChange" | "value"> & {
|
|
18
|
+
indeterminate?: boolean;
|
|
19
|
+
label?: ReactNode;
|
|
20
|
+
field?: boolean;
|
|
21
|
+
value?: boolean;
|
|
22
|
+
onChange?: (value: boolean) => void;
|
|
23
|
+
children?: ReactNode;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const Checkbox = forwardRef<ComponentRef<typeof UICheckbox>, CheckboxProps>((props, forwardedRef) => {
|
|
27
|
+
const { value, checked, field = false, onCheckedChange, onChange, indeterminate, label, children, ...rest } = props;
|
|
28
|
+
const elementRef = forwardedRef;
|
|
29
|
+
|
|
30
|
+
const v = useMemo(() => (field ? value : checked), [value, checked, field]);
|
|
31
|
+
const handleChange = useCallback(
|
|
32
|
+
(checked: boolean) => {
|
|
33
|
+
if (field) {
|
|
34
|
+
onChange?.(checked);
|
|
35
|
+
} else {
|
|
36
|
+
onCheckedChange?.(checked);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
[field, onCheckedChange, onChange],
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const content = useMemo(() => children || label, [children, label]);
|
|
43
|
+
|
|
44
|
+
const autoId = useId();
|
|
45
|
+
const id = props.id ?? autoId;
|
|
46
|
+
|
|
47
|
+
const checkbox = (
|
|
48
|
+
<UICheckbox
|
|
49
|
+
{...rest}
|
|
50
|
+
checked={v}
|
|
51
|
+
className={cn(
|
|
52
|
+
indeterminate && "border-primary bg-primary text-primary-foreground",
|
|
53
|
+
indeterminate && "flex items-center justify-center",
|
|
54
|
+
!content && props.className,
|
|
55
|
+
)}
|
|
56
|
+
id={id}
|
|
57
|
+
onCheckedChange={(checked) => handleChange(checked as boolean)}
|
|
58
|
+
ref={elementRef}
|
|
59
|
+
>
|
|
60
|
+
{indeterminate && <MinusIcon className="size-3.5" />}
|
|
61
|
+
</UICheckbox>
|
|
62
|
+
);
|
|
63
|
+
if (!content) return checkbox;
|
|
64
|
+
return (
|
|
65
|
+
<label
|
|
66
|
+
className={classNames(
|
|
67
|
+
"inline-flex items-center justify-start space-x-1",
|
|
68
|
+
"font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
|
69
|
+
props.className,
|
|
70
|
+
)}
|
|
71
|
+
htmlFor={id}
|
|
72
|
+
>
|
|
73
|
+
{checkbox}
|
|
74
|
+
<span>{content}</span>
|
|
75
|
+
</label>
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
Checkbox.displayName = "Checkbox";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type FC, forwardRef, type ReactNode, useState } from "react";
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
import remove from "lodash/remove";
|
|
4
|
+
|
|
5
|
+
import { Checkbox } from "@meta-1/design/components/uix/checkbox";
|
|
6
|
+
|
|
7
|
+
export interface CheckboxGroupOptionProps {
|
|
8
|
+
label: ReactNode;
|
|
9
|
+
value: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CheckboxGroupProps {
|
|
14
|
+
options?: CheckboxGroupOptionProps[];
|
|
15
|
+
value?: string[];
|
|
16
|
+
onChange?: (value: string[]) => void;
|
|
17
|
+
itemClassName?: string;
|
|
18
|
+
checkboxClassName?: string;
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const CheckboxGroup: FC<CheckboxGroupProps> = forwardRef((props, _ref) => {
|
|
23
|
+
const { options = [], itemClassName, className, checkboxClassName } = props;
|
|
24
|
+
|
|
25
|
+
const [checkedValues, setCheckedValues] = useState<string[]>(props.value || []);
|
|
26
|
+
|
|
27
|
+
const onCheckedChange = (value: string, checked: boolean) => {
|
|
28
|
+
if (checked) {
|
|
29
|
+
checkedValues.push(value);
|
|
30
|
+
} else {
|
|
31
|
+
remove(checkedValues, (v) => v === value);
|
|
32
|
+
}
|
|
33
|
+
setCheckedValues([...checkedValues]);
|
|
34
|
+
props.onChange?.([...checkedValues]);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className={classNames("-m-2 flex flex-wrap items-center justify-start space-x-2 space-y-2", className)}>
|
|
39
|
+
<span />
|
|
40
|
+
{options.map((option) => {
|
|
41
|
+
const id = `checkbox-group-${option.value}`;
|
|
42
|
+
return (
|
|
43
|
+
<label
|
|
44
|
+
className={classNames("flex items-center justify-start", "ml-1 space-x-1", itemClassName)}
|
|
45
|
+
htmlFor={id}
|
|
46
|
+
key={option.value}
|
|
47
|
+
>
|
|
48
|
+
<Checkbox
|
|
49
|
+
checked={checkedValues.includes(option.value)}
|
|
50
|
+
className={checkboxClassName}
|
|
51
|
+
id={id}
|
|
52
|
+
onCheckedChange={(checked: boolean) => onCheckedChange(option.value, checked)}
|
|
53
|
+
/>
|
|
54
|
+
<span>{option.label}</span>
|
|
55
|
+
</label>
|
|
56
|
+
);
|
|
57
|
+
})}
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
});
|