Skip to content

Commit e662f12

Browse files
authored
2.0.0 Alpha prep (#2887)
* bump React & React DOM dependencies * fix typo `TOmitableProps` → `TOmittableProps` * bump prettier * run prettier after prettier version bump * bump TypeScript * run prettier after TypeScript version bump * enable `verbatimModuleSyntax` This ensures all imported types are using the `type` keyword. * add `type` to type related imports * add common testing scenarios Will be used in the new and existing components. * add script to make Next.js happy Right now Next.js does barrel file optimization and re-writing imports to a real path in the `dist` folder. Most of those rewrites don't actually exist because they have an assumption: ```js import { FooBar } from '@headlessui/react' ``` is rewritten as: ```js import { FooBar } from '@headlessui/react/dist/components/foo-bar/foo-bar' ``` This script will make sure these paths exist... * improve `by` prop, introduce `useByComparator` This hook has a default implementation when comparing objects. If the object contains an `id`, then we will compare the objects by their `id`'s without the user of the library needing to specify `by="id"`. If the objects don't have an `id` prop, then the default is still to compare by reference (unless specicified otherwise). * sync yarn.lock * rename `Features` to `HiddenFeatures` for `Hidden` component * rename `Features` to `FocusTrapFeatures` in `FocusTrap` component * rename `Features` to `RenderFeatures` in `render` util * add `floating-ui` as a dependency + introduce internal floating related components * bump Vue dependencies * ensure scroll bar calculations can't go negative * improve types in `@headlessui/vue` * use snapshot tests for `Transition` tests in `@headlessui/vue` * use snapshot tests for `portal` tests in `@headlessui/vue` * rename `src/components/transitions/` to `src/components/transition/` (singular) This is so that we can be consistent with the other components. * drop custom `toMatchFormattedCss`, prefer snapshot tests instead * use snapshot tests for `Label` tests in `@headlessui/vue` * use snapshot tests for `Description` tests in `@headlessui/vue` * sort exported components in tests for `@headlessui/vue` * use snapshot tests in `@headlessui/tailwindcss` * rename `mergeProps` to `mergePropsAdvanced` This is a more complex version of a soon to be exported `mergeProps` that we will be using in our components. * do not expose `aria-labelledby` if it is only referencing itself * expose boolean state as `kebab-case` instead of `camelCase` These are the ones being exposed inside `data-headlessui-state="..."` * expose boolean data attributes A slot with `{active,focus,hover}` will be exposed as: ```html <span data-headlessui-state="active focus hover"></span> ``` But also as boolean attributes: ```html <span data-active data-focus data-hover></span> ``` * improve internal types for `className` in `render` util * ensure we keep exposed data attributes into account when trying to forward them to the component inside the `Fragment` * add small typescript type fix This is internal code, and the public API is not influenced by this `:any`. It does make TypeScript happy. * introduce `mergeProps` util to be used in our components This will help us to merge props, when event handlers are available they will be merged by wrapping them in a function such that both (or more) event handlers are called for the same `event`. * add new internal `Modal` component * fix: when using `Focus.Previous` with `activeIndex = -1` should start at the end * prefer `window.scrollY` instead of `window.pageYOffset` Because `window.pageYOffset` is deprecated. * add `'use client'` directives on client only components These components use hooks that won't work in server components and you will receive an error otherwise. * drop `import 'client-only'` in favor of the `'use client'` directive * add React Aria dependencies * pin beta dependencies * prettier bump formatting * improve TypeScript types in tests * use new Jest matchers instead of deprecated ones * improve typescript types in Vue * prefer `useLabelledBy` and `useDescribedBy` * add internal `DisabledProvider` * add internal `IdProvider` * add internal `useDidElementMove` hook * add internal `useElementSize` hook * add internal `useIsTouchDevice` hook * add internal `useActivePress` hook * use snapshot tests for `Description` tests * use snapshot tests for `Label` tests * use snapshot tests for `Portal` tests * use snapshot tests for `render` tests * add (private) `Tooltip` component Currently this one is not ready yet, so its not publicly exposed yet. * add internal `FormFields` component This one adds a component to render (hidden) inputs for native form support. It also ensures that form fields can be hoisted to the end of the nearest `Field`. If the components are not inside a `Field` they will be rendered in place. * add new `Button` component * add new `Checkbox` component * add new `DataInteractive` component * add new `Field` component * add new `Fieldset` component * add new `Legend` component * add new `Input` component * add new `Select` component * add new `Textarea` component * export new components * WIP * remove `within: true` This only makes sense if anything inside the current element receives focus, which is not the case for `input`, `select`, `textarea` or `Radio/RadioOption`. * group focus/hover/active hooks together * conditionally link anchor panel * immediately focus the container * prevent premature disabling of `Listbox`'s floating integration + Track whether the button moved or not when disabling such that we can disable the transitions earlier. * improve scroll locking on iOS * skip hydration tests for now * skip certain focus trap tests for now * update CHANGELOG.md * add missing requires * drop unused `@ts-expect-error` * ignore type issues in playgrounds These playgrounds are mainly test playgrounds. Lower priority for now, we will get back to them. * add yarn resolutions to solve swc bug
1 parent 01a34cb commit e662f12

File tree

203 files changed

+11833
-6280
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

203 files changed

+11833
-6280
lines changed

package.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
"workspaces": [
1010
"packages/*"
1111
],
12+
"resolutions": {
13+
"next/@swc/helpers": "0.4.36"
14+
},
1215
"scripts": {
1316
"react": "yarn workspace @headlessui/react",
1417
"react-playground": "yarn workspace playground-react dev",
@@ -46,6 +49,7 @@
4649
},
4750
"devDependencies": {
4851
"@arethetypeswrong/cli": "^0.13.3",
52+
"@swc-node/register": "^1.6.8",
4953
"@swc/core": "^1.2.131",
5054
"@swc/jest": "^0.2.17",
5155
"@testing-library/jest-dom": "^5.16.4",
@@ -56,11 +60,11 @@
5660
"jest": "26",
5761
"lint-staged": "^12.2.1",
5862
"npm-run-all": "^4.1.5",
59-
"prettier": "^2.6.2",
60-
"prettier-plugin-organize-imports": "^3.2.3",
61-
"prettier-plugin-tailwindcss": "0.4",
63+
"prettier": "^3.1.0",
64+
"prettier-plugin-organize-imports": "^3.2.4",
65+
"prettier-plugin-tailwindcss": "^0.5.7",
6266
"rimraf": "^3.0.2",
6367
"tslib": "^2.3.1",
64-
"typescript": "^4.9.5"
68+
"typescript": "^5.3.2"
6569
}
6670
}

