From dc388c27b21e81ddc85e155d13e0e4769a1e9f18 Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Sun, 24 Nov 2024 00:23:58 +0900 Subject: [PATCH 01/15] Write chip component document using joy ui component --- .../components/02-data-display/04-chip.mdx | 366 ++++++++++++++++++ apps/website/src/demos/chip/Basics.tsx | 15 + apps/website/src/demos/chip/Clickable.tsx | 34 ++ .../src/demos/chip/ClickableAndDeletable.tsx | 32 ++ apps/website/src/demos/chip/Decorators.tsx | 19 + apps/website/src/demos/chip/DeleteButton.tsx | 37 ++ apps/website/src/demos/chip/WithCheckbox.tsx | 68 ++++ apps/website/src/demos/chip/WithRadio.tsx | 71 ++++ apps/website/src/demos/chip/index.ts | 7 + 9 files changed, 649 insertions(+) create mode 100644 apps/website/docs/components/02-data-display/04-chip.mdx create mode 100644 apps/website/src/demos/chip/Basics.tsx create mode 100644 apps/website/src/demos/chip/Clickable.tsx create mode 100644 apps/website/src/demos/chip/ClickableAndDeletable.tsx create mode 100644 apps/website/src/demos/chip/Decorators.tsx create mode 100644 apps/website/src/demos/chip/DeleteButton.tsx create mode 100644 apps/website/src/demos/chip/WithCheckbox.tsx create mode 100644 apps/website/src/demos/chip/WithRadio.tsx create mode 100644 apps/website/src/demos/chip/index.ts diff --git a/apps/website/docs/components/02-data-display/04-chip.mdx b/apps/website/docs/components/02-data-display/04-chip.mdx new file mode 100644 index 0000000..0c47eed --- /dev/null +++ b/apps/website/docs/components/02-data-display/04-chip.mdx @@ -0,0 +1,366 @@ +--- +sidebar_position: 4 +slug: /components/chip +--- + +import { + ChipBasics, + ChipDecorators, + ChipDeleteButton, + ChipClickable, + ChipClickableAndDeletable, + ChipWithRadio, + ChipWithCheckbox, +} from '@site/src/demos/chip'; + +# Chip + + + +Chip generates a compact element that can represent an input, attribute, or action. + +## Basics + +```tsx +import { Chip } from 'tailwind-joy/components'; +``` + +Chips comes with medium size, neutral color, and soft variant set by default. + + + +```tsx +import { Chip as JoyChip } from '@mui/joy'; +import { CssVarsProvider } from '@mui/joy/styles'; + +export function ChipBasics() { + return ( + +
+ This is a chip +
+
+ ); +} +``` + +## Customization + +### Decorators + +Use the `startDecorator` and/or `endDecorator` props to add supporting icons to the chip. + + + +```tsx +import { Chip as JoyChip } from '@mui/joy'; +import { CssVarsProvider } from '@mui/joy/styles'; +import CloudIcon from '@mui/icons-material/Cloud'; +import SunIcon from '@mui/icons-material/LightMode'; +import { Box } from 'tailwind-joy/components'; + +export function ChipDecorators() { + return ( + + + }>Today is sunny + }>Tomorrow is cloudy + + + ); +} +``` + +### Delete button + +To add a delete action inside a chip, use the complementary `ChipDelete` component. + +The `onDelete` callback is fired on `ChipDelete` either when: + +- `Backspace`, `Enter` or `Delete` is pressed. +- The `ChipDelete` is clicked. + + + +```tsx +import { Chip as JoyChip, ChipDelete as JoyChipDelete } from '@mui/joy'; +import { CssVarsProvider } from '@mui/joy/styles'; +import { Box } from 'tailwind-joy/components'; + +export function ChipDeleteButton() { + return ( + + + alert('Delete')} />} + > + Delete + + alert('Delete')} />} + > + Delete + + alert('Delete')} />} + > + Delete + + + + ); +} +``` + +### Clickable + +To make chips clickable, pass a function to the `onClick` prop. + + + +```tsx +import { Chip as JoyChip } from '@mui/joy'; +import { CssVarsProvider } from '@mui/joy/styles'; +import CheckIcon from '@mui/icons-material/Check'; +import { Box } from 'tailwind-joy/components'; + +export function ChipClickable() { + return ( + + + + Octocat + + } + endDecorator={} + onClick={() => alert('You clicked the Tailwind Joy Chip!')} + > + Octocat + + + + ); +} +``` + +### Clickable and deletable + +Use both the `onClick` prop and the complementary `ChipDelete` component to make a chip support two actions. + + + +```tsx +import { Chip as JoyChip, ChipDelete as JoyChipDelete } from '@mui/joy'; +import { CssVarsProvider } from '@mui/joy/styles'; +import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; +import { Box } from 'tailwind-joy/components'; + +export function ChipClickableAndDeletable() { + return ( + + + alert('You clicked the chip!')} + endDecorator={ + alert('You clicked the delete button!')} + > + + + } + > + Clear + + + + ); +} +``` + +### With radio + +Common to filtering UIs, wrap the `Radio` component with the `Chip` to use them together. +Use radios when you want to enable single selection. + + + +```tsx +import { Chip as JoyChip } from '@mui/joy'; +import { CssVarsProvider } from '@mui/joy/styles'; +import CheckIcon from '@mui/icons-material/Check'; +import { useState } from 'react'; +import { Box, Radio, RadioGroup, Typography } from 'tailwind-joy/components'; + +export function ChipWithRadio() { + const [selected, setSelected] = useState(''); + + return ( + + +
+ + Best Movie + + + {[ + 'Star trek', + 'Batman', + 'Spider man', + 'Eternals', + 'Shang chi', + 'Jungle cruise', + 'No time to die', + 'Thor', + 'The hulk', + ].map((name) => { + const checked = selected === name; + + return ( + + ) + } + > + { + if (event.target.checked) { + setSelected(name); + } + }} + /> + + ); + })} + +
+
+
+ ); +} +``` + +### With checkbox + +Similar to the above, wrap the `Checkbox` component with the `Chip` to use them together. +Use checkboxes when you want to enable multiple selection. + + + +```tsx +import { Chip as JoyChip } from '@mui/joy'; +import { CssVarsProvider } from '@mui/joy/styles'; +import CheckIcon from '@mui/icons-material/Check'; +import { useState } from 'react'; +import { Box, Checkbox, Typography } from 'tailwind-joy/components'; + +export function ChipWithCheckbox() { + const [selected, setSelected] = useState([]); + + return ( + + +
+ + Favorite Movies + + + {[ + 'Star trek', + 'Batman', + 'Spider man', + 'Eternals', + 'Shang chi', + 'Jungle cruise', + 'No time to die', + 'Thor', + 'The hulk', + ].map((name) => { + const checked = selected.includes(name); + + return ( + + ) + } + > + { + setSelected((names) => + !event.target.checked + ? names.filter((n) => n !== name) + : [...names, name], + ); + }} + /> + + ); + })} + +
+
+
+ ); +} +``` + +## Anatomy + + + +## API + +See the documentation below for a complete reference to all of the props available to the components mentioned here. + +- [``](../apis/box) +- [``](../apis/checkbox) +- `` +- `` +- [``](../apis/radio) +- [``](../apis/radio-group) +- [``](../apis/typography) diff --git a/apps/website/src/demos/chip/Basics.tsx b/apps/website/src/demos/chip/Basics.tsx new file mode 100644 index 0000000..e6ab69e --- /dev/null +++ b/apps/website/src/demos/chip/Basics.tsx @@ -0,0 +1,15 @@ +import { Chip as JoyChip } from '@mui/joy'; +import { CssVarsProvider } from '@mui/joy/styles'; +import { DisplayStand } from '@site/src/components/docs/DisplayStand'; + +export function ChipBasics() { + return ( + + +
+ This is a chip +
+
+
+ ); +} diff --git a/apps/website/src/demos/chip/Clickable.tsx b/apps/website/src/demos/chip/Clickable.tsx new file mode 100644 index 0000000..2ee343f --- /dev/null +++ b/apps/website/src/demos/chip/Clickable.tsx @@ -0,0 +1,34 @@ +import { Chip as JoyChip } from '@mui/joy'; +import { CssVarsProvider } from '@mui/joy/styles'; +import CheckIcon from '@mui/icons-material/Check'; +import { Box } from 'tailwind-joy/components'; +import { DisplayStand } from '@site/src/components/docs/DisplayStand'; + +export function ChipClickable() { + return ( + + + + + Octocat + + } + endDecorator={} + onClick={() => alert('You clicked the Tailwind Joy Chip!')} + > + Octocat + + + + + ); +} diff --git a/apps/website/src/demos/chip/ClickableAndDeletable.tsx b/apps/website/src/demos/chip/ClickableAndDeletable.tsx new file mode 100644 index 0000000..bd14541 --- /dev/null +++ b/apps/website/src/demos/chip/ClickableAndDeletable.tsx @@ -0,0 +1,32 @@ +import { Chip as JoyChip, ChipDelete as JoyChipDelete } from '@mui/joy'; +import { CssVarsProvider } from '@mui/joy/styles'; +import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; +import { Box } from 'tailwind-joy/components'; +import { DisplayStand } from '@site/src/components/docs/DisplayStand'; + +export function ChipClickableAndDeletable() { + return ( + + + + alert('You clicked the chip!')} + endDecorator={ + alert('You clicked the delete button!')} + > + + + } + > + Clear + + + + + ); +} diff --git a/apps/website/src/demos/chip/Decorators.tsx b/apps/website/src/demos/chip/Decorators.tsx new file mode 100644 index 0000000..cb32bca --- /dev/null +++ b/apps/website/src/demos/chip/Decorators.tsx @@ -0,0 +1,19 @@ +import { Chip as JoyChip } from '@mui/joy'; +import { CssVarsProvider } from '@mui/joy/styles'; +import CloudIcon from '@mui/icons-material/Cloud'; +import SunIcon from '@mui/icons-material/LightMode'; +import { Box } from 'tailwind-joy/components'; +import { DisplayStand } from '@site/src/components/docs/DisplayStand'; + +export function ChipDecorators() { + return ( + + + + }>Today is sunny + }>Tomorrow is cloudy + + + + ); +} diff --git a/apps/website/src/demos/chip/DeleteButton.tsx b/apps/website/src/demos/chip/DeleteButton.tsx new file mode 100644 index 0000000..b2d02b2 --- /dev/null +++ b/apps/website/src/demos/chip/DeleteButton.tsx @@ -0,0 +1,37 @@ +import { Chip as JoyChip, ChipDelete as JoyChipDelete } from '@mui/joy'; +import { CssVarsProvider } from '@mui/joy/styles'; +import { Box } from 'tailwind-joy/components'; +import { DisplayStand } from '@site/src/components/docs/DisplayStand'; + +export function ChipDeleteButton() { + return ( + + + + alert('Delete')} />} + > + Delete + + alert('Delete')} />} + > + Delete + + alert('Delete')} />} + > + Delete + + + + + ); +} diff --git a/apps/website/src/demos/chip/WithCheckbox.tsx b/apps/website/src/demos/chip/WithCheckbox.tsx new file mode 100644 index 0000000..df655c0 --- /dev/null +++ b/apps/website/src/demos/chip/WithCheckbox.tsx @@ -0,0 +1,68 @@ +import { Chip as JoyChip } from '@mui/joy'; +import { CssVarsProvider } from '@mui/joy/styles'; +import CheckIcon from '@mui/icons-material/Check'; +import { useState } from 'react'; +import { Box, Checkbox, Typography } from 'tailwind-joy/components'; +import { DisplayStand } from '@site/src/components/docs/DisplayStand'; + +export function ChipWithCheckbox() { + const [selected, setSelected] = useState([]); + + return ( + + + +
+ + Favorite Movies + + + {[ + 'Star trek', + 'Batman', + 'Spider man', + 'Eternals', + 'Shang chi', + 'Jungle cruise', + 'No time to die', + 'Thor', + 'The hulk', + ].map((name) => { + const checked = selected.includes(name); + + return ( + + ) + } + > + { + setSelected((names) => + !event.target.checked + ? names.filter((n) => n !== name) + : [...names, name], + ); + }} + /> + + ); + })} + +
+
+
+
+ ); +} diff --git a/apps/website/src/demos/chip/WithRadio.tsx b/apps/website/src/demos/chip/WithRadio.tsx new file mode 100644 index 0000000..bbd107e --- /dev/null +++ b/apps/website/src/demos/chip/WithRadio.tsx @@ -0,0 +1,71 @@ +import { Chip as JoyChip } from '@mui/joy'; +import { CssVarsProvider } from '@mui/joy/styles'; +import CheckIcon from '@mui/icons-material/Check'; +import { useState } from 'react'; +import { Box, Radio, RadioGroup, Typography } from 'tailwind-joy/components'; +import { DisplayStand } from '@site/src/components/docs/DisplayStand'; + +export function ChipWithRadio() { + const [selected, setSelected] = useState(''); + + return ( + + + +
+ + Best Movie + + + {[ + 'Star trek', + 'Batman', + 'Spider man', + 'Eternals', + 'Shang chi', + 'Jungle cruise', + 'No time to die', + 'Thor', + 'The hulk', + ].map((name) => { + const checked = selected === name; + + return ( + + ) + } + > + { + if (event.target.checked) { + setSelected(name); + } + }} + /> + + ); + })} + +
+
+
+
+ ); +} diff --git a/apps/website/src/demos/chip/index.ts b/apps/website/src/demos/chip/index.ts new file mode 100644 index 0000000..1ef820c --- /dev/null +++ b/apps/website/src/demos/chip/index.ts @@ -0,0 +1,7 @@ +export { ChipBasics } from './Basics'; +export { ChipDecorators } from './Decorators'; +export { ChipDeleteButton } from './DeleteButton'; +export { ChipClickable } from './Clickable'; +export { ChipClickableAndDeletable } from './ClickableAndDeletable'; +export { ChipWithRadio } from './WithRadio'; +export { ChipWithCheckbox } from './WithCheckbox'; From 28d04a29928711607e1b3ef58de6783bd4908e61 Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Sun, 24 Nov 2024 14:17:15 +0900 Subject: [PATCH 02/15] Implements chip component --- packages/tailwind-joy/src/components.ts | 1 + packages/tailwind-joy/src/components/Chip.tsx | 397 ++++++++++++++++++ .../src/plugins/safelist-generator.ts | 2 + 3 files changed, 400 insertions(+) create mode 100644 packages/tailwind-joy/src/components/Chip.tsx diff --git a/packages/tailwind-joy/src/components.ts b/packages/tailwind-joy/src/components.ts index 9d182ed..b5044f5 100644 --- a/packages/tailwind-joy/src/components.ts +++ b/packages/tailwind-joy/src/components.ts @@ -3,6 +3,7 @@ export { Box } from './components/Box'; export { Button } from './components/Button'; export { ButtonGroup } from './components/ButtonGroup'; export { Checkbox } from './components/Checkbox'; +export { Chip } from './components/Chip'; export { CircularProgress } from './components/CircularProgress'; export { Divider } from './components/Divider'; export { IconAdapter } from './components/IconAdapter'; diff --git a/packages/tailwind-joy/src/components/Chip.tsx b/packages/tailwind-joy/src/components/Chip.tsx new file mode 100644 index 0000000..5b14472 --- /dev/null +++ b/packages/tailwind-joy/src/components/Chip.tsx @@ -0,0 +1,397 @@ +import { clsx } from 'clsx'; +import type { ComponentProps, ForwardedRef, ReactNode } from 'react'; +import { createContext, forwardRef, createElement, useMemo } from 'react'; +import { r, twMerge } from '../base/alias'; +import { + focus, + backgroundColor, + borderColor, + textColor, + toVariableClass, +} from '../base/modifier'; +import { theme } from '../base/theme'; +import { baseTokens, colorTokens } from '../base/tokens'; +import type { + BaseVariants, + GeneratorInput, + GenericComponentPropsWithVariants, +} from '../base/types'; +import { excludeClassName } from '../base/utils'; + +type PassingProps = Pick, 'disabled' | 'onClick'>; + +export const ChipContext = createContext<{ disabled?: boolean }>({}); + +function chipRootVariants( + props?: BaseVariants & { + borderRadius?: boolean; + clickable?: boolean; + visuallyDisabled?: boolean; + }, +) { + const { + color = 'neutral', + size = 'md', + variant = 'soft', + borderRadius = false, + clickable = false, + visuallyDisabled = false, + } = props ?? {}; + + return twMerge( + clsx([ + 'tj-chip-root group/tj-chip', + r`[--Chip-decoratorChildOffset:min(calc(var(--Chip-paddingInline)-(var(--\_Chip-minHeight)-2*var(--variant-borderWidth,0px)-var(--Chip-decoratorChildHeight))/2),var(--Chip-paddingInline))]`, + r`[--Chip-decoratorChildRadius:max(var(--\_Chip-radius)-var(--variant-borderWidth,0px)-var(--\_Chip-paddingBlock),min(var(--\_Chip-paddingBlock)+var(--variant-borderWidth,0px),var(--\_Chip-radius)/2))]`, + '[--Chip-deleteRadius:var(--Chip-decoratorChildRadius)]', + '[--Chip-deleteSize:var(--Chip-decoratorChildHeight)]', + '[--Avatar-radius:var(--Chip-decoratorChildRadius)]', + '[--Avatar-size:var(--Chip-decoratorChildHeight)]', + '[--Icon-margin:initial]', + '[--Icon-color:currentColor]', + r`[--unstable_actionRadius:var(--\_Chip-radius)]`, + size === 'sm' && [ + '[--Chip-paddingInline:0.375rem]', + r`[--Chip-decoratorChildHeight:calc(var(--\_Chip-minHeight)-2*var(--variant-borderWidth))]`, + '[--Icon-fontSize:0.875rem]', + '[--_Chip-minHeight:var(--Chip-minHeight,1.25rem)]', + 'gap-[3px]', + ], + size === 'md' && [ + '[--Chip-paddingInline:0.5rem]', + r`[--Chip-decoratorChildHeight:calc(var(--\_Chip-minHeight)-0.25rem-2*var(--variant-borderWidth))]`, + '[--Icon-fontSize:1rem]', + '[--_Chip-minHeight:var(--Chip-minHeight,1.5rem)]', + 'gap-[0.25rem]', + ], + size === 'lg' && [ + '[--Chip-paddingInline:0.75rem]', + r`[--Chip-decoratorChildHeight:calc(var(--\_Chip-minHeight)-0.375rem-2*var(--variant-borderWidth))]`, + '[--Icon-fontSize:1.125rem]', + '[--_Chip-minHeight:var(--Chip-minHeight,1.75rem)]', + 'gap-[0.375rem]', + ], + '[--_Chip-radius:var(--Chip-radius,1.5rem)]', + r`[--_Chip-paddingBlock:max((var(--\_Chip-minHeight)-2*var(--variant-borderWidth,0px)-var(--Chip-decoratorChildHeight))/2,0px)]`, + r`min-h-[var(--\_Chip-minHeight)]`, + 'max-w-max', + 'ps-[var(--Chip-paddingInline)] pe-[var(--Chip-paddingInline)]', + r`rounded-[var(--\_Chip-radius)]`, + 'relative', + 'inline-flex', + 'items-center', + 'justify-center', + 'whitespace-nowrap', + 'no-underline', + 'align-middle', + 'box-border', + size === 'sm' && theme.typography['body-xs'].className, + size === 'md' && theme.typography['body-sm'].className, + size === 'lg' && theme.typography['body-md'].className, + 'font-medium', + visuallyDisabled && + textColor(theme.variants[`${variant}Disabled`][color].tokens.color), + !clickable + ? [ + colorTokens.background.surface, + theme.variants[variant][color].className, + visuallyDisabled && [ + 'pointer-events-none cursor-default [--Icon-color:currentColor] dark:[--Icon-color:currentColor]', + textColor( + theme.variants[`${variant}Disabled`][color].tokens.color, + ), + backgroundColor( + theme.variants[`${variant}Disabled`][color].tokens + .backgroundColor, + ), + borderColor( + theme.variants[`${variant}Disabled`][color].tokens.borderColor, + ), + ], + ] + : [ + '[--variant-borderWidth:0px]', + textColor(theme.variants[variant][color].tokens.color), + ], + borderRadius && '[--_Chip-radius:var(--tj-Chip-borderRadius)]', + ]), + ); +} + +function chipLabelVariants(props?: { clickable?: boolean }) { + const { clickable = false } = props ?? {}; + + return twMerge( + clsx([ + 'tj-chip-label', + 'inline-block', + 'overflow-hidden', + 'text-ellipsis', + 'order-1', + '[min-inline-size:0]', + 'grow', + clickable && ['z-[1]', 'pointer-events-none'], + ]), + ); +} + +function chipActionVariants(props?: Pick) { + const { color = 'neutral', variant = 'soft' } = props ?? {}; + + return twMerge( + clsx([ + 'tj-chip-action', + color !== 'neutral' || variant === 'solid' + ? '[--Icon-color:currentColor] dark:[--Icon-color:currentColor]' + : toVariableClass(baseTokens.text.icon, 'Icon-color'), + 'absolute', + 'z-0', + 'inset-0', + 'w-full', + 'border-none', + 'cursor-pointer', + 'p-[initial]', + 'm-[initial]', + 'bg-[color:initial]', + 'no-underline', + 'rounded-[inherit]', + [focus('outline-2 outline outline-offset-2'), colorTokens.focusVisible], + [ + colorTokens.background.surface, + theme.variants[variant][color].className, + ], + theme.variants[`${variant}Hover`][color].className, + theme.variants[`${variant}Active`][color].className, + theme.variants[`${variant}Disabled`][color].className, + ]), + ); +} + +function chipStartDecoratorVariants() { + return twMerge( + clsx([ + 'tj-chip-start-decorator', + '[--Avatar-marginInlineStart:calc(var(--Chip-decoratorChildOffset)*-1)]', + '[--IconButton-margin:0_calc(-1*var(--Chip-paddingInline)/3)_0_calc(var(--Chip-decoratorChildOffset)*-1)]', + '[--Icon-margin:0_0_0_calc(var(--Chip-paddingInline)/-4)]', + '[display:inherit]', + 'order-none', + 'z-[1]', + 'pointer-events-none', + ]), + ); +} + +function chipEndDecoratorVariants() { + return twMerge( + clsx([ + 'tj-chip-end-decorator', + '[--IconButton-margin:0_calc(var(--Chip-decoratorChildOffset)*-1)_0_calc(-1*var(--Chip-paddingInline)/3)]', + '[--Icon-margin:0_calc(var(--Chip-paddingInline)/-4)_0_0]', + '[display:inherit]', + 'order-2', + 'z-[1]', + 'pointer-events-none', + ]), + ); +} + +type ChipRootVariants = BaseVariants & { + endDecorator?: ReactNode; + startDecorator?: ReactNode; +} & { + slotProps?: { + root?: ComponentProps<'div'>; + label?: ComponentProps<'span'>; + action?: ComponentProps<'button'>; + startDecorator?: ComponentProps<'span'>; + endDecorator?: ComponentProps<'span'>; + }; +} & PassingProps; + +type ChipRootProps = GenericComponentPropsWithVariants< + 'div', + ChipRootVariants, + T +>; + +function ChipRoot< + T extends keyof JSX.IntrinsicElements | undefined = undefined, +>( + { + // ---- passing props ---- + disabled, + onClick, + // ----------------------- + + // ---- non-passing props ---- + // base variants + color, + size, + variant, + + // non-base variants + className, + endDecorator, + startDecorator, + style, + + // slot props + slotProps = {}, + + // others + component = 'div', + children, + ...otherProps + // --------------------------- + }: ChipRootProps, + ref: ForwardedRef, +) { + const slotPropsWithoutClassName = useMemo( + () => excludeClassName(slotProps), + [slotProps], + ); + + const resolvedClassNames = twMerge(className).split(' '); + const resolvedBorderRadiusWithArbitraryValue = useMemo(() => { + const regExp = /^rounded-\[([^\]]+)\]$/; + + return resolvedClassNames + .filter((text) => regExp.test(text)) + .at(0) + ?.replace(regExp, '$1'); + }, [resolvedClassNames]); + const resolvedBorderRadiusWithArbitraryProperty = useMemo(() => { + const regExp = /^\[border-radius:([^\]]+)\]$/; + + return resolvedClassNames + .filter((text) => regExp.test(text)) + .at(0) + ?.replace(regExp, '$1'); + }, [resolvedClassNames]); + + const instanceBorderRadius = + resolvedBorderRadiusWithArbitraryProperty || + resolvedBorderRadiusWithArbitraryValue; + const instanceClickable = Boolean(onClick) || Boolean(slotProps.action); + const visuallyDisabled = Boolean(disabled); + + return ( + + {createElement( + component, + { + ref, + className: twMerge( + chipRootVariants({ + color, + size, + variant, + borderRadius: instanceBorderRadius !== undefined, + clickable: instanceClickable, + visuallyDisabled, + }), + className, + slotProps.root?.className ?? '', + ), + style: { + ...style, + '--tj-Chip-borderRadius': instanceBorderRadius, + }, + ...otherProps, + ...(slotPropsWithoutClassName.root ?? {}), + }, + <> + {instanceClickable && ( +