Skip to content

Commit 351fac9

Browse files
authored
fix(styles): show pointer cursor on clickable elements (#520)
1 parent 1eac3aa commit 351fac9

21 files changed

Lines changed: 162 additions & 5 deletions

File tree

.changeset/clickable-cursor.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
"@cloudflare/kumo": patch
3+
---
4+
5+
fix(styles): show pointer cursor on clickable Kumo elements by default
6+
7+
Adds a global `cursor: pointer` rule scoped to elements rendered by Kumo
8+
components, identified by the new `data-kumo-component` and `data-kumo-part`
9+
attributes. Interactive component roots and parts now opt into the rule by
10+
setting these attributes, which gives the library a stable scoping primitive
11+
that doesn't couple to Tailwind class names.
12+
13+
Components updated to set `data-kumo-component` / `data-kumo-part`:
14+
Button, LinkButton, Link, Checkbox, Radio, Switch, Select (trigger, option),
15+
DropdownMenu (item, link-item, checkbox-item, radio-item, submenu-trigger),
16+
Combobox (trigger, item, clear, chip-remove), Autocomplete (item),
17+
Dialog (trigger, close), Popover (trigger), Tabs (tab),
18+
Collapsible (trigger, default-trigger), Breadcrumbs (link),
19+
TableOfContents (item, group-link), Sidebar (menu-button, menu-sub-button,
20+
trigger, rail), MenuBar (option), Toast (close), SensitiveInput
21+
(toggle-visibility, copy, masked-container).

packages/kumo/src/components/autocomplete/autocomplete.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,8 @@ function List({
231231
function Item({ children, ...props }: AutocompleteBase.Item.Props) {
232232
return (
233233
<AutocompleteBase.Item
234+
data-kumo-component="Autocomplete"
235+
data-kumo-part="item"
234236
{...props}
235237
className="group mx-1.5 grid cursor-pointer grid-cols-[1fr_16px] gap-2 rounded px-2 py-1.5 text-base data-highlighted:bg-kumo-overlay data-selected:font-medium"
236238
>

packages/kumo/src/components/breadcrumbs/breadcrumbs.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ const Link = ({
6868

6969
return (
7070
<LinkComponent
71+
data-kumo-component="Breadcrumbs"
72+
data-kumo-part="link"
7173
to={href}
7274
className="flex min-w-0 max-w-full items-center gap-1 text-kumo-subtle no-underline"
7375
>

packages/kumo/src/components/button/button.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
261261
const button = (
262262
<button
263263
ref={ref}
264+
data-kumo-component="Button"
264265
className={cn(
265266
buttonVariants({ variant, size, shape }),
266267
disabled && "cursor-not-allowed opacity-50",
@@ -346,6 +347,7 @@ export const LinkButton = React.forwardRef<HTMLAnchorElement, LinkButtonProps>(
346347
return (
347348
<LinkComponent
348349
ref={ref}
350+
data-kumo-component="LinkButton"
349351
className={cn(
350352
buttonVariants({ variant, size, shape }),
351353
"flex items-center no-underline!",

packages/kumo/src/components/checkbox/checkbox.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ const CheckboxBase = forwardRef<HTMLButtonElement, CheckboxProps>(
255255
const checkboxControl = (
256256
<BaseCheckbox.Root
257257
ref={ref}
258+
data-kumo-component="Checkbox"
258259
name={name}
259260
checked={checked}
260261
indeterminate={indeterminate}
@@ -340,6 +341,8 @@ const CheckboxItem = forwardRef<HTMLButtonElement, CheckboxItemProps>(
340341

341342
return (
342343
<label
344+
data-kumo-component="Checkbox"
345+
data-kumo-part="item-label"
343346
className={cn(
344347
"m-0 relative inline-flex items-start gap-2",
345348
// Control first (default): checkbox before label
@@ -351,6 +354,8 @@ const CheckboxItem = forwardRef<HTMLButtonElement, CheckboxItemProps>(
351354
>
352355
<BaseCheckbox.Root
353356
ref={ref}
357+
data-kumo-component="Checkbox"
358+
data-kumo-part="item"
354359
value={value}
355360
name={name}
356361
checked={checked}

packages/kumo/src/components/collapsible/collapsible.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ const CollapsibleTrigger = forwardRef<HTMLButtonElement, CollapsibleTriggerProps
8282
return (
8383
<CollapsibleBase.Trigger
8484
ref={ref}
85+
data-kumo-component="Collapsible"
86+
data-kumo-part="trigger"
8587
className={cn("cursor-pointer", className)}
8688
{...props}
8789
/>
@@ -158,6 +160,8 @@ const CollapsibleDefaultTrigger = forwardRef<
158160
return (
159161
<CollapsibleBase.Trigger
160162
ref={ref}
163+
data-kumo-component="Collapsible"
164+
data-kumo-part="default-trigger"
161165
className={cn(
162166
// Defensive resets to prevent global button styles from polluting the trigger
163167
"bg-transparent border-none shadow-none p-0 m-0",

packages/kumo/src/components/combobox/combobox.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,8 @@ function TriggerValue({
266266

267267
return (
268268
<ComboboxBase.Trigger
269+
data-kumo-component="Combobox"
270+
data-kumo-part="trigger"
269271
className={cn(
270272
inputVariants({ size, variant: hasError ? "error" : "default" }),
271273
"relative flex items-center",
@@ -355,6 +357,8 @@ function TriggerInput({
355357
/>
356358

357359
<ComboboxBase.Clear
360+
data-kumo-component="Combobox"
361+
data-kumo-part="clear"
358362
aria-label={clearLabel}
359363
className={cn(
360364
"absolute top-1/2 flex -translate-y-1/2 cursor-pointer bg-transparent p-0",
@@ -366,6 +370,8 @@ function TriggerInput({
366370
</ComboboxBase.Clear>
367371

368372
<ComboboxBase.Trigger
373+
data-kumo-component="Combobox"
374+
data-kumo-part="trigger"
369375
aria-label={showOptionsLabel}
370376
className={cn(
371377
"absolute top-1/2 -translate-y-1/2 flex items-center justify-center cursor-pointer text-kumo-subtle",
@@ -388,6 +394,8 @@ function Item({
388394
}: ComboboxBase.Item.Props & { className?: string }) {
389395
return (
390396
<ComboboxBase.Item
397+
data-kumo-component="Combobox"
398+
data-kumo-part="item"
391399
{...props}
392400
className={cn(
393401
"group mx-1.5 grid grid-cols-[1fr_16px] gap-2 rounded px-2 py-1.5 text-base",
@@ -490,6 +498,8 @@ function Chip({
490498
>
491499
{props.children}
492500
<ComboboxBase.ChipRemove
501+
data-kumo-component="Combobox"
502+
data-kumo-part="chip-remove"
493503
aria-label={removeLabel}
494504
className={cn(
495505
"cursor-pointer rounded-md p-1 hover:bg-kumo-fill-hover",

packages/kumo/src/components/dialog/dialog.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -326,13 +326,25 @@ function DialogTrigger({ children, ...props }: DialogTriggerProps) {
326326

327327
if (role === "alertdialog") {
328328
return (
329-
<AlertDialogBase.Trigger {...(props as BaseAlertDialogTriggerProps)}>
329+
<AlertDialogBase.Trigger
330+
data-kumo-component="Dialog"
331+
data-kumo-part="trigger"
332+
{...(props as BaseAlertDialogTriggerProps)}
333+
>
330334
{children}
331335
</AlertDialogBase.Trigger>
332336
);
333337
}
334338

335-
return <DialogBase.Trigger {...props}>{children}</DialogBase.Trigger>;
339+
return (
340+
<DialogBase.Trigger
341+
data-kumo-component="Dialog"
342+
data-kumo-part="trigger"
343+
{...props}
344+
>
345+
{children}
346+
</DialogBase.Trigger>
347+
);
336348
}
337349

338350
DialogTrigger.displayName = "Dialog.Trigger";
@@ -387,7 +399,11 @@ function DialogClose({ children, ...props }: DialogCloseProps) {
387399
const role = useDialogRole();
388400
const BaseClose =
389401
role === "alertdialog" ? AlertDialogBase.Close : DialogBase.Close;
390-
return <BaseClose {...props}>{children}</BaseClose>;
402+
return (
403+
<BaseClose data-kumo-component="Dialog" data-kumo-part="close" {...props}>
404+
{children}
405+
</BaseClose>
406+
);
391407
}
392408

393409
DialogClose.displayName = "Dialog.Close";

packages/kumo/src/components/dropdown/dropdown.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ const DropdownMenuSubTrigger = React.forwardRef<
6363
>(({ className, inset, children, icon: IconComponent, ...props }, ref) => (
6464
<DropdownMenuPrimitive.SubmenuTrigger
6565
ref={ref}
66+
data-kumo-component="DropdownMenu"
67+
data-kumo-part="submenu-trigger"
6668
className={cn(
6769
"flex cursor-default items-center rounded-sm text-base outline-hidden select-none", // base styles
6870
"px-2 py-1.5", // spacing
@@ -237,6 +239,8 @@ const DropdownMenuItem = React.forwardRef<
237239
return (
238240
<DropdownMenuPrimitive.Item
239241
ref={ref}
242+
data-kumo-component="DropdownMenu"
243+
data-kumo-part="item"
240244
className={cn(
241245
"relative flex cursor-default items-center rounded-md px-2 py-1.5 text-base outline-hidden select-none focus:text-kumo-default focus:ring-kumo-focus/50 focus-visible:ring-2 focus-visible:ring-kumo-brand data-disabled:pointer-events-none data-disabled:opacity-50 data-highlighted:bg-kumo-overlay",
242246
inset && "pl-8",
@@ -300,6 +304,8 @@ const DropdownMenuLinkItem = React.forwardRef<
300304
return (
301305
<DropdownMenuPrimitive.LinkItem
302306
ref={ref}
307+
data-kumo-component="DropdownMenu"
308+
data-kumo-part="link-item"
303309
className={cn(
304310
"relative flex cursor-default items-center rounded-md px-2 py-1.5 text-base outline-hidden select-none",
305311
"focus:text-kumo-default focus:ring-kumo-focus/50 focus-visible:ring-2 focus-visible:ring-kumo-brand data-disabled:pointer-events-none data-disabled:opacity-50 data-highlighted:bg-kumo-overlay",
@@ -325,6 +331,8 @@ const DropdownMenuCheckboxItem = React.forwardRef<
325331
>(({ className, children, checked, ...props }, ref) => (
326332
<DropdownMenuPrimitive.CheckboxItem
327333
ref={ref}
334+
data-kumo-component="DropdownMenu"
335+
data-kumo-part="checkbox-item"
328336
className={cn(
329337
"relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-base outline-hidden transition-colors select-none focus:bg-kumo-tint focus:text-kumo-default focus:ring-kumo-focus/50 focus-visible:ring-2 focus-visible:ring-kumo-brand data-disabled:pointer-events-none data-disabled:opacity-50",
330338
className,
@@ -393,6 +401,8 @@ const DropdownMenuRadioItem = React.forwardRef<
393401
>(({ className, children, inset, icon: IconComponent, ...props }, ref) => (
394402
<DropdownMenuPrimitive.RadioItem
395403
ref={ref}
404+
data-kumo-component="DropdownMenu"
405+
data-kumo-part="radio-item"
396406
className={cn(
397407
"relative flex cursor-default items-center rounded-md px-2 py-1.5 text-base outline-hidden select-none",
398408
"data-disabled:pointer-events-none data-disabled:opacity-50 data-highlighted:bg-kumo-tint",

packages/kumo/src/components/link/link.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,13 @@ const LinkBase = forwardRef<HTMLAnchorElement, LinkProps>(function Link(
175175
}, []); // eslint-disable-line react-hooks/exhaustive-deps -- one-time warning
176176
}
177177

178-
const defaultProps: useRender.ElementProps<"a"> = {
178+
const defaultProps = {
179+
"data-kumo-component": "Link",
179180
className: cn(
180181
linkVariants({ variant }),
181182
"group/link inline-flex items-center gap-[0.1875em]",
182183
),
183-
};
184+
} as useRender.ElementProps<"a">;
184185

185186
const element = useRender({
186187
render: render ?? <LinkComponent />,

0 commit comments

Comments
 (0)