packages/@headlessui-react/CHANGELOG.md

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Add `immediate` prop to `<Combobox />` for immediately opening the Combobox when the `input` receives focus ([#2686](https://github.com/tailwindlabs/headlessui/pull/2686))
13+
- Add `virtual` prop to `Combobox` component ([#2779](https://github.com/tailwindlabs/headlessui/pull/2779))
14+
- Add new `Checkbox` component
15+
- Add new `Radio` component as an alternative to the existing `RadioGroup.Option` component
16+
- Add new `Button` component
17+
- Add new `Input` component
18+
- Add new `Textarea` component
19+
- Add new `Select` component
20+
- Add new `Field`, `Label`, `Description`, `Fieldset` and `Legend` components
21+
- Add new `DataInteractive` component
22+
- Add new `anchor` and `modal` prop to `ComboboxOptions`, `ListboxOptions`, `MenuItems` and `PopoverPanel` components
23+
- Add new `ListboxSelectedOption` component
24+
- Add new `MenuSection`, `MenuHeading`, and `MenuSeparator` components
25+
- Add new simplified `data-*` attributes as an alternative to the existing `data-headlessui-state="..."` attribute
26+
- Add `autoFocus` prop on focusable components (which maps to `data-autofocus`)
27+
28+
### Changed
29+
30+
- Bumped to React and React DOM 18
31+
- Dialog is focused by default instead of the first focusable element (unless an element exists with a `data-autofocus` in the dialog)
32+
1033
### Fixed
1134

1235
- Don't call `<Dialog>`'s `onClose` twice on mobile devices ([#2690](https://github.com/tailwindlabs/headlessui/pull/2690))
@@ -21,11 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2144
- Fix outside click detection when component is mounted in the Shadow DOM ([#2866](https://github.com/tailwindlabs/headlessui/pull/2866))
2245
- Fix CJS types ([#2880](https://github.com/tailwindlabs/headlessui/pull/2880))
2346
- Fix error when transition classes contain new lines ([#2871](https://github.com/tailwindlabs/headlessui/pull/2871))
24-
25-
### Added
26-
27-
- Add `immediate` prop to `<Combobox />` for immediately opening the Combobox when the `input` receives focus ([#2686](https://github.com/tailwindlabs/headlessui/pull/2686))
28-
- Add `virtual` prop to `Combobox` component ([#2779](https://github.com/tailwindlabs/headlessui/pull/2779))
47+
- Fix iOS scroll lock glitches
2948

3049
## [1.7.17] - 2023-08-17
3150

packages/@headlessui-react/package.json

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,17 @@
4747
},
4848
"devDependencies": {
4949
"@testing-library/react": "^13.0.0",
50-
"@types/react": "^17.0.43",
51-
"@types/react-dom": "^17.0.14",
50+
"@types/react": "^18.2.14",
51+
"@types/react-dom": "^18.2.6",
5252
"esbuild": "^0.11.18",
53-
"react": "^18.0.0",
54-
"react-dom": "^18.0.0",
53+
"react": "^18.2.0",
54+
"react-dom": "^18.2.0",
5555
"snapshot-diff": "^0.8.1"
5656
},
5757
"dependencies": {
58-
"@tanstack/react-virtual": "^3.0.0-beta.60",
59-
"client-only": "^0.0.1"
58+
"@floating-ui/react": "^0.26.2",
59+
"@tanstack/react-virtual": "3.0.0-beta.60",
60+
"@react-aria/focus": "^3.14.3",
61+
"@react-aria/interactions": "3.0.0-nightly.2584"
6062
}
6163
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { render, screen } from '@testing-library/react'
2+
import React from 'react'
3+
import { Button } from './button'
4+
5+
describe('Rendering', () => {
6+
describe('Button', () => {
7+
it('should render a button', async () => {
8+
render(<Button>My Button</Button>)
9+
10+
expect(screen.getByRole('button')).toBeInTheDocument()
11+
})
12+
13+
it('should default to `type="button"`', async () => {
14+
render(<Button>My Button</Button>)
15+
16+
expect(screen.getByRole('button')).toHaveAttribute('type', 'button')
17+
})
18+
19+
it('should render a button using a render prop', () => {
20+
render(<Button>{(slot) => <>{JSON.stringify(slot)}</>}</Button>)
21+
22+
expect(screen.getByRole('button').textContent).toEqual(
23+
JSON.stringify({
24+
disabled: false,
25+
hover: false,
26+
focus: false,
27+
active: false,
28+
autofocus: false,
29+
})
30+
)
31+
})
32+
33+
it('should map the `autoFocus` prop to a `data-autofocus` attribute', () => {
34+
render(<Button autoFocus>My Button</Button>)
35+
36+
expect(screen.getByRole('button')).toHaveAttribute('data-autofocus')
37+
})
38+
})
39+
})
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
'use client'
2+
3+
import { useFocusRing } from '@react-aria/focus'
4+
import { useHover } from '@react-aria/interactions'
5+
import { useMemo, type ElementType, type Ref } from 'react'
6+
import { useActivePress } from '../../hooks/use-active-press'
7+
import { useDisabled } from '../../internal/disabled'
8+
import type { Props } from '../../types'
9+
import {
10+
forwardRefWithAs,
11+
mergeProps,
12+
render,
13+
type HasDisplayName,
14+
type RefProp,
15+
} from '../../utils/render'
16+
17+
let DEFAULT_BUTTON_TAG = 'button' as const
18+
19+
type ButtonRenderPropArg = {
20+
disabled: boolean
21+
hover: boolean
22+
focus: boolean
23+
active: boolean
24+
autofocus: boolean
25+
}
26+
type ButtonPropsWeControl = never
27+
28+
export type ButtonProps<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG> = Props<
29+
TTag,
30+
ButtonRenderPropArg,
31+
ButtonPropsWeControl,
32+
{
33+
disabled?: boolean
34+
autoFocus?: boolean
35+
type?: 'button' | 'submit' | 'reset'
36+
}
37+
>
38+
39+
function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
40+
props: ButtonProps<TTag>,
41+
ref: Ref<HTMLElement>
42+
) {
43+
let providedDisabled = useDisabled()
44+
let { disabled = providedDisabled || false, ...theirProps } = props
45+
46+
let { isFocusVisible: focus, focusProps } = useFocusRing({ autoFocus: props.autoFocus ?? false })
47+
let { isHovered: hover, hoverProps } = useHover({ isDisabled: disabled })
48+
let { pressed: active, pressProps } = useActivePress({ disabled })
49+
50+
let ourProps = mergeProps(
51+
{
52+
ref,
53+
disabled: disabled || undefined,
54+
type: theirProps.type ?? 'button',
55+
},
56+
focusProps,
57+
hoverProps,
58+
pressProps
59+
)
60+
61+
let slot = useMemo(
62+
() =>
63+
({
64+
disabled,
65+
hover,
66+
focus,
67+
active,
68+
autofocus: props.autoFocus ?? false,
69+
}) satisfies ButtonRenderPropArg,
70+
[disabled, hover, focus, active, props.autoFocus]
71+
)
72+
73+
return render({
74+
ourProps,
75+
theirProps,
76+
slot,
77+
defaultTag: DEFAULT_BUTTON_TAG,
78+
name: 'Button',
79+
})
80+
}
81+
82+
export interface _internal_ComponentButton extends HasDisplayName {
83+
<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
84+
props: ButtonProps<TTag> & RefProp<typeof ButtonFn>
85+
): JSX.Element
86+
}
87+
88+
export let Button = forwardRefWithAs(ButtonFn) as unknown as _internal_ComponentButton
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { render } from '@testing-library/react'
2+
import React, { useState } from 'react'
3+
import {
4+
CheckboxState,
5+
assertCheckbox,
6+
getCheckbox,
7+
} from '../../test-utils/accessibility-assertions'
8+
import { Keys, click, focus, press } from '../../test-utils/interactions'
9+
import {
10+
commonControlScenarios,
11+
commonFormScenarios,
12+
commonRenderingScenarios,
13+
} from '../../test-utils/scenarios'
14+
import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
15+
import { Checkbox, type CheckboxProps } from './checkbox'
16+
17+
commonRenderingScenarios(Checkbox, { getElement: getCheckbox })
18+
commonControlScenarios(Checkbox)
19+
commonFormScenarios((props) => <Checkbox defaultChecked {...props} />, {
20+
async performUserInteraction(control) {
21+
await click(control)
22+
},
23+
})
24+
25+
describe('Rendering', () => {
26+
it(
27+
'should be possible to put the checkbox in an indeterminate state',
28+
suppressConsoleLogs(async () => {
29+
render(<Checkbox indeterminate />)
30+
31+
assertCheckbox({ state: CheckboxState.Indeterminate })
32+
})
33+
)
34+
35+
it(
36+
'should be possible to put the checkbox in an default checked state',
37+
suppressConsoleLogs(async () => {
38+
render(<Checkbox defaultChecked />)
39+
40+
assertCheckbox({ state: CheckboxState.Checked })
41+
})
42+
)
43+
44+
it(
45+
'should render a checkbox in an unchecked state',
46+
suppressConsoleLogs(async () => {
47+
render(<Checkbox />)
48+
49+
assertCheckbox({ state: CheckboxState.Unchecked })
50+
})
51+
)
52+
})
53+
54+
describe.each([
55+
[
56+
'Uncontrolled',
57+
function Example(props: CheckboxProps) {
58+
return <Checkbox {...props} />
59+
},
60+
],
61+
[
62+
'Controlled',
63+
function Example(props: CheckboxProps) {
64+
let [checked, setChecked] = useState(false)
65+
return <Checkbox checked={checked} onChange={setChecked} {...props} />
66+
},
67+
],
68+
])('Keyboard interactions (%s)', (_, Example) => {
69+
describe('`Space` key', () => {
70+
it(
71+
'should be possible to toggle a checkbox',
72+
suppressConsoleLogs(async () => {
73+
render(<Example />)
74+
75+
assertCheckbox({ state: CheckboxState.Unchecked })
76+
77+
await focus(getCheckbox())
78+
await press(Keys.Space)
79+
80+
assertCheckbox({ state: CheckboxState.Checked })
81+
82+
await press(Keys.Space)
83+
84+
assertCheckbox({ state: CheckboxState.Unchecked })
85+
})
86+
)
87+
})
88+
})
89+
90+
describe.each([
91+
[
92+
'Uncontrolled',
93+
function Example(props: CheckboxProps) {
94+
return <Checkbox {...props} />
95+
},
96+
],
97+
[
98+
'Controlled',
99+
function Example(props: CheckboxProps) {
100+
let [checked, setChecked] = useState(false)
101+
return <Checkbox checked={checked} onChange={setChecked} {...props} />
102+
},
103+
],
104+
])('Mouse interactions (%s)', (_, Example) => {
105+
it(
106+
'should be possible to toggle a checkbox by clicking it',
107+
suppressConsoleLogs(async () => {
108+
render(<Example />)
109+
110+
assertCheckbox({ state: CheckboxState.Unchecked })
111+
112+
await click(getCheckbox())
113+
114+
assertCheckbox({ state: CheckboxState.Checked })
115+
116+
await click(getCheckbox())
117+
118+
assertCheckbox({ state: CheckboxState.Unchecked })
119+
})
120+
)
121+
})

0 commit comments

Comments
 (0)