feat(components): focus visible & other states are now handled by data properties

This commit is contained in:
Junior Garcia 2023-04-15 18:05:12 -03:00
parent 5f1d889d8c
commit f4e2e502a6
26 changed files with 231 additions and 185 deletions

View File

@ -15,7 +15,7 @@ import {useAvatarGroupContext} from "./avatar-group-context";
export interface UseAvatarProps
extends Omit<
HTMLNextUIProps<"span", AvatarVariantProps>,
"children" | "isInGroup" | "isInGridGroup" | "isFocusVisible"
"children" | "isInGroup" | "isInGridGroup"
> {
/**
* Ref to the DOM node.

View File

@ -18,7 +18,7 @@ import {useButtonGroupContext} from "./button-group-context";
export interface UseButtonProps
extends HTMLNextUIProps<"button", Omit<AriaButtonProps, keyof ButtonVariantProps>>,
Omit<ButtonVariantProps, "isFocusVisible" | "isInGroup" | "isInVerticalGroup"> {
Omit<ButtonVariantProps, "isInGroup" | "isInVerticalGroup"> {
/**
* Ref to the DOM node.
*/

View File

@ -68,7 +68,7 @@ interface Props extends HTMLNextUIProps<"label"> {
export type UseCheckboxProps = Omit<Props, "defaultChecked"> &
Omit<AriaCheckboxProps, keyof CheckboxVariantProps | "onChange"> &
Omit<CheckboxVariantProps, "isFocusVisible">;
CheckboxVariantProps;
export function useCheckbox(props: UseCheckboxProps) {
const groupContext = useCheckboxGroupContext();

View File

@ -9,7 +9,7 @@ import {TreeState} from "@react-stately/tree";
import {clsx, dataAttr} from "@nextui-org/shared-utils";
import {useMenuItem} from "@react-aria/menu";
import {chain, filterDOMProps, mergeProps} from "@react-aria/utils";
import {usePress} from "@react-aria/interactions";
import {useHover, usePress} from "@react-aria/interactions";
import {useDropdownContext} from "./dropdown-context";
@ -65,12 +65,16 @@ export function useDropdownItem<T extends object>(originalProps: UseDropdownItem
const descriptionId = useId();
const keyboardId = useId();
const {pressProps} = usePress({
const {pressProps, isPressed} = usePress({
ref: domRef,
isDisabled,
onPress,
});
const {isHovered, hoverProps} = useHover({
isDisabled,
});
const {isFocusVisible, focusProps} = useFocusRing({
autoFocus,
});
@ -101,10 +105,9 @@ export function useDropdownItem<T extends object>(originalProps: UseDropdownItem
dropdownItem({
...variantProps,
isDisabled,
isFocusVisible,
disableAnimation,
}),
[...Object.values(variantProps), isDisabled, isFocusVisible, disableAnimation],
[...Object.values(variantProps), isDisabled, disableAnimation],
);
const baseStyles = clsx(classNames?.base, className);
@ -115,10 +118,16 @@ export function useDropdownItem<T extends object>(originalProps: UseDropdownItem
menuItemProps,
focusProps,
pressProps,
hoverProps,
filterDOMProps(otherProps, {labelable: true}),
props,
),
"data-focused": dataAttr(isFocused),
"data-focus": dataAttr(isFocused),
"data-hover": dataAttr(isHovered),
"data-disabled": dataAttr(isDisabled),
"data-selected": dataAttr(isSelected),
"data-pressed": dataAttr(isPressed),
"data-focus-visible": dataAttr(isFocusVisible),
"aria-labelledby": labelId,
"aria-describedby": [descriptionId, keyboardId].filter(Boolean).join(" ") || undefined,
className: slots.base({class: clsx(baseStyles, props.className)}),

View File

@ -60,11 +60,9 @@ const Pagination = forwardRef<PaginationProps, "ul">((props, ref) => {
<PaginationItem
key={key}
className={slots.prev({
class: clsx(
classNames?.prev,
!loop && activePage === 1 && "opacity-50 pointer-events-none",
),
class: classNames?.prev,
})}
isDisabled={!loop && activePage === 1}
value={value}
onPress={onPrevious}
>
@ -77,11 +75,9 @@ const Pagination = forwardRef<PaginationProps, "ul">((props, ref) => {
<PaginationItem
key={key}
className={slots.next({
class: clsx(
classNames?.next,
!loop && activePage === total && "opacity-50 pointer-events-none",
),
class: clsx(classNames?.next),
})}
isDisabled={!loop && activePage === total}
value={value}
onPress={onNext}
>

View File

@ -4,11 +4,10 @@ import type {PressEvent} from "@react-types/shared";
import {useMemo} from "react";
import {PaginationItemType, PaginationItemValue} from "@nextui-org/use-pagination";
import {ringClasses} from "@nextui-org/theme";
import {clsx, dataAttr, warn} from "@nextui-org/shared-utils";
import {mergeProps} from "@react-aria/utils";
import {clsx, dataAttr} from "@nextui-org/shared-utils";
import {chain, mergeProps} from "@react-aria/utils";
import {useDOMRef} from "@nextui-org/dom-utils";
import {usePress} from "@react-aria/interactions";
import {useHover, usePress} from "@react-aria/interactions";
import {useFocusRing} from "@react-aria/focus";
export interface UsePaginationItemProps extends Omit<HTMLNextUIProps<"li">, "onClick"> {
@ -91,19 +90,13 @@ export function usePaginationItem(props: UsePaginationItemProps) {
[value, isActive],
);
const handlePress = (e: PressEvent) => {
if (onClick) {
warn("onClick is deprecated, use onPress instead.", "PaginationItem");
}
onPress?.(e);
};
const {pressProps} = usePress({
isDisabled,
onPress: handlePress,
onPress,
});
const {focusProps, isFocused, isFocusVisible} = useFocusRing();
const {focusProps, isFocused, isFocusVisible} = useFocusRing({});
const {isHovered, hoverProps} = useHover({isDisabled});
const getItemProps: PropGetter = (props = {}) => {
return {
@ -113,11 +106,14 @@ export function usePaginationItem(props: UsePaginationItemProps) {
"aria-label": ariaLabel,
"aria-current": dataAttr(isActive),
"aria-disabled": dataAttr(isDisabled),
"data-disabled": dataAttr(isDisabled),
"data-active": dataAttr(isActive),
"data-focus": dataAttr(isFocused),
"data-hover": dataAttr(isHovered),
"data-focus-visible": dataAttr(isFocusVisible),
"data-focused": dataAttr(isFocused),
className: clsx(isFocusVisible && [...ringClasses], className),
...mergeProps(props, pressProps, focusProps, otherProps),
...mergeProps(props, pressProps, focusProps, hoverProps, otherProps),
className: clsx(className, props.className),
onClick: chain(pressProps.onClick, onClick),
};
};

View File

@ -64,7 +64,6 @@ interface Props extends Omit<HTMLNextUIProps<"ul">, "onChange"> {
* ```ts
* <Pagination classNames={{
* base:"base-classes",
* wrapper: "wrapper-classes",
* prev: "prev-classes", // prev button classes
* item: "item-classes",
* next: "next-classes", // next button classes
@ -214,7 +213,7 @@ export function usePagination(originalProps: UsePaginationProps) {
"data-dots-jump": dotsJump,
"data-total": total,
"data-active-page": activePage,
className: slots.base({class: baseStyles}),
className: slots.base({class: clsx(baseStyles, props?.className)}),
...otherProps,
};
};
@ -224,7 +223,7 @@ export function usePagination(originalProps: UsePaginationProps) {
...props,
ref: (node) => getItemRef(node, props.value),
isActive: props.value === activePage,
className: slots.item({class: classNames?.item}),
className: slots.item({class: clsx(classNames?.item, props?.className)}),
onPress: () => {
if (props.value !== activePage) {
setPage(props.value);
@ -238,7 +237,7 @@ export function usePagination(originalProps: UsePaginationProps) {
...props,
ref: cursorRef,
activePage,
className: slots.cursor({class: classNames?.cursor}),
className: slots.cursor({class: clsx(classNames?.cursor, props?.className)}),
};
};

View File

@ -4,7 +4,6 @@ import type {HTMLMotionProps} from "framer-motion";
import {DOMAttributes, ReactNode, useMemo, useRef} from "react";
import {forwardRef} from "@nextui-org/system";
import {DismissButton} from "@react-aria/overlays";
import {FocusScope} from "@react-aria/focus";
import {TRANSITION_VARIANTS} from "@nextui-org/framer-transitions";
import {motion} from "framer-motion";
import {getTransformOrigins} from "@nextui-org/aria-utils";
@ -54,9 +53,7 @@ const PopoverContent = forwardRef<PopoverContentProps, "section">((props, _) =>
<>
<DismissButton onDismiss={onClose} />
<Component {...getDialogProps(mergeProps(dialogProps, otherProps))} ref={dialogRef}>
<FocusScope contain restoreFocus>
{typeof children === "function" ? children(titleProps) : children}
</FocusScope>
{typeof children === "function" ? children(titleProps) : children}
{arrowContent}
</Component>
<DismissButton onDismiss={onClose} />

View File

@ -16,7 +16,7 @@ import {toReactAriaPlacement, getArrowPlacement} from "@nextui-org/aria-utils";
import {popover} from "@nextui-org/theme";
import {mergeProps, mergeRefs} from "@react-aria/utils";
import {createDOMRef} from "@nextui-org/dom-utils";
import {ReactRef, clsx} from "@nextui-org/shared-utils";
import {ReactRef, clsx, dataAttr} from "@nextui-org/shared-utils";
import {useId, useMemo, useCallback, useImperativeHandle, useRef} from "react";
export interface Props extends HTMLNextUIProps<"div"> {
@ -154,15 +154,14 @@ export function usePopover(originalProps: UsePopoverProps) {
const {triggerProps} = useOverlayTrigger({type: triggerType}, state, triggerRef);
const {isFocusVisible, focusProps} = useFocusRing();
const {isFocusVisible, isFocused, focusProps} = useFocusRing();
const slots = useMemo(
() =>
popover({
...variantProps,
isFocusVisible,
}),
[...Object.values(variantProps), isFocusVisible],
[...Object.values(variantProps)],
);
const baseStyles = clsx(classNames?.base, className);
@ -175,6 +174,9 @@ export function usePopover(originalProps: UsePopoverProps) {
const getDialogProps: PropGetter = (props = {}) => ({
className: slots.base({class: clsx(baseStyles, props.className)}),
"data-open": dataAttr(state.isOpen),
"data-focus": dataAttr(isFocused),
"data-focus-visible": dataAttr(isFocusVisible),
...mergeProps(focusProps, props),
style: {
// this prevent the dialog to have a default outline

View File

@ -1,3 +1,4 @@
/* eslint-disable jsx-a11y/no-autofocus */
import React from "react";
import {ComponentStory, ComponentMeta} from "@storybook/react";
import {popover, ButtonVariantProps} from "@nextui-org/theme";
@ -379,7 +380,7 @@ const WithFormTemplate: ComponentStory<typeof Popover> = (args: PopoverProps) =>
Dimensions
</p>
<div className="mt-2 flex flex-col gap-2 w-full">
<Input defaultValue="100%" label="Width" size="sm" variant="bordered" />
<Input autoFocus defaultValue="100%" label="Width" size="sm" variant="bordered" />
<Input defaultValue="300px" label="Max. width" size="sm" variant="bordered" />
<Input defaultValue="24px" label="Height" size="sm" variant="bordered" />
<Input defaultValue="30px" label="Max. height" size="sm" variant="bordered" />

View File

@ -48,7 +48,7 @@ interface Props extends HTMLNextUIProps<"label"> {
export type UseRadioProps = Omit<Props, "defaultChecked"> &
Omit<AriaRadioProps, keyof RadioVariantProps> &
Omit<RadioVariantProps, "isFocusVisible">;
RadioVariantProps;
export function useRadio(props: UseRadioProps) {
const groupContext = useRadioGroupContext();

View File

@ -43,7 +43,9 @@
"@nextui-org/dom-utils": "workspace:*",
"@nextui-org/use-clipboard": "workspace:*",
"@nextui-org/tooltip": "workspace:*",
"@react-aria/focus": "^3.12.0"
"@react-aria/focus": "^3.12.0",
"@react-aria/interactions": "^3.15.0",
"@react-aria/utils": "^3.16.0"
},
"devDependencies": {
"clean-package": "2.2.0",

View File

@ -1,5 +1,4 @@
import {forwardRef} from "@nextui-org/system";
import {clsx} from "@nextui-org/shared-utils";
import {Tooltip} from "@nextui-org/tooltip";
import {ReactNode, useCallback, useMemo} from "react";
@ -26,9 +25,9 @@ const Snippet = forwardRef<SnippetProps, "div">((props, ref) => {
hideCopyButton,
tooltipProps,
isMultiLine,
focusProps,
onCopy,
getSnippetProps,
getCopyButtonProps,
} = useSnippet({ref, ...props});
const TooltipContent = useCallback(
@ -41,17 +40,7 @@ const Snippet = forwardRef<SnippetProps, "div">((props, ref) => {
return null;
}
const copyButton = (
<button
className={slots.copy({
class: clsx(disableCopy && "opacity-50 cursor-not-allowed", classNames?.copy),
})}
onClick={onCopy}
{...focusProps}
>
{copied ? checkIcon : copyIcon}
</button>
);
const copyButton = <button {...getCopyButtonProps()}>{copied ? checkIcon : copyIcon}</button>;
if (disableTooltip) {
return copyButton;

View File

@ -3,11 +3,13 @@ import type {SnippetVariantProps, SnippetSlots, SlotsToClasses} from "@nextui-or
import {snippet} from "@nextui-org/theme";
import {HTMLNextUIProps, mapPropsVariants, PropGetter} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {clsx, ReactRef} from "@nextui-org/shared-utils";
import {clsx, dataAttr, ReactRef} from "@nextui-org/shared-utils";
import {useClipboard} from "@nextui-org/use-clipboard";
import {useFocusRing} from "@react-aria/focus";
import {useMemo, useCallback} from "react";
import {TooltipProps} from "@nextui-org/tooltip";
import {usePress} from "@react-aria/interactions";
import {mergeProps} from "@react-aria/utils";
export interface UseSnippetProps
extends Omit<HTMLNextUIProps<"div">, "onCopy">,
SnippetVariantProps {
@ -79,6 +81,14 @@ export interface UseSnippetProps
hideSymbol?: boolean;
/**
* Tooltip props.
* @default {
* offset: 15,
* delay: 1000,
* content: "Copy to clipboard",
* variant: snippetProps?.variant, // same as the snippet variant
* color: snippetProps?.color, // same as the snippet color
* isDisabled: disableCopy,
* }
*/
tooltipProps?: TooltipProps;
/**
@ -107,6 +117,7 @@ export function useSnippet(originalProps: UseSnippetProps) {
onCopy: onCopyProp,
tooltipProps = {
offset: 15,
delay: 1000,
content: "Copy to clipboard",
variant: originalProps?.variant as TooltipProps["variant"],
color: originalProps?.color as TooltipProps["color"],
@ -124,7 +135,7 @@ export function useSnippet(originalProps: UseSnippetProps) {
const isMultiLine = children && Array.isArray(children);
const {isFocusVisible, focusProps} = useFocusRing({
const {isFocusVisible, isFocused, focusProps} = useFocusRing({
autoFocus,
});
@ -132,9 +143,8 @@ export function useSnippet(originalProps: UseSnippetProps) {
() =>
snippet({
...variantProps,
isFocusVisible,
}),
[...Object.values(variantProps), isFocusVisible],
[...Object.values(variantProps)],
);
const symbolBefore = useMemo(() => {
@ -172,6 +182,33 @@ export function useSnippet(originalProps: UseSnippetProps) {
onCopyProp?.(value);
}, [copy, disableCopy, onCopyProp, children]);
const {isPressed: isCopyPressed, pressProps: copyButtonPressProps} = usePress({
isDisabled: disableCopy,
onPress: onCopy,
});
const getCopyButtonProps = useCallback<PropGetter>(
(props = {}) => ({
...mergeProps(copyButtonPressProps, focusProps, props),
"data-focus": dataAttr(isFocused),
"data-focus-visible": dataAttr(isFocusVisible),
"data-pressed": dataAttr(isCopyPressed),
className: slots.copy({
class: clsx(disableCopy && "opacity-50 cursor-not-allowed", classNames?.copy),
}),
}),
[
slots,
isCopyPressed,
isFocusVisible,
isFocused,
disableCopy,
classNames?.copy,
copyButtonPressProps,
focusProps,
],
);
return {
Component,
as,
@ -186,13 +223,13 @@ export function useSnippet(originalProps: UseSnippetProps) {
symbolBefore,
isMultiLine,
isFocusVisible,
focusProps,
hideCopyButton,
disableCopy,
disableTooltip,
hideSymbol,
tooltipProps,
getSnippetProps,
getCopyButtonProps,
};
}

View File

@ -1,15 +1,14 @@
import type {UserVariantProps, SlotsToClasses, UserSlots} from "@nextui-org/theme";
import type {SlotsToClasses, UserSlots} from "@nextui-org/theme";
import type {AvatarProps} from "@nextui-org/avatar";
import {ReactNode, useMemo, useCallback} from "react";
import {useFocusRing} from "@react-aria/focus";
import {HTMLNextUIProps, PropGetter} from "@nextui-org/system";
import {user} from "@nextui-org/theme";
import {ReactRef, clsx} from "@nextui-org/shared-utils";
import {ReactRef, clsx, dataAttr} from "@nextui-org/shared-utils";
import {useDOMRef} from "@nextui-org/dom-utils";
import {mergeProps} from "@react-aria/utils";
export interface UseUserProps
extends Omit<HTMLNextUIProps<"div", UserVariantProps>, "children" | "isFocusVisible"> {
export interface UseUserProps extends Omit<HTMLNextUIProps<"div">, "children"> {
/**
* Ref to the DOM node.
*/
@ -69,13 +68,13 @@ export function useUser(props: UseUserProps) {
const domRef = useDOMRef(ref);
const {isFocusVisible, focusProps} = useFocusRing();
const {isFocusVisible, isFocused, focusProps} = useFocusRing();
const canBeFocused = useMemo(() => {
return isFocusable || as === "button";
}, [isFocusable, as]);
const slots = useMemo(() => user({isFocusVisible}), [isFocusVisible]);
const slots = useMemo(() => user(), []);
const baseStyles = clsx(classNames?.base, className);
@ -99,6 +98,8 @@ export function useUser(props: UseUserProps) {
() => ({
ref: domRef,
tabIndex: canBeFocused ? 0 : -1,
"data-focus-visible": dataAttr(isFocusVisible),
"data-focused": dataAttr(isFocused),
className: slots.base({
class: clsx(baseStyles, buttonStyles),
}),

View File

@ -43,7 +43,7 @@ const accordionItem = tv({
// focus ring
"data-[focus-visible=true]:outline-none",
"data-[focus-visible=true]:ring-2",
"data-[focus-visible=true]:!ring-primary",
"data-[focus-visible=true]:ring-primary",
"data-[focus-visible=true]:ring-offset-2",
"data-[focus-visible=true]:ring-offset-background",
"data-[focus-visible=true]:dark:ring-offset-background-dark",

View File

@ -31,7 +31,7 @@ const avatar = tv({
// focus ring
"data-[focus-visible=true]:outline-none",
"data-[focus-visible=true]:ring-2",
"data-[focus-visible=true]:!ring-primary",
"data-[focus-visible=true]:ring-primary",
"data-[focus-visible=true]:ring-offset-2",
"data-[focus-visible=true]:ring-offset-background",
"data-[focus-visible=true]:dark:ring-offset-background-dark",

View File

@ -41,7 +41,7 @@ const button = tv({
// focus ring
"data-[focus-visible=true]:outline-none",
"data-[focus-visible=true]:ring-2",
"data-[focus-visible=true]:!ring-primary",
"data-[focus-visible=true]:ring-primary",
"data-[focus-visible=true]:ring-offset-2",
"data-[focus-visible=true]:ring-offset-background",
"data-[focus-visible=true]:dark:ring-offset-background-dark",

View File

@ -33,7 +33,7 @@ const card = tv({
// focus ring
"data-[focus-visible=true]:outline-none",
"data-[focus-visible=true]:ring-2",
"data-[focus-visible=true]:!ring-primary",
"data-[focus-visible=true]:ring-primary",
"data-[focus-visible=true]:ring-offset-2",
"data-[focus-visible=true]:ring-offset-background",
"data-[focus-visible=true]:dark:ring-offset-background-dark",

View File

@ -2,15 +2,13 @@ import type {VariantProps} from "tailwind-variants";
import {tv} from "tailwind-variants";
import {ringClasses} from "../utils";
/**
* DropdownItem wrapper **Tailwind Variants** component
*
* const {base, heading, indicator, trigger, leftIndicator, title, subtitle, content } = dropdownItem({...})
*
* @example
* <div className={base())}>
* <div className={base())} data-focus-visible={boolean} data-hover={boolean}>
* <div className={heading())}>
* <button className={trigger())}>
* <div className={leftIndicator()}>
@ -43,6 +41,13 @@ const dropdownItem = tv({
"rounded-lg",
"outline-none",
"cursor-pointer",
// focus ring
"data-[focus-visible=true]:outline-none",
"data-[focus-visible=true]:ring-2",
"data-[focus-visible=true]:ring-primary",
"data-[focus-visible=true]:ring-offset-2",
"data-[focus-visible=true]:ring-offset-background",
"data-[focus-visible=true]:dark:ring-offset-background-content1",
],
wrapper: "w-full flex flex-col items-start justify-center",
title: "flex-1",
@ -73,13 +78,13 @@ const dropdownItem = tv({
base: "bg-transparent",
},
faded: {
base: "border border-transparent hover:border-neutral hover:bg-neutral-100",
base: "border border-transparent hover:border-neutral data-[hover=true]:bg-neutral-100",
},
flat: {
base: "",
},
shadow: {
base: "hover:shadow-lg",
base: "data-[hover=true]:shadow-lg",
},
},
color: {
@ -109,11 +114,6 @@ const dropdownItem = tv({
base: "opacity-50 pointer-events-none",
},
},
isFocusVisible: {
true: {
base: [...ringClasses],
},
},
disableAnimation: {
true: {},
false: {},
@ -130,42 +130,42 @@ const dropdownItem = tv({
variant: "solid",
color: "neutral",
class: {
base: "hover:bg-neutral hover:text-neutral-contrastText",
base: "data-[hover=true]:bg-neutral data-[hover=true]:text-neutral-contrastText",
},
},
{
variant: "solid",
color: "primary",
class: {
base: "hover:bg-primary hover:text-primary-contrastText",
base: "data-[hover=true]:bg-primary data-[hover=true]:text-primary-contrastText",
},
},
{
variant: "solid",
color: "secondary",
class: {
base: "hover:bg-secondary hover:text-secondary-contrastText",
base: "data-[hover=true]:bg-secondary data-[hover=true]:text-secondary-contrastText",
},
},
{
variant: "solid",
color: "success",
class: {
base: "hover:bg-success hover:text-success-contrastText",
base: "data-[hover=true]:bg-success data-[hover=true]:text-success-contrastText",
},
},
{
variant: "solid",
color: "warning",
class: {
base: "hover:bg-warning hover:text-warning-contrastText",
base: "data-[hover=true]:bg-warning data-[hover=true]:text-warning-contrastText",
},
},
{
variant: "solid",
color: "danger",
class: {
base: "hover:bg-danger hover:text-danger-contrastText",
base: "data-[hover=true]:bg-danger data-[hover=true]:text-danger-contrastText",
},
},
// shadow / color
@ -173,42 +173,48 @@ const dropdownItem = tv({
variant: "shadow",
color: "neutral",
class: {
base: "hover:shadow-neutral/50 hover:bg-neutral hover:text-neutral-contrastText",
base:
"data-[hover=true]:shadow-neutral/50 data-[hover=true]:bg-neutral data-[hover=true]:text-neutral-contrastText",
},
},
{
variant: "shadow",
color: "primary",
class: {
base: "hover:shadow-primary/30 hover:bg-primary hover:text-primary-contrastText",
base:
"data-[hover=true]:shadow-primary/30 data-[hover=true]:bg-primary data-[hover=true]:text-primary-contrastText",
},
},
{
variant: "shadow",
color: "secondary",
class: {
base: "hover:shadow-secondary/30 hover:bg-secondary hover:text-secondary-contrastText",
base:
"data-[hover=true]:shadow-secondary/30 data-[hover=true]:bg-secondary data-[hover=true]:text-secondary-contrastText",
},
},
{
variant: "shadow",
color: "success",
class: {
base: "hover:shadow-success/30 hover:bg-success hover:text-success-contrastText",
base:
"data-[hover=true]:shadow-success/30 data-[hover=true]:bg-success data-[hover=true]:text-success-contrastText",
},
},
{
variant: "shadow",
color: "warning",
class: {
base: "hover:shadow-warning/30 hover:bg-warning hover:text-warning-contrastText",
base:
"data-[hover=true]:shadow-warning/30 data-[hover=true]:bg-warning data-[hover=true]:text-warning-contrastText",
},
},
{
variant: "shadow",
color: "danger",
class: {
base: "hover:shadow-danger/30 hover:bg-danger hover:text-danger-contrastText",
base:
"data-[hover=true]:shadow-danger/30 data-[hover=true]:bg-danger data-[hover=true]:text-danger-contrastText",
},
},
// bordered / color
@ -216,42 +222,42 @@ const dropdownItem = tv({
variant: "bordered",
color: "neutral",
class: {
base: "hover:border-neutral",
base: "data-[hover=true]:border-neutral",
},
},
{
variant: "bordered",
color: "primary",
class: {
base: "hover:border-primary hover:text-primary",
base: "data-[hover=true]:border-primary data-[hover=true]:text-primary",
},
},
{
variant: "bordered",
color: "secondary",
class: {
base: "hover:border-secondary hover:text-secondary",
base: "data-[hover=true]:border-secondary data-[hover=true]:text-secondary",
},
},
{
variant: "bordered",
color: "success",
class: {
base: "hover:border-success hover:text-success",
base: "data-[hover=true]:border-success data-[hover=true]:text-success",
},
},
{
variant: "bordered",
color: "warning",
class: {
base: "hover:border-warning hover:text-warning",
base: "data-[hover=true]:border-warning data-[hover=true]:text-warning",
},
},
{
variant: "bordered",
color: "danger",
class: {
base: "hover:border-danger hover:text-danger",
base: "data-[hover=true]:border-danger data-[hover=true]:text-danger",
},
},
// flat / color
@ -259,42 +265,42 @@ const dropdownItem = tv({
variant: "flat",
color: "neutral",
class: {
base: "hover:bg-neutral-100 hover:text-neutral-contrastText",
base: "data-[hover=true]:bg-neutral-100 data-[hover=true]:text-neutral-contrastText",
},
},
{
variant: "flat",
color: "primary",
class: {
base: "hover:bg-primary-50 hover:text-primary",
base: "data-[hover=true]:bg-primary-50 data-[hover=true]:text-primary",
},
},
{
variant: "flat",
color: "secondary",
class: {
base: "hover:bg-secondary-100 hover:text-secondary",
base: "data-[hover=true]:bg-secondary-100 data-[hover=true]:text-secondary",
},
},
{
variant: "flat",
color: "success",
class: {
base: "hover:bg-success-50 hover:text-success",
base: "data-[hover=true]:bg-success-50 data-[hover=true]:text-success",
},
},
{
variant: "flat",
color: "warning",
class: {
base: "hover:bg-warning-50 hover:text-warning",
base: "data-[hover=true]:bg-warning-50 data-[hover=true]:text-warning",
},
},
{
variant: "flat",
color: "danger",
class: {
base: "hover:bg-danger-50 hover:text-danger",
base: "data-[hover=true]:bg-danger-50 data-[hover=true]:text-danger",
},
},
// faded / color
@ -302,42 +308,42 @@ const dropdownItem = tv({
variant: "faded",
color: "neutral",
class: {
base: "hover:text-neutral-contrastText",
base: "data-[hover=true]:text-neutral-contrastText",
},
},
{
variant: "faded",
color: "primary",
class: {
base: "hover:text-primary",
base: "data-[hover=true]:text-primary",
},
},
{
variant: "faded",
color: "secondary",
class: {
base: "hover:text-secondary",
base: "data-[hover=true]:text-secondary",
},
},
{
variant: "faded",
color: "success",
class: {
base: "hover:text-success",
base: "data-[hover=true]:text-success",
},
},
{
variant: "faded",
color: "warning",
class: {
base: "hover:text-warning",
base: "data-[hover=true]:text-warning",
},
},
{
variant: "faded",
color: "danger",
class: {
base: "hover:text-danger",
base: "data-[hover=true]:text-danger",
},
},
// light / color
@ -345,42 +351,42 @@ const dropdownItem = tv({
variant: "light",
color: "neutral",
class: {
base: "hover:text-neutral-500",
base: "data-[hover=true]:text-neutral-500",
},
},
{
variant: "light",
color: "primary",
class: {
base: "hover:text-primary",
base: "data-[hover=true]:text-primary",
},
},
{
variant: "light",
color: "secondary",
class: {
base: "hover:text-secondary",
base: "data-[hover=true]:text-secondary",
},
},
{
variant: "light",
color: "success",
class: {
base: "hover:text-success",
base: "data-[hover=true]:text-success",
},
},
{
variant: "light",
color: "warning",
class: {
base: "hover:text-warning",
base: "data-[hover=true]:text-warning",
},
},
{
variant: "light",
color: "danger",
class: {
base: "hover:text-danger",
base: "data-[hover=true]:text-danger",
},
},
],

View File

@ -2,7 +2,7 @@ import type {VariantProps} from "tailwind-variants";
import {tv} from "tailwind-variants";
import {colorVariants, ringClasses} from "../utils";
import {colorVariants} from "../utils";
/**
* Pagination wrapper **Tailwind Variants** component
@ -23,7 +23,7 @@ import {colorVariants, ringClasses} from "../utils";
const pagination = tv({
slots: {
base: "flex flex-wrap relative items-center gap-1 max-w-fit",
item: "",
item: [],
prev: "",
next: "",
cursor: [
@ -39,7 +39,12 @@ const pagination = tv({
variants: {
variant: {
bordered: {
item: ["border-1.5", "border-neutral", "bg-transparent", "hover:bg-neutral-100"],
item: [
"border-1.5",
"border-neutral",
"bg-transparent",
"data-[hover=true]:bg-neutral-100",
],
},
light: {
item: "bg-transparent",
@ -102,11 +107,6 @@ const pagination = tv({
base: "opacity-50 pointer-events-none",
},
},
isFocusVisible: {
true: {
wrapper: [...ringClasses],
},
},
showShadow: {
true: {},
},
@ -325,22 +325,32 @@ const pagination = tv({
"items-center",
"justify-center",
"text-neutral-contrastText",
// focus ring
"data-[focus-visible=true]:outline-none",
"data-[focus-visible=true]:ring-2",
"data-[focus-visible=true]:ring-primary",
"data-[focus-visible=true]:ring-offset-2",
"data-[focus-visible=true]:ring-offset-background",
"data-[focus-visible=true]:dark:ring-offset-background-dark",
// disabled
"data-[disabled=true]:opacity-50",
"data-[disabled=true]:pointer-events-none",
],
},
{
slots: ["item", "prev", "next"],
variant: "flat",
class: ["bg-neutral-100", "hover:bg-neutral-200", "active:bg-neutral-300"],
class: ["bg-neutral-100", "data-[hover=true]:bg-neutral-200", "active:bg-neutral-300"],
},
{
slots: ["item", "prev", "next"],
variant: "faded",
class: ["bg-neutral-100", "hover:bg-neutral-200", "active:bg-neutral-300"],
class: ["bg-neutral-100", "data-[hover=true]:bg-neutral-200", "active:bg-neutral-300"],
},
{
slots: ["item", "prev", "next"],
variant: "light",
class: ["hover:bg-neutral-100", "active:bg-neutral-200"],
class: ["data-[hover=true]:bg-neutral-100", "active:bg-neutral-200"],
},
// size
{

View File

@ -2,7 +2,7 @@ import type {VariantProps} from "tailwind-variants";
import {tv} from "tailwind-variants";
import {colorVariants, ringClasses} from "../utils";
import {colorVariants} from "../utils";
/**
* Popover wrapper **Tailwind Variants** component
*
@ -31,6 +31,13 @@ const popover = tv({
"py-1",
"text-base",
"!outline-none",
// focus ring
"data-[focus-visible=true]:outline-none",
"data-[focus-visible=true]:ring-2",
"data-[focus-visible=true]:ring-primary",
"data-[focus-visible=true]:ring-offset-2",
"data-[focus-visible=true]:ring-offset-background",
"data-[focus-visible=true]:dark:ring-offset-background-dark",
],
trigger: ["z-10"],
backdrop: ["hidden"],
@ -111,11 +118,6 @@ const popover = tv({
backdrop: "backdrop-blur-sm backdrop-saturate-150 bg-black/20",
},
},
isFocusVisible: {
true: {
base: [...ringClasses],
},
},
triggerScaleOnOpen: {
true: {
trigger: ["aria-expanded:scale-95", "aria-expanded:opacity-70", "subpixel-antialiased"],

View File

@ -2,7 +2,7 @@ import type {VariantProps} from "tailwind-variants";
import {tv} from "tailwind-variants";
import {ringClasses, colorVariants} from "../utils";
import {colorVariants} from "../utils";
/**
* Snippet wrapper **Tailwind Variants** component
@ -25,7 +25,16 @@ const snippet = tv({
slots: {
base: "inline-flex items-center justify-between space-x-3 rounded-md",
pre: "bg-transparent text-inherit font-mono whitespace-pre-wrap",
copy: "z-10 appearance-none outline-none",
copy: [
"z-10 appearance-none outline-none rounded-sm",
// focus ring
"data-[focus-visible=true]:outline-none",
"data-[focus-visible=true]:ring-1",
"data-[focus-visible=true]:ring-primary",
"data-[focus-visible=true]:ring-offset-2",
"data-[focus-visible=true]:ring-offset-transparent",
"data-[focus-visible=true]:dark:ring-offset-transparent",
],
},
variants: {
variant: {
@ -99,17 +108,6 @@ const snippet = tv({
base: "w-full",
},
},
isFocusVisible: {
true: {
copy: [
...ringClasses,
"ring-1",
"rounded-sm",
"ring-offset-transparent",
"dark:ring-offset-transparent",
],
},
},
},
defaultVariants: {
color: "neutral",
@ -117,7 +115,6 @@ const snippet = tv({
size: "md",
radius: "lg",
fullWidth: false,
isFocusVisible: false,
},
compoundVariants: [
// solid & shadow / color

View File

@ -105,21 +105,6 @@ const toggle = tv({
},
size: {
xs: {
wrapper: "px-0.5 w-7 h-4 mr-1",
thumb: [
"w-3 h-3 text-[0.5rem]",
//checked
"group-data-[checked=true]:ml-3",
// pressed
"group-data-[pressed=true]:w-4",
"group-data-[checked]:group-data-[pressed]:ml-2",
],
startIcon: "text-[0.5rem] left-1",
endIcon: "text-[0.5rem] right-1",
right: "text-[0.5rem]",
label: "text-xs",
},
sm: {
wrapper: "w-8 h-5 mr-1",
thumb: [
"w-3 h-3 text-[0.6rem]",
@ -133,7 +118,7 @@ const toggle = tv({
endIcon: "text-[0.6rem] right-1",
label: "text-sm",
},
md: {
sm: {
wrapper: "w-10 h-6 mr-2",
thumb: [
"w-4 h-4 text-xs",
@ -147,7 +132,7 @@ const toggle = tv({
startIcon: "text-xs",
label: "text-base",
},
lg: {
md: {
wrapper: "w-12 h-7 mr-2",
thumb: [
"w-5 h-5 text-sm",
@ -161,7 +146,7 @@ const toggle = tv({
startIcon: "text-sm",
label: "text-lg",
},
xl: {
lg: {
wrapper: "w-14 h-8 mr-2",
thumb: [
"w-6 h-6 text-base",
@ -175,6 +160,20 @@ const toggle = tv({
startIcon: "text-base",
label: "text-xl",
},
xl: {
wrapper: "w-16 h-9 mr-2",
thumb: [
"w-7 h-7 text-lg",
//checked
"group-data-[checked=true]:ml-7",
// pressed
"group-data-[pressed=true]:w-8",
"group-data-[checked]:group-data-[pressed]:ml-6",
],
endIcon: "text-base",
startIcon: "text-base",
label: "text-xl",
},
},
isDisabled: {
true: {
@ -207,7 +206,7 @@ const toggle = tv({
},
defaultVariants: {
color: "primary",
size: "lg",
size: "md",
isDisabled: false,
disableAnimation: false,
},

View File

@ -1,9 +1,5 @@
import type {VariantProps} from "tailwind-variants";
import {tv} from "tailwind-variants";
import {ringClasses} from "../utils";
/**
* User wrapper **Tailwind Variants** component
*
@ -20,21 +16,22 @@ import {ringClasses} from "../utils";
*/
const user = tv({
slots: {
base: "inline-flex items-center justify-center gap-2",
base: [
"inline-flex items-center justify-center gap-2 rounded-sm",
// focus ring
"data-[focus-visible=true]:outline-none",
"data-[focus-visible=true]:ring-2",
"data-[focus-visible=true]:ring-primary",
"data-[focus-visible=true]:ring-offset-2",
"data-[focus-visible=true]:ring-offset-background",
"data-[focus-visible=true]:dark:ring-offset-background-dark",
],
wrapper: "inline-flex flex-col items-start",
name: "text-sm text-foreground dark:text-foreground-dark",
description: "text-xs text-neutral-500",
},
variants: {
isFocusVisible: {
true: {
base: [...ringClasses, "rounded-sm"],
},
},
},
});
export type UserVariantProps = VariantProps<typeof user>;
export type UserSlots = keyof ReturnType<typeof user>;
export {user};

8
pnpm-lock.yaml generated
View File

@ -1290,6 +1290,12 @@ importers:
'@react-aria/focus':
specifier: ^3.12.0
version: 3.12.0(react@18.2.0)
'@react-aria/interactions':
specifier: ^3.15.0
version: 3.15.0(react@18.2.0)
'@react-aria/utils':
specifier: ^3.16.0
version: 3.16.0(react@18.2.0)
devDependencies:
clean-package:
specifier: 2.2.0
@ -4272,7 +4278,7 @@ packages:
dependencies:
'@babel/runtime': 7.21.0
'@types/react': 18.0.1
clsx: 1.1.0
clsx: 1.2.1
focus-lock: 0.8.1
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)