Skip to content

Commit

Permalink
2.0.0 Alpha prep (#2887)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
RobinMalfait committed Dec 20, 2023
1 parent 01a34cb commit e662f12
Show file tree
Hide file tree
Showing 203 changed files with 11,833 additions and 6,280 deletions.
12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
"workspaces": [
"packages/*"
],
"resolutions": {
"next/@swc/helpers": "0.4.36"
},
"scripts": {
"react": "yarn workspace @headlessui/react",
"react-playground": "yarn workspace playground-react dev",
Expand Down Expand Up @@ -46,6 +49,7 @@
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.13.3",
"@swc-node/register": "^1.6.8",
"@swc/core": "^1.2.131",
"@swc/jest": "^0.2.17",
"@testing-library/jest-dom": "^5.16.4",
Expand All @@ -56,11 +60,11 @@
"jest": "26",
"lint-staged": "^12.2.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.6.2",
"prettier-plugin-organize-imports": "^3.2.3",
"prettier-plugin-tailwindcss": "0.4",
"prettier": "^3.1.0",
"prettier-plugin-organize-imports": "^3.2.4",
"prettier-plugin-tailwindcss": "^0.5.7",
"rimraf": "^3.0.2",
"tslib": "^2.3.1",
"typescript": "^4.9.5"
"typescript": "^5.3.2"
}
}
29 changes: 24 additions & 5 deletions packages/@headlessui-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add `immediate` prop to `<Combobox />` for immediately opening the Combobox when the `input` receives focus ([#2686](https://github.com/tailwindlabs/headlessui/pull/2686))
- Add `virtual` prop to `Combobox` component ([#2779](https://github.com/tailwindlabs/headlessui/pull/2779))
- Add new `Checkbox` component
- Add new `Radio` component as an alternative to the existing `RadioGroup.Option` component
- Add new `Button` component
- Add new `Input` component
- Add new `Textarea` component
- Add new `Select` component
- Add new `Field`, `Label`, `Description`, `Fieldset` and `Legend` components
- Add new `DataInteractive` component
- Add new `anchor` and `modal` prop to `ComboboxOptions`, `ListboxOptions`, `MenuItems` and `PopoverPanel` components
- Add new `ListboxSelectedOption` component
- Add new `MenuSection`, `MenuHeading`, and `MenuSeparator` components
- Add new simplified `data-*` attributes as an alternative to the existing `data-headlessui-state="..."` attribute
- Add `autoFocus` prop on focusable components (which maps to `data-autofocus`)

### Changed

- Bumped to React and React DOM 18
- Dialog is focused by default instead of the first focusable element (unless an element exists with a `data-autofocus` in the dialog)

### Fixed

- Don't call `<Dialog>`'s `onClose` twice on mobile devices ([#2690](https://github.com/tailwindlabs/headlessui/pull/2690))
Expand All @@ -21,11 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix outside click detection when component is mounted in the Shadow DOM ([#2866](https://github.com/tailwindlabs/headlessui/pull/2866))
- Fix CJS types ([#2880](https://github.com/tailwindlabs/headlessui/pull/2880))
- Fix error when transition classes contain new lines ([#2871](https://github.com/tailwindlabs/headlessui/pull/2871))

### Added

- Add `immediate` prop to `<Combobox />` for immediately opening the Combobox when the `input` receives focus ([#2686](https://github.com/tailwindlabs/headlessui/pull/2686))
- Add `virtual` prop to `Combobox` component ([#2779](https://github.com/tailwindlabs/headlessui/pull/2779))
- Fix iOS scroll lock glitches

## [1.7.17] - 2023-08-17

Expand Down
14 changes: 8 additions & 6 deletions packages/@headlessui-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,17 @@
},
"devDependencies": {
"@testing-library/react": "^13.0.0",
"@types/react": "^17.0.43",
"@types/react-dom": "^17.0.14",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"esbuild": "^0.11.18",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"snapshot-diff": "^0.8.1"
},
"dependencies": {
"@tanstack/react-virtual": "^3.0.0-beta.60",
"client-only": "^0.0.1"
"@floating-ui/react": "^0.26.2",
"@tanstack/react-virtual": "3.0.0-beta.60",
"@react-aria/focus": "^3.14.3",
"@react-aria/interactions": "3.0.0-nightly.2584"
}
}
39 changes: 39 additions & 0 deletions packages/@headlessui-react/src/components/button/button.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { render, screen } from '@testing-library/react'
import React from 'react'
import { Button } from './button'

describe('Rendering', () => {
describe('Button', () => {
it('should render a button', async () => {
render(<Button>My Button</Button>)

expect(screen.getByRole('button')).toBeInTheDocument()
})

it('should default to `type="button"`', async () => {
render(<Button>My Button</Button>)

expect(screen.getByRole('button')).toHaveAttribute('type', 'button')
})

it('should render a button using a render prop', () => {
render(<Button>{(slot) => <>{JSON.stringify(slot)}</>}</Button>)

expect(screen.getByRole('button').textContent).toEqual(
JSON.stringify({
disabled: false,
hover: false,
focus: false,
active: false,
autofocus: false,
})
)
})

it('should map the `autoFocus` prop to a `data-autofocus` attribute', () => {
render(<Button autoFocus>My Button</Button>)

expect(screen.getByRole('button')).toHaveAttribute('data-autofocus')
})
})
})
88 changes: 88 additions & 0 deletions packages/@headlessui-react/src/components/button/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
'use client'

import { useFocusRing } from '@react-aria/focus'
import { useHover } from '@react-aria/interactions'
import { useMemo, type ElementType, type Ref } from 'react'
import { useActivePress } from '../../hooks/use-active-press'
import { useDisabled } from '../../internal/disabled'
import type { Props } from '../../types'
import {
forwardRefWithAs,
mergeProps,
render,
type HasDisplayName,
type RefProp,
} from '../../utils/render'

let DEFAULT_BUTTON_TAG = 'button' as const

type ButtonRenderPropArg = {
disabled: boolean
hover: boolean
focus: boolean
active: boolean
autofocus: boolean
}
type ButtonPropsWeControl = never

export type ButtonProps<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG> = Props<
TTag,
ButtonRenderPropArg,
ButtonPropsWeControl,
{
disabled?: boolean
autoFocus?: boolean
type?: 'button' | 'submit' | 'reset'
}
>

function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
props: ButtonProps<TTag>,
ref: Ref<HTMLElement>
) {
let providedDisabled = useDisabled()
let { disabled = providedDisabled || false, ...theirProps } = props

let { isFocusVisible: focus, focusProps } = useFocusRing({ autoFocus: props.autoFocus ?? false })
let { isHovered: hover, hoverProps } = useHover({ isDisabled: disabled })
let { pressed: active, pressProps } = useActivePress({ disabled })

let ourProps = mergeProps(
{
ref,
disabled: disabled || undefined,
type: theirProps.type ?? 'button',
},
focusProps,
hoverProps,
pressProps
)

let slot = useMemo(
() =>
({
disabled,
hover,
focus,
active,
autofocus: props.autoFocus ?? false,
}) satisfies ButtonRenderPropArg,
[disabled, hover, focus, active, props.autoFocus]
)

return render({
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_BUTTON_TAG,
name: 'Button',
})
}

export interface _internal_ComponentButton extends HasDisplayName {
<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
props: ButtonProps<TTag> & RefProp<typeof ButtonFn>
): JSX.Element
}

export let Button = forwardRefWithAs(ButtonFn) as unknown as _internal_ComponentButton
121 changes: 121 additions & 0 deletions packages/@headlessui-react/src/components/checkbox/checkbox.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { render } from '@testing-library/react'
import React, { useState } from 'react'
import {
CheckboxState,
assertCheckbox,
getCheckbox,
} from '../../test-utils/accessibility-assertions'
import { Keys, click, focus, press } from '../../test-utils/interactions'
import {
commonControlScenarios,
commonFormScenarios,
commonRenderingScenarios,
} from '../../test-utils/scenarios'
import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
import { Checkbox, type CheckboxProps } from './checkbox'

commonRenderingScenarios(Checkbox, { getElement: getCheckbox })
commonControlScenarios(Checkbox)
commonFormScenarios((props) => <Checkbox defaultChecked {...props} />, {
async performUserInteraction(control) {
await click(control)
},
})

describe('Rendering', () => {
it(
'should be possible to put the checkbox in an indeterminate state',
suppressConsoleLogs(async () => {
render(<Checkbox indeterminate />)

assertCheckbox({ state: CheckboxState.Indeterminate })
})
)

it(
'should be possible to put the checkbox in an default checked state',
suppressConsoleLogs(async () => {
render(<Checkbox defaultChecked />)

assertCheckbox({ state: CheckboxState.Checked })
})
)

it(
'should render a checkbox in an unchecked state',
suppressConsoleLogs(async () => {
render(<Checkbox />)

assertCheckbox({ state: CheckboxState.Unchecked })
})
)
})

describe.each([
[
'Uncontrolled',
function Example(props: CheckboxProps) {
return <Checkbox {...props} />
},
],
[
'Controlled',
function Example(props: CheckboxProps) {
let [checked, setChecked] = useState(false)
return <Checkbox checked={checked} onChange={setChecked} {...props} />
},
],
])('Keyboard interactions (%s)', (_, Example) => {
describe('`Space` key', () => {
it(
'should be possible to toggle a checkbox',
suppressConsoleLogs(async () => {
render(<Example />)

assertCheckbox({ state: CheckboxState.Unchecked })

await focus(getCheckbox())
await press(Keys.Space)

assertCheckbox({ state: CheckboxState.Checked })

await press(Keys.Space)

assertCheckbox({ state: CheckboxState.Unchecked })
})
)
})
})

describe.each([
[
'Uncontrolled',
function Example(props: CheckboxProps) {
return <Checkbox {...props} />
},
],
[
'Controlled',
function Example(props: CheckboxProps) {
let [checked, setChecked] = useState(false)
return <Checkbox checked={checked} onChange={setChecked} {...props} />
},
],
])('Mouse interactions (%s)', (_, Example) => {
it(
'should be possible to toggle a checkbox by clicking it',
suppressConsoleLogs(async () => {
render(<Example />)

assertCheckbox({ state: CheckboxState.Unchecked })

await click(getCheckbox())

assertCheckbox({ state: CheckboxState.Checked })

await click(getCheckbox())

assertCheckbox({ state: CheckboxState.Unchecked })
})
)
})
Loading

1 comment on commit e662f12

@vercel
Copy link

@vercel vercel bot commented on e662f12 Dec 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

headlessui-vue – ./packages/playground-vue

headlessui-vue-tailwindlabs.vercel.app
headlessui-vue-git-main-tailwindlabs.vercel.app
headlessui-vue.vercel.app

Please sign in to comment.