From e18b6d1547b6c1c4aff569549fb0315e0312d7c0 Mon Sep 17 00:00:00 2001 From: Robb Niznik Date: Tue, 19 Nov 2024 10:56:10 -0500 Subject: [PATCH 1/2] feat(components): add `Perceivable` to support `aria-disabled` for RAC --- .changeset/chatty-bags-care.md | 5 +++ .../components/__tests__/Perceivable.spec.tsx | 38 +++++++++++++++++++ packages/components/package.json | 1 + packages/components/src/Button.tsx | 4 ++ packages/components/src/IconButton.tsx | 5 ++- packages/components/src/Perceivable.tsx | 37 ++++++++++++++++++ packages/components/src/index.ts | 1 + .../components/src/styles/base.module.css | 5 +++ .../stories/composition.stories.tsx | 37 +++++++++++++++++- pnpm-lock.yaml | 3 ++ 10 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 .changeset/chatty-bags-care.md create mode 100644 packages/components/__tests__/Perceivable.spec.tsx create mode 100644 packages/components/src/Perceivable.tsx diff --git a/.changeset/chatty-bags-care.md b/.changeset/chatty-bags-care.md new file mode 100644 index 000000000..df26bfc72 --- /dev/null +++ b/.changeset/chatty-bags-care.md @@ -0,0 +1,5 @@ +--- +"@launchpad-ui/components": patch +--- + +Add `Perceivable` to support `aria-disabled` for RAC diff --git a/packages/components/__tests__/Perceivable.spec.tsx b/packages/components/__tests__/Perceivable.spec.tsx new file mode 100644 index 000000000..a76727627 --- /dev/null +++ b/packages/components/__tests__/Perceivable.spec.tsx @@ -0,0 +1,38 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { render, screen, userEvent, waitFor } from '../../../test/utils'; +import { Button, Perceivable, Tooltip, TooltipTrigger } from '../src'; + +describe('Perceivable', () => { + it('sets aria-disabled', async () => { + render( + + + + Message + + , + ); + + await waitFor(() => { + expect(screen.getByRole('button', { hidden: true })).toHaveAttribute('aria-disabled', 'true'); + }); + }); + + it('prevents events', async () => { + const spy = vi.fn(); + const user = userEvent.setup(); + + render( + + + + Message + + , + ); + + await user.click(screen.getByRole('button', { hidden: true })); + expect(spy).toHaveBeenCalledTimes(0); + }); +}); diff --git a/packages/components/package.json b/packages/components/package.json index a85f4fdde..2db799a38 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -36,6 +36,7 @@ "@internationalized/date": "3.5.6", "@launchpad-ui/icons": "workspace:~", "@launchpad-ui/tokens": "workspace:~", + "@react-aria/focus": "3.18.4", "@react-aria/toast": "3.0.0-beta.17", "@react-aria/utils": "3.25.3", "@react-stately/toast": "3.0.0-beta.6", diff --git a/packages/components/src/Button.tsx b/packages/components/src/Button.tsx index b0471a1e7..960b36e75 100644 --- a/packages/components/src/Button.tsx +++ b/packages/components/src/Button.tsx @@ -15,6 +15,7 @@ import { } from 'react-aria-components'; import { input } from './Input'; +import { PerceivableContext } from './Perceivable'; import { ProgressBar } from './ProgressBar'; import styles from './styles/Button.module.css'; @@ -47,9 +48,12 @@ const _Button = ( ) => { const selectContext = useSlottedContext(SelectContext); const state = useContext(SelectStateContext); + const ctx = useContext(PerceivableContext); + return ( state diff --git a/packages/components/src/IconButton.tsx b/packages/components/src/IconButton.tsx index a6e5fc1a4..1f908c743 100644 --- a/packages/components/src/IconButton.tsx +++ b/packages/components/src/IconButton.tsx @@ -7,10 +7,11 @@ import type { ButtonVariants } from './Button'; import { Icon } from '@launchpad-ui/icons'; import { cva, cx } from 'class-variance-authority'; -import { forwardRef } from 'react'; +import { forwardRef, useContext } from 'react'; import { Button as AriaButton, composeRenderProps } from 'react-aria-components'; import { button } from './Button'; +import { PerceivableContext } from './Perceivable'; import styles from './styles/IconButton.module.css'; const iconButton = cva(styles.base, { @@ -43,9 +44,11 @@ const _IconButton = ( { size = 'medium', variant = 'default', icon, ...props }: IconButtonProps, ref: ForwardedRef, ) => { + const ctx = useContext(PerceivableContext); return ( cx(button({ ...renderProps, size, variant, className }), iconButton({ size })), diff --git a/packages/components/src/Perceivable.tsx b/packages/components/src/Perceivable.tsx new file mode 100644 index 000000000..a129b792f --- /dev/null +++ b/packages/components/src/Perceivable.tsx @@ -0,0 +1,37 @@ +import type { KeyboardEvents, PressEvents } from '@react-types/shared'; +import type { ReactNode } from 'react'; + +import { FocusableProvider } from '@react-aria/focus'; +import { createContext } from 'react'; +import { Provider } from 'react-aria-components'; + +interface InteractionProps extends KeyboardEvents, PressEvents {} + +interface PerceivableProps { + children: ReactNode; +} + +const PerceivableContext = createContext({}); + +const Perceivable = ({ children }: PerceivableProps) => { + const props = { + onPress: undefined, + onPressStart: undefined, + onPressEnd: undefined, + onPressChange: undefined, + onPressUp: undefined, + onKeyDown: undefined, + onKeyUp: undefined, + onClick: undefined, + href: undefined, + }; + + return ( + + {children} + + ); +}; + +export { Perceivable, PerceivableContext }; +export type { PerceivableProps }; diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index a3d1a2012..19649de03 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -119,6 +119,7 @@ export { Menu, MenuItem, MenuTrigger, SubmenuTrigger } from './Menu'; export { Meter } from './Meter'; export { Modal, ModalOverlay } from './Modal'; export { NumberField } from './NumberField'; +export { Perceivable } from './Perceivable'; export { OverlayArrow, Popover } from './Popover'; export { Pressable } from './Pressable'; export { ProgressBar } from './ProgressBar'; diff --git a/packages/components/src/styles/base.module.css b/packages/components/src/styles/base.module.css index 90492de1a..be5e55d55 100644 --- a/packages/components/src/styles/base.module.css +++ b/packages/components/src/styles/base.module.css @@ -77,3 +77,8 @@ cursor: not-allowed; } } + +:global([data-rac][aria-disabled='true']):not([href]) { + opacity: 0.64; + cursor: default; +} diff --git a/packages/components/stories/composition.stories.tsx b/packages/components/stories/composition.stories.tsx index 31cded813..98cf6a4c6 100644 --- a/packages/components/stories/composition.stories.tsx +++ b/packages/components/stories/composition.stories.tsx @@ -1,10 +1,10 @@ import type { Meta, StoryFn, StoryObj } from '@storybook/react'; -import type { ComponentPropsWithoutRef, Fragment } from 'react'; +import type { ComponentPropsWithoutRef } from 'react'; import { Icon } from '@launchpad-ui/icons'; import { vars } from '@launchpad-ui/vars'; import { expect, userEvent, within } from '@storybook/test'; -import { useRef, useState } from 'react'; +import { type Fragment, useRef, useState } from 'react'; import { VisuallyHidden } from 'react-aria'; import { @@ -15,11 +15,13 @@ import { Dialog, DialogTrigger, Group, + IconButton, Input, Label, ListBox, ListBoxItem, type ListBoxItemProps, + Perceivable, Popover, RadioButton, RadioGroup, @@ -222,3 +224,34 @@ export const ListBoxTooltip: Story = { }, name: 'ListBox Tooltip', }; + +export const DisabledWithTooltip: Story = { + render: () => { + return ( +
+ + + + Message + + + console.log('Pressed')} + /> + Message + + +
+ ); + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7d74e5ae..069b4f77c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -433,6 +433,9 @@ importers: '@launchpad-ui/tokens': specifier: workspace:~ version: link:../tokens + '@react-aria/focus': + specifier: 3.18.4 + version: 3.18.4(react@18.3.1) '@react-aria/toast': specifier: 3.0.0-beta.17 version: 3.0.0-beta.17(react@18.3.1) From 670df831acfc595017345f77455c752bcfd48ae9 Mon Sep 17 00:00:00 2001 From: Robb Niznik Date: Tue, 19 Nov 2024 11:09:26 -0500 Subject: [PATCH 2/2] fix: scope with data attribute --- packages/components/src/Perceivable.tsx | 4 +++- packages/components/src/styles/base.module.css | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/components/src/Perceivable.tsx b/packages/components/src/Perceivable.tsx index a129b792f..53d5dbeb5 100644 --- a/packages/components/src/Perceivable.tsx +++ b/packages/components/src/Perceivable.tsx @@ -28,7 +28,9 @@ const Perceivable = ({ children }: PerceivableProps) => { return ( - {children} + + {children} + ); }; diff --git a/packages/components/src/styles/base.module.css b/packages/components/src/styles/base.module.css index be5e55d55..07da8df83 100644 --- a/packages/components/src/styles/base.module.css +++ b/packages/components/src/styles/base.module.css @@ -78,7 +78,7 @@ } } -:global([data-rac][aria-disabled='true']):not([href]) { +:global([data-lp][aria-disabled='true']) { opacity: 0.64; cursor: default; }