feat(pswui): update component and library to latest

This commit is contained in:
p-sw 2024-07-12 02:04:54 +09:00
parent b76014db0b
commit f92be39455
14 changed files with 210 additions and 100 deletions

View File

@ -111,21 +111,20 @@ export interface ButtonProps
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
(props, ref) => { (props, ref) => {
const [variantProps, otherPropsCompressed] = resolveVariants(props); const [variantProps, otherPropsCompressed] = resolveVariants(props);
const { asChild, ...otherPropsExtracted } = otherPropsCompressed; const { asChild, type, ...otherPropsExtracted } = otherPropsCompressed;
const Comp = asChild ? Slot : "button"; const Comp = asChild ? Slot : "button";
const compProps = {
...otherPropsExtracted,
className: buttonVariants(variantProps),
};
return ( return (
<Comp <Comp
ref={ref} ref={ref}
{...compProps} type={type ?? "button"}
className={buttonVariants(variantProps)}
{...otherPropsExtracted}
/> />
); );
}, },
); );
Button.displayName = "Button";
export { Button }; export { Button };

View File

@ -110,5 +110,6 @@ const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
); );
}, },
); );
Checkbox.displayName = "Checkbox";
export { Checkbox }; export { Checkbox };

View File

@ -1,5 +1,10 @@
import { Slot, type VariantProps, vcn } from "@pswui-lib"; import {
import React, { useState } from "react"; ServerSideDocumentFallback,
Slot,
type VariantProps,
vcn,
} from "@pswui-lib";
import React, { type ReactNode, useState } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { import {
@ -100,8 +105,9 @@ const DialogOverlay = React.forwardRef<HTMLDivElement, DialogOverlay>(
}); });
const { children, closeOnClick, onClick, ...otherPropsExtracted } = const { children, closeOnClick, onClick, ...otherPropsExtracted } =
otherPropsCompressed; otherPropsCompressed;
return ( return (
<> <ServerSideDocumentFallback>
{ReactDOM.createPortal( {ReactDOM.createPortal(
<div <div
{...otherPropsExtracted} {...otherPropsExtracted}
@ -125,10 +131,11 @@ const DialogOverlay = React.forwardRef<HTMLDivElement, DialogOverlay>(
</div>, </div>,
document.body, document.body,
)} )}
</> </ServerSideDocumentFallback>
); );
}, },
); );
DialogOverlay.displayName = "DialogOverlay";
/** /**
* ========================= * =========================
@ -204,6 +211,7 @@ const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps>(
); );
}, },
); );
DialogContent.displayName = "DialogContent";
/** /**
* ========================= * =========================
@ -268,6 +276,8 @@ const DialogHeader = React.forwardRef<HTMLElement, DialogHeaderProps>(
}, },
); );
DialogHeader.displayName = "DialogHeader";
/** /**
* ========================= * =========================
* DialogTitle / DialogSubtitle * DialogTitle / DialogSubtitle
@ -342,6 +352,7 @@ const DialogTitle = React.forwardRef<HTMLHeadingElement, DialogTitleProps>(
); );
}, },
); );
DialogTitle.displayName = "DialogTitle";
const DialogSubtitle = React.forwardRef< const DialogSubtitle = React.forwardRef<
HTMLHeadingElement, HTMLHeadingElement,
@ -360,6 +371,7 @@ const DialogSubtitle = React.forwardRef<
</h2> </h2>
); );
}); });
DialogSubtitle.displayName = "DialogSubtitle";
/** /**
* ========================= * =========================
@ -401,6 +413,34 @@ const DialogFooter = React.forwardRef<HTMLDivElement, DialogFooterProps>(
); );
}, },
); );
DialogFooter.displayName = "DialogFooter";
interface DialogControllers {
context: IDialogContext;
setContext: React.Dispatch<React.SetStateAction<IDialogContext>>;
close: () => void;
}
interface DialogControllerProps {
children: (controllers: DialogControllers) => ReactNode;
}
const DialogController = (props: DialogControllerProps) => {
return (
<DialogContext.Consumer>
{([context, setContext]) =>
props.children({
context,
setContext,
close() {
setContext((p) => ({ ...p, opened: false }));
},
})
}
</DialogContext.Consumer>
);
};
export { export {
DialogRoot, DialogRoot,
@ -412,4 +452,5 @@ export {
DialogTitle, DialogTitle,
DialogSubtitle, DialogSubtitle,
DialogFooter, DialogFooter,
DialogController,
}; };

View File

@ -1,4 +1,10 @@
import { type AsChild, Slot, type VariantProps, vcn } from "@pswui-lib"; import {
type AsChild,
ServerSideDocumentFallback,
Slot,
type VariantProps,
vcn,
} from "@pswui-lib";
import React, { import React, {
type ComponentPropsWithoutRef, type ComponentPropsWithoutRef,
type TouchEvent as ReactTouchEvent, type TouchEvent as ReactTouchEvent,
@ -119,25 +125,30 @@ const DrawerOverlay = forwardRef<HTMLDivElement, DrawerOverlayProps>(
: 1 : 1
})`; })`;
return createPortal( return (
<Comp <ServerSideDocumentFallback>
{...restPropsExtracted} {createPortal(
className={drawerOverlayVariant({ <Comp
...variantProps, {...restPropsExtracted}
opened: state.isDragging ? true : state.opened, className={drawerOverlayVariant({
})} ...variantProps,
onClick={onOutsideClick} opened: state.isDragging ? true : state.opened,
style={{ })}
backdropFilter, onClick={onOutsideClick}
WebkitBackdropFilter: backdropFilter, style={{
transitionDuration: state.isDragging ? "0s" : undefined, backdropFilter,
}} WebkitBackdropFilter: backdropFilter,
ref={ref} transitionDuration: state.isDragging ? "0s" : undefined,
/>, }}
document.body, ref={ref}
/>,
document.body,
)}
</ServerSideDocumentFallback>
); );
}, },
); );
DrawerOverlay.displayName = "DrawerOverlay";
const drawerContentColors = { const drawerContentColors = {
background: "bg-white dark:bg-black", background: "bg-white dark:bg-black",
@ -309,7 +320,7 @@ const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(
...variantProps, ...variantProps,
opened: true, opened: true,
className: dragState.isDragging className: dragState.isDragging
? "transition-[width_0ms]" ? "transition-[width] duration-0"
: variantProps.className, : variantProps.className,
})} })}
style={ style={
@ -374,6 +385,7 @@ const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(
); );
}, },
); );
DrawerContent.displayName = "DrawerContent";
const DrawerClose = forwardRef< const DrawerClose = forwardRef<
HTMLButtonElement, HTMLButtonElement,
@ -388,6 +400,7 @@ const DrawerClose = forwardRef<
/> />
); );
}); });
DrawerClose.displayName = "DrawerClose";
const [drawerHeaderVariant, resolveDrawerHeaderVariantProps] = vcn({ const [drawerHeaderVariant, resolveDrawerHeaderVariantProps] = vcn({
base: "flex flex-col gap-2", base: "flex flex-col gap-2",
@ -417,6 +430,7 @@ const DrawerHeader = forwardRef<HTMLDivElement, DrawerHeaderProps>(
); );
}, },
); );
DrawerHeader.displayName = "DrawerHeader";
const [drawerBodyVariant, resolveDrawerBodyVariantProps] = vcn({ const [drawerBodyVariant, resolveDrawerBodyVariantProps] = vcn({
base: "flex-grow", base: "flex-grow",
@ -444,6 +458,7 @@ const DrawerBody = forwardRef<HTMLDivElement, DrawerBodyProps>((props, ref) => {
/> />
); );
}); });
DrawerBody.displayName = "DrawerBody";
const [drawerFooterVariant, resolveDrawerFooterVariantProps] = vcn({ const [drawerFooterVariant, resolveDrawerFooterVariantProps] = vcn({
base: "flex flex-row justify-end gap-2", base: "flex flex-row justify-end gap-2",
@ -473,6 +488,7 @@ const DrawerFooter = forwardRef<HTMLDivElement, DrawerFooterProps>(
); );
}, },
); );
DrawerFooter.displayName = "DrawerFooter";
export { export {
DrawerRoot, DrawerRoot,

View File

View File

@ -1,4 +1,4 @@
import { type VariantProps, vcn } from "@pswui-lib"; import { type AsChild, Slot, type VariantProps, vcn } from "@pswui-lib";
import React from "react"; import React from "react";
const inputColors = { const inputColors = {
@ -42,7 +42,8 @@ const [inputVariant, resolveInputVariantProps] = vcn({
interface InputFrameProps interface InputFrameProps
extends VariantProps<typeof inputVariant>, extends VariantProps<typeof inputVariant>,
React.ComponentPropsWithoutRef<"label"> { React.ComponentPropsWithoutRef<"label">,
AsChild {
children?: React.ReactNode; children?: React.ReactNode;
} }
@ -50,19 +51,22 @@ const InputFrame = React.forwardRef<HTMLLabelElement, InputFrameProps>(
(props, ref) => { (props, ref) => {
const [variantProps, otherPropsCompressed] = const [variantProps, otherPropsCompressed] =
resolveInputVariantProps(props); resolveInputVariantProps(props);
const { children, ...otherPropsExtracted } = otherPropsCompressed; const { children, asChild, ...otherPropsExtracted } = otherPropsCompressed;
const Comp = asChild ? Slot : "label";
return ( return (
<label <Comp
ref={ref} ref={ref}
className={`group/input-frame ${inputVariant(variantProps)}`} className={`group/input-frame ${inputVariant(variantProps)}`}
{...otherPropsExtracted} {...otherPropsExtracted}
> >
{children} {children}
</label> </Comp>
); );
}, },
); );
InputFrame.displayName = "InputFrame";
interface InputProps interface InputProps
extends VariantProps<typeof inputVariant>, extends VariantProps<typeof inputVariant>,
@ -113,5 +117,6 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
/> />
); );
}); });
Input.displayName = "Input";
export { InputFrame, Input }; export { InputFrame, Input };

View File

@ -29,5 +29,6 @@ const Label = React.forwardRef<HTMLLabelElement, LabelProps>((props, ref) => {
/> />
); );
}); });
Label.displayName = "Label";
export { Label }; export { Label };

View File

@ -65,7 +65,7 @@ const popoverColors = {
}; };
const [popoverContentVariant, resolvePopoverContentVariantProps] = vcn({ const [popoverContentVariant, resolvePopoverContentVariantProps] = vcn({
base: `absolute transition-all duration-150 border rounded-lg p-0.5 [&>*]:w-full z-10 ${popoverColors.background} ${popoverColors.border}`, base: `absolute transition-all duration-150 border rounded-lg p-0.5 [&>*]:w-full ${popoverColors.background} ${popoverColors.border}`,
variants: { variants: {
direction: { direction: {
row: "", row: "",
@ -200,7 +200,7 @@ const PopoverContent = React.forwardRef<HTMLDivElement, PopoverContentProps>(
(props, ref) => { (props, ref) => {
const [variantProps, otherPropsCompressed] = const [variantProps, otherPropsCompressed] =
resolvePopoverContentVariantProps(props); resolvePopoverContentVariantProps(props);
const { children, ...otherPropsExtracted } = otherPropsCompressed; const { children, asChild, ...otherPropsExtracted } = otherPropsCompressed;
const [state, setState] = useContext(PopoverContext); const [state, setState] = useContext(PopoverContext);
const internalRef = useRef<HTMLDivElement | null>(null); const internalRef = useRef<HTMLDivElement | null>(null);
@ -221,14 +221,16 @@ const PopoverContent = React.forwardRef<HTMLDivElement, PopoverContentProps>(
}; };
}, [state.controlled, setState]); }, [state.controlled, setState]);
const Comp = asChild ? Slot : "div";
return ( return (
<div <Comp
{...otherPropsExtracted} {...otherPropsExtracted}
className={popoverContentVariant({ className={popoverContentVariant({
...variantProps, ...variantProps,
opened: state.opened, opened: state.opened,
})} })}
ref={(el) => { ref={(el: HTMLDivElement) => {
internalRef.current = el; internalRef.current = el;
if (typeof ref === "function") { if (typeof ref === "function") {
ref(el); ref(el);
@ -238,9 +240,10 @@ const PopoverContent = React.forwardRef<HTMLDivElement, PopoverContentProps>(
}} }}
> >
{children} {children}
</div> </Comp>
); );
}, },
); );
PopoverContent.displayName = "PopoverContent";
export { Popover, PopoverTrigger, PopoverContent }; export { Popover, PopoverTrigger, PopoverContent };

View File

@ -80,5 +80,6 @@ const Switch = React.forwardRef<HTMLInputElement, SwitchProps>((props, ref) => {
</label> </label>
); );
}); });
Switch.displayName = "Switch";
export { Switch }; export { Switch };

View File

@ -1,4 +1,4 @@
import { type VariantProps, vcn } from "@pswui-lib"; import { type VariantProps, vcn, withServerSideDocument } from "@pswui-lib";
import React, { useEffect, useId, useRef } from "react"; import React, { useEffect, useId, useRef } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
@ -145,68 +145,71 @@ interface ToasterProps
muteDuplicationWarning?: boolean; muteDuplicationWarning?: boolean;
} }
const Toaster = React.forwardRef<HTMLDivElement, ToasterProps>((props, ref) => { const Toaster = withServerSideDocument(
const id = useId(); React.forwardRef<HTMLDivElement, ToasterProps>((props, ref) => {
const [variantProps, otherPropsCompressed] = const id = useId();
resolveToasterVariantProps(props); const [variantProps, otherPropsCompressed] =
const { defaultOption, muteDuplicationWarning, ...otherPropsExtracted } = resolveToasterVariantProps(props);
otherPropsCompressed; const { defaultOption, muteDuplicationWarning, ...otherPropsExtracted } =
otherPropsCompressed;
const [toastList, setToastList] = React.useState<typeof toasts>(toasts); const [toastList, setToastList] = React.useState<typeof toasts>(toasts);
const internalRef = useRef<HTMLDivElement | null>(null); const internalRef = useRef<HTMLDivElement | null>(null);
useEffect(() => { useEffect(() => {
return subscribe(() => { return subscribe(() => {
setToastList(getSnapshot()); setToastList(getSnapshot());
}); });
}, []); }, []);
const option = React.useMemo(() => { const option = React.useMemo(() => {
return { return {
...defaultToastOption, ...defaultToastOption,
...defaultOption, ...defaultOption,
}; };
}, [defaultOption]); }, [defaultOption]);
const toasterInstance = document.querySelector("div[data-toaster-root]"); const toasterInstance = document.querySelector("div[data-toaster-root]");
if (toasterInstance && id !== toasterInstance.id) { if (toasterInstance && id !== toasterInstance.id) {
if (process.env.NODE_ENV === "development" && !muteDuplicationWarning) { if (process.env.NODE_ENV === "development" && !muteDuplicationWarning) {
console.warn( console.warn(
"Multiple Toaster instances detected. Only one Toaster is allowed.", "Multiple Toaster instances detected. Only one Toaster is allowed.",
); );
}
return null;
} }
return null;
}
return ( return (
<> <>
{ReactDOM.createPortal( {ReactDOM.createPortal(
<div <div
{...otherPropsExtracted} {...otherPropsExtracted}
data-toaster-root={true} data-toaster-root={true}
className={toasterVariant(variantProps)} className={toasterVariant(variantProps)}
ref={(el) => { ref={(el) => {
internalRef.current = el; internalRef.current = el;
if (typeof ref === "function") { if (typeof ref === "function") {
ref(el); ref(el);
} else if (ref) { } else if (ref) {
ref.current = el; ref.current = el;
} }
}} }}
id={id} id={id}
> >
{Object.entries(toastList).map(([id]) => ( {Object.entries(toastList).map(([id]) => (
<ToastTemplate <ToastTemplate
key={id} key={id}
id={id as `${number}`} id={id as `${number}`}
globalOption={option} globalOption={option}
/> />
))} ))}
</div>, </div>,
document.body, document.body,
)} )}
</> </>
); );
}); }),
);
Toaster.displayName = "Toaster";
export { Toaster }; export { Toaster };

View File

@ -71,6 +71,7 @@ const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>((props, ref) => {
</TooltipContext.Provider> </TooltipContext.Provider>
); );
}); });
Tooltip.displayName = "Tooltip";
const tooltipContentColors = { const tooltipContentColors = {
variants: { variants: {
@ -83,10 +84,10 @@ const tooltipContentColors = {
}; };
const [tooltipContentVariant, resolveTooltipContentVariantProps] = vcn({ const [tooltipContentVariant, resolveTooltipContentVariantProps] = vcn({
base: `absolute py-1 px-3 rounded-md border opacity-0 transition-all base: `absolute py-1 px-3 rounded-md border opacity-0 transition-all
group-[:not(.controlled):hover]/tooltip:opacity-100 group-[.opened]/tooltip:opacity-100 group-[:not(.controlled):hover]/tooltip:opacity-100 group-[.opened]/tooltip:opacity-100
select-none pointer-events-none select-none pointer-events-none
group-[:not(.controlled):hover]/tooltip:select-auto group-[.opened]/tooltip:select-auto group-[:not(.controlled):hover]/tooltip:pointer-events-auto group-[.opened]/tooltip:pointer-events-auto group-[:not(.controlled):hover]/tooltip:select-auto group-[.opened]/tooltip:select-auto group-[:not(.controlled):hover]/tooltip:pointer-events-auto group-[.opened]/tooltip:pointer-events-auto
group-[:not(.controlled):hover]/tooltip:[transition:transform_150ms_ease-out_var(--delay),opacity_150ms_ease-out_var(--delay),background-color_150ms_ease-in-out,color_150ms_ease-in-out,border-color_150ms_ease-in-out]`, group-[:not(.controlled):hover]/tooltip:[transition:transform_150ms_ease-out_var(--delay),opacity_150ms_ease-out_var(--delay),background-color_150ms_ease-in-out,color_150ms_ease-in-out,border-color_150ms_ease-in-out]`,
variants: { variants: {
position: { position: {
@ -144,5 +145,6 @@ const TooltipContent = React.forwardRef<HTMLDivElement, TooltipContentProps>(
); );
}, },
); );
TooltipContent.displayName = "TooltipContent";
export { Tooltip, TooltipContent }; export { Tooltip, TooltipContent };

View File

@ -1,2 +1,4 @@
export * from "./vcn"; export * from "./vcn";
export * from "./Slot"; export * from "./Slot";
export * from "./ssrFallback";
export * from "./withSSD";

View File

@ -0,0 +1,20 @@
import { type ReactNode, useEffect, useState } from "react";
/**
* This component allows components to use `document` as like they're always in the client side.
* Return null if there is no `document` (which represents it's server side) or initial render(to avoid hydration error).
*/
function ServerSideDocumentFallback({ children }: { children: ReactNode }) {
const [initialRender, setInitialRender] = useState<boolean>(true);
useEffect(() => {
setInitialRender(false);
}, []);
if (typeof document === "undefined" /* server side */ || initialRender)
return null;
return children;
}
export { ServerSideDocumentFallback };

16
src/pswui/lib/withSSD.tsx Normal file
View File

@ -0,0 +1,16 @@
import type { ComponentType } from "react";
import { ServerSideDocumentFallback } from "./ssrFallback";
function withServerSideDocument<P extends {}>(
Component: ComponentType<P>,
): ComponentType<P> {
return (props) => {
return (
<ServerSideDocumentFallback>
<Component {...props} />
</ServerSideDocumentFallback>
);
};
}
export { withServerSideDocument };