Repository: tailwindlabs/headlessui Branch: main Commit: 7c06d2b53622 Files: 472 Total size: 3.2 MB Directory structure: gitextract_53laupa_/ ├── .eslintignore ├── .github/ │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.md │ │ └── config.yml │ └── workflows/ │ ├── main.yml │ ├── prepare-release.yml │ ├── release-insiders.yml │ └── release.yml ├── .gitignore ├── .prettierignore ├── .swcrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── jest/ │ ├── create-jest-config.cjs │ ├── custom-matchers.ts │ └── polyfills.ts ├── jest.config.cjs ├── package.json ├── packages/ │ ├── @headlessui-react/ │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── build/ │ │ │ └── index.cjs │ │ ├── jest.config.cjs │ │ ├── jest.setup.js │ │ ├── package.json │ │ ├── src/ │ │ │ ├── components/ │ │ │ │ ├── button/ │ │ │ │ │ ├── button.test.tsx │ │ │ │ │ └── button.tsx │ │ │ │ ├── checkbox/ │ │ │ │ │ ├── checkbox.test.tsx │ │ │ │ │ └── checkbox.tsx │ │ │ │ ├── close-button/ │ │ │ │ │ └── close-button.tsx │ │ │ │ ├── combobox/ │ │ │ │ │ ├── combobox-machine-glue.tsx │ │ │ │ │ ├── combobox-machine.ts │ │ │ │ │ ├── combobox.test.tsx │ │ │ │ │ └── combobox.tsx │ │ │ │ ├── combobox-button/ │ │ │ │ │ └── combobox-button.tsx │ │ │ │ ├── combobox-input/ │ │ │ │ │ └── combobox-input.tsx │ │ │ │ ├── combobox-label/ │ │ │ │ │ └── combobox-label.tsx │ │ │ │ ├── combobox-option/ │ │ │ │ │ └── combobox-option.tsx │ │ │ │ ├── combobox-options/ │ │ │ │ │ └── combobox-options.tsx │ │ │ │ ├── data-interactive/ │ │ │ │ │ ├── data-interactive.test.tsx │ │ │ │ │ └── data-interactive.tsx │ │ │ │ ├── description/ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ └── description.test.tsx.snap │ │ │ │ │ ├── description.test.tsx │ │ │ │ │ └── description.tsx │ │ │ │ ├── dialog/ │ │ │ │ │ ├── dialog.test.tsx │ │ │ │ │ └── dialog.tsx │ │ │ │ ├── dialog-description/ │ │ │ │ │ └── dialog-description.tsx │ │ │ │ ├── dialog-panel/ │ │ │ │ │ └── dialog-panel.tsx │ │ │ │ ├── dialog-title/ │ │ │ │ │ └── dialog-title.tsx │ │ │ │ ├── disclosure/ │ │ │ │ │ ├── disclosure.test.tsx │ │ │ │ │ └── disclosure.tsx │ │ │ │ ├── disclosure-button/ │ │ │ │ │ └── disclosure-button.tsx │ │ │ │ ├── disclosure-panel/ │ │ │ │ │ └── disclosure-panel.tsx │ │ │ │ ├── field/ │ │ │ │ │ ├── field.test.tsx │ │ │ │ │ └── field.tsx │ │ │ │ ├── fieldset/ │ │ │ │ │ ├── fieldset.test.tsx │ │ │ │ │ └── fieldset.tsx │ │ │ │ ├── focus-trap/ │ │ │ │ │ ├── focus-trap.test.tsx │ │ │ │ │ └── focus-trap.tsx │ │ │ │ ├── focus-trap-features/ │ │ │ │ │ └── focus-trap-features.tsx │ │ │ │ ├── input/ │ │ │ │ │ ├── input.test.tsx │ │ │ │ │ └── input.tsx │ │ │ │ ├── keyboard.ts │ │ │ │ ├── label/ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ └── label.test.tsx.snap │ │ │ │ │ ├── label.test.tsx │ │ │ │ │ └── label.tsx │ │ │ │ ├── legend/ │ │ │ │ │ └── legend.tsx │ │ │ │ ├── listbox/ │ │ │ │ │ ├── listbox-machine-glue.tsx │ │ │ │ │ ├── listbox-machine.ts │ │ │ │ │ ├── listbox.test.tsx │ │ │ │ │ └── listbox.tsx │ │ │ │ ├── listbox-button/ │ │ │ │ │ └── listbox-button.tsx │ │ │ │ ├── listbox-label/ │ │ │ │ │ └── listbox-label.tsx │ │ │ │ ├── listbox-option/ │ │ │ │ │ └── listbox-option.tsx │ │ │ │ ├── listbox-options/ │ │ │ │ │ └── listbox-options.tsx │ │ │ │ ├── listbox-selected-option/ │ │ │ │ │ └── listbox-selected-option.tsx │ │ │ │ ├── menu/ │ │ │ │ │ ├── menu-machine-glue.tsx │ │ │ │ │ ├── menu-machine.ts │ │ │ │ │ ├── menu.test.tsx │ │ │ │ │ └── menu.tsx │ │ │ │ ├── menu-button/ │ │ │ │ │ └── menu-button.tsx │ │ │ │ ├── menu-heading/ │ │ │ │ │ └── menu-heading.tsx │ │ │ │ ├── menu-item/ │ │ │ │ │ └── menu-item.tsx │ │ │ │ ├── menu-items/ │ │ │ │ │ └── menu-items.tsx │ │ │ │ ├── menu-section/ │ │ │ │ │ └── menu-section.tsx │ │ │ │ ├── menu-separator/ │ │ │ │ │ └── menu-separator.tsx │ │ │ │ ├── mouse.ts │ │ │ │ ├── popover/ │ │ │ │ │ ├── popover-machine-glue.tsx │ │ │ │ │ ├── popover-machine.ts │ │ │ │ │ ├── popover.test.tsx │ │ │ │ │ └── popover.tsx │ │ │ │ ├── popover-backdrop/ │ │ │ │ │ └── popover-backdrop.tsx │ │ │ │ ├── popover-button/ │ │ │ │ │ └── popover-button.tsx │ │ │ │ ├── popover-group/ │ │ │ │ │ └── popover-group.tsx │ │ │ │ ├── popover-overlay/ │ │ │ │ │ └── popover-overlay.tsx │ │ │ │ ├── popover-panel/ │ │ │ │ │ └── popover-panel.tsx │ │ │ │ ├── portal/ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ └── portal.test.tsx.snap │ │ │ │ │ ├── portal.test.tsx │ │ │ │ │ └── portal.tsx │ │ │ │ ├── radio/ │ │ │ │ │ └── radio.tsx │ │ │ │ ├── radio-group/ │ │ │ │ │ ├── radio-group.test.tsx │ │ │ │ │ └── radio-group.tsx │ │ │ │ ├── radio-group-description/ │ │ │ │ │ └── radio-group-description.tsx │ │ │ │ ├── radio-group-label/ │ │ │ │ │ └── radio-group-label.tsx │ │ │ │ ├── radio-group-option/ │ │ │ │ │ └── radio-group-option.tsx │ │ │ │ ├── select/ │ │ │ │ │ ├── select.test.tsx │ │ │ │ │ └── select.tsx │ │ │ │ ├── switch/ │ │ │ │ │ ├── switch.test.tsx │ │ │ │ │ └── switch.tsx │ │ │ │ ├── switch-description/ │ │ │ │ │ └── switch-description.tsx │ │ │ │ ├── switch-group/ │ │ │ │ │ └── switch-group.tsx │ │ │ │ ├── switch-label/ │ │ │ │ │ └── switch-label.tsx │ │ │ │ ├── tab/ │ │ │ │ │ └── tab.tsx │ │ │ │ ├── tab-group/ │ │ │ │ │ └── tab-group.tsx │ │ │ │ ├── tab-list/ │ │ │ │ │ └── tab-list.tsx │ │ │ │ ├── tab-panel/ │ │ │ │ │ └── tab-panel.tsx │ │ │ │ ├── tab-panels/ │ │ │ │ │ └── tab-panels.tsx │ │ │ │ ├── tabs/ │ │ │ │ │ ├── tabs.ssr.test.tsx │ │ │ │ │ ├── tabs.test.tsx │ │ │ │ │ └── tabs.tsx │ │ │ │ ├── textarea/ │ │ │ │ │ ├── textarea.test.tsx │ │ │ │ │ └── textarea.tsx │ │ │ │ ├── tooltip/ │ │ │ │ │ └── tooltip.tsx │ │ │ │ ├── transition/ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ └── transition.test.tsx.snap │ │ │ │ │ ├── transition.ssr.test.tsx │ │ │ │ │ ├── transition.test.tsx │ │ │ │ │ └── transition.tsx │ │ │ │ ├── transition-child/ │ │ │ │ │ └── transition-child.tsx │ │ │ │ └── transitions/ │ │ │ │ └── transition.tsx │ │ │ ├── hooks/ │ │ │ │ ├── __mocks__/ │ │ │ │ │ └── use-id.ts │ │ │ │ ├── document-overflow/ │ │ │ │ │ ├── adjust-scrollbar-padding.ts │ │ │ │ │ ├── handle-ios-locking.ts │ │ │ │ │ ├── overflow-store.ts │ │ │ │ │ ├── prevent-scroll.ts │ │ │ │ │ └── use-document-overflow.ts │ │ │ │ ├── use-active-press.tsx │ │ │ │ ├── use-by-comparator.ts │ │ │ │ ├── use-computed.ts │ │ │ │ ├── use-controllable.ts │ │ │ │ ├── use-default-value.ts │ │ │ │ ├── use-disposables.ts │ │ │ │ ├── use-document-event.ts │ │ │ │ ├── use-element-size.ts │ │ │ │ ├── use-escape.ts │ │ │ │ ├── use-event-listener.ts │ │ │ │ ├── use-event.ts │ │ │ │ ├── use-flags.ts │ │ │ │ ├── use-handle-toggle.tsx │ │ │ │ ├── use-id.ts │ │ │ │ ├── use-inert-others.test.tsx │ │ │ │ ├── use-inert-others.tsx │ │ │ │ ├── use-is-initial-render.ts │ │ │ │ ├── use-is-mounted.ts │ │ │ │ ├── use-is-top-layer.ts │ │ │ │ ├── use-is-touch-device.ts │ │ │ │ ├── use-iso-morphic-effect.ts │ │ │ │ ├── use-latest-value.ts │ │ │ │ ├── use-on-disappear.ts │ │ │ │ ├── use-on-unmount.ts │ │ │ │ ├── use-outside-click.ts │ │ │ │ ├── use-owner.ts │ │ │ │ ├── use-quick-release.ts │ │ │ │ ├── use-refocusable-input.ts │ │ │ │ ├── use-resolve-button-type.ts │ │ │ │ ├── use-resolved-tag.ts │ │ │ │ ├── use-root-containers.tsx │ │ │ │ ├── use-scroll-lock.ts │ │ │ │ ├── use-server-handoff-complete.ts │ │ │ │ ├── use-slot.ts │ │ │ │ ├── use-store.ts │ │ │ │ ├── use-sync-refs.ts │ │ │ │ ├── use-tab-direction.ts │ │ │ │ ├── use-text-value.ts │ │ │ │ ├── use-tracked-pointer.ts │ │ │ │ ├── use-transition.ts │ │ │ │ ├── use-tree-walker.ts │ │ │ │ ├── use-watch.ts │ │ │ │ └── use-window-event.ts │ │ │ ├── index.test.ts │ │ │ ├── index.ts │ │ │ ├── internal/ │ │ │ │ ├── close-provider.tsx │ │ │ │ ├── disabled.tsx │ │ │ │ ├── floating.tsx │ │ │ │ ├── focus-sentinel.tsx │ │ │ │ ├── form-fields.tsx │ │ │ │ ├── frozen.tsx │ │ │ │ ├── hidden.tsx │ │ │ │ ├── id.tsx │ │ │ │ ├── open-closed.tsx │ │ │ │ └── portal-force-root.tsx │ │ │ ├── machine.ts │ │ │ ├── machines/ │ │ │ │ └── stack-machine.ts │ │ │ ├── react-glue.tsx │ │ │ ├── test-utils/ │ │ │ │ ├── accessibility-assertions.ts │ │ │ │ ├── execute-timeline.ts │ │ │ │ ├── fake-pointer.ts │ │ │ │ ├── interactions.test.tsx │ │ │ │ ├── interactions.ts │ │ │ │ ├── report-dom-node-changes.ts │ │ │ │ ├── scenarios.tsx │ │ │ │ ├── snapshot.ts │ │ │ │ ├── ssr.tsx │ │ │ │ └── suppress-console-logs.ts │ │ │ ├── types.ts │ │ │ └── utils/ │ │ │ ├── __snapshots__/ │ │ │ │ └── render.test.tsx.snap │ │ │ ├── active-element-history.ts │ │ │ ├── bugs.ts │ │ │ ├── calculate-active-index.ts │ │ │ ├── class-names.ts │ │ │ ├── default-map.ts │ │ │ ├── disposables.ts │ │ │ ├── document-ready.ts │ │ │ ├── dom.ts │ │ │ ├── element-movement.ts │ │ │ ├── env.ts │ │ │ ├── focus-management.ts │ │ │ ├── form.test.ts │ │ │ ├── form.ts │ │ │ ├── get-text-value.test.ts │ │ │ ├── get-text-value.ts │ │ │ ├── match.ts │ │ │ ├── micro-task.ts │ │ │ ├── once.ts │ │ │ ├── owner.ts │ │ │ ├── platform.ts │ │ │ ├── render.test.tsx │ │ │ ├── render.ts │ │ │ ├── stable-collection.tsx │ │ │ ├── start-transition.ts │ │ │ └── store.ts │ │ ├── tsconfig.json │ │ └── types/ │ │ └── jest.d.ts │ ├── @headlessui-tailwindcss/ │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── build/ │ │ │ └── index.cjs │ │ ├── jest.config.cjs │ │ ├── package.json │ │ ├── scripts/ │ │ │ └── fix-types.cjs │ │ ├── src/ │ │ │ ├── __snapshots__/ │ │ │ │ └── index.test.ts.snap │ │ │ ├── index.test.ts │ │ │ └── index.ts │ │ └── tsconfig.json │ └── @headlessui-vue/ │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── build/ │ │ └── index.cjs │ ├── jest.config.cjs │ ├── package.json │ ├── src/ │ │ ├── components/ │ │ │ ├── combobox/ │ │ │ │ ├── combobox.test.ts │ │ │ │ └── combobox.ts │ │ │ ├── description/ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── description.test.ts.snap │ │ │ │ ├── description.test.ts │ │ │ │ └── description.ts │ │ │ ├── dialog/ │ │ │ │ ├── dialog.test.ts │ │ │ │ └── dialog.ts │ │ │ ├── disclosure/ │ │ │ │ ├── disclosure.srr.test.ts │ │ │ │ ├── disclosure.test.ts │ │ │ │ └── disclosure.ts │ │ │ ├── focus-trap/ │ │ │ │ ├── focus-trap.test.ts │ │ │ │ └── focus-trap.ts │ │ │ ├── label/ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── label.test.ts.snap │ │ │ │ ├── label.test.ts │ │ │ │ └── label.ts │ │ │ ├── listbox/ │ │ │ │ ├── listbox.test.tsx │ │ │ │ └── listbox.ts │ │ │ ├── menu/ │ │ │ │ ├── menu.test.tsx │ │ │ │ └── menu.ts │ │ │ ├── popover/ │ │ │ │ ├── popover.test.ts │ │ │ │ └── popover.ts │ │ │ ├── portal/ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── portal.test.ts.snap │ │ │ │ ├── portal.test.ts │ │ │ │ └── portal.ts │ │ │ ├── radio-group/ │ │ │ │ ├── radio-group.test.ts │ │ │ │ └── radio-group.ts │ │ │ ├── switch/ │ │ │ │ ├── switch.test.tsx │ │ │ │ └── switch.ts │ │ │ ├── tabs/ │ │ │ │ ├── tabs.ssr.test.ts │ │ │ │ ├── tabs.test.ts │ │ │ │ └── tabs.ts │ │ │ └── transitions/ │ │ │ ├── __snapshots__/ │ │ │ │ └── transition.test.ts.snap │ │ │ ├── transition.ssr.test.ts │ │ │ ├── transition.test.ts │ │ │ ├── transition.ts │ │ │ └── utils/ │ │ │ ├── transition.test.ts │ │ │ └── transition.ts │ │ ├── hooks/ │ │ │ ├── __mocks__/ │ │ │ │ └── use-id.ts │ │ │ ├── document-overflow/ │ │ │ │ ├── adjust-scrollbar-padding.ts │ │ │ │ ├── handle-ios-locking.ts │ │ │ │ ├── overflow-store.ts │ │ │ │ ├── prevent-scroll.ts │ │ │ │ └── use-document-overflow.ts │ │ │ ├── use-controllable.ts │ │ │ ├── use-disposables.ts │ │ │ ├── use-document-event.ts │ │ │ ├── use-event-listener.ts │ │ │ ├── use-frame-debounce.ts │ │ │ ├── use-id.ts │ │ │ ├── use-inert.test.ts │ │ │ ├── use-inert.ts │ │ │ ├── use-outside-click.ts │ │ │ ├── use-resolve-button-type.ts │ │ │ ├── use-root-containers.ts │ │ │ ├── use-store.ts │ │ │ ├── use-tab-direction.ts │ │ │ ├── use-text-value.ts │ │ │ ├── use-tracked-pointer.ts │ │ │ ├── use-tree-walker.ts │ │ │ └── use-window-event.ts │ │ ├── index.test.ts │ │ ├── index.ts │ │ ├── internal/ │ │ │ ├── dom-containers.ts │ │ │ ├── focus-sentinel.ts │ │ │ ├── hidden.ts │ │ │ ├── open-closed.ts │ │ │ ├── portal-force-root.ts │ │ │ └── stack-context.ts │ │ ├── keyboard.ts │ │ ├── mouse.ts │ │ ├── test-utils/ │ │ │ ├── accessibility-assertions.ts │ │ │ ├── execute-timeline.ts │ │ │ ├── fake-pointer.ts │ │ │ ├── html.ts │ │ │ ├── interactions.test.ts │ │ │ ├── interactions.ts │ │ │ ├── report-dom-node-changes.ts │ │ │ ├── ssr.ts │ │ │ ├── suppress-console-logs.ts │ │ │ └── vue-testing-library.ts │ │ └── utils/ │ │ ├── active-element-history.ts │ │ ├── calculate-active-index.ts │ │ ├── disposables.ts │ │ ├── document-ready.ts │ │ ├── dom.ts │ │ ├── env.ts │ │ ├── focus-management.ts │ │ ├── form.test.ts │ │ ├── form.ts │ │ ├── get-text-value.test.ts │ │ ├── get-text-value.ts │ │ ├── match.ts │ │ ├── micro-task.ts │ │ ├── once.ts │ │ ├── owner.ts │ │ ├── pipeline.ts │ │ ├── platform.ts │ │ ├── render.test.ts │ │ ├── render.ts │ │ ├── resolve-prop-value.ts │ │ └── store.ts │ ├── tsconfig.json │ └── types/ │ └── jest.d.ts ├── playgrounds/ │ ├── react/ │ │ ├── components/ │ │ │ ├── button.tsx │ │ │ └── input.tsx │ │ ├── data.ts │ │ ├── next-env.d.ts │ │ ├── next.config.js │ │ ├── package.json │ │ ├── pages/ │ │ │ ├── _app.tsx │ │ │ ├── _document.tsx │ │ │ ├── _error.tsx │ │ │ ├── combinations/ │ │ │ │ ├── form.tsx │ │ │ │ └── tabs-in-dialog.tsx │ │ │ ├── combobox/ │ │ │ │ ├── combobox-countries.tsx │ │ │ │ ├── combobox-open-on-focus.tsx │ │ │ │ ├── combobox-virtual-with-empty-states.tsx │ │ │ │ ├── combobox-virtualized.tsx │ │ │ │ ├── combobox-with-pure-tailwind.tsx │ │ │ │ ├── command-palette-with-groups.tsx │ │ │ │ ├── command-palette.tsx │ │ │ │ └── multi-select.tsx │ │ │ ├── dialog/ │ │ │ │ ├── dialog-built-in-transition.tsx │ │ │ │ ├── dialog-focus-issue.tsx │ │ │ │ ├── dialog-scroll-issue.tsx │ │ │ │ ├── dialog-with-shadow-children.tsx │ │ │ │ ├── dialog.tsx │ │ │ │ ├── scrollable-dialog.tsx │ │ │ │ ├── scrollable-page-with-dialog.tsx │ │ │ │ └── sibling-dialogs.tsx │ │ │ ├── disclosure/ │ │ │ │ └── disclosure.tsx │ │ │ ├── listbox/ │ │ │ │ ├── listbox-overlaps.tsx │ │ │ │ ├── listbox-with-pure-tailwind.tsx │ │ │ │ ├── multi-select.tsx │ │ │ │ └── multiple-elements.tsx │ │ │ ├── menu/ │ │ │ │ ├── menu-with-floating-ui.tsx │ │ │ │ ├── menu-with-framer-motion.tsx │ │ │ │ ├── menu-with-popper.tsx │ │ │ │ ├── menu-with-transition-and-popper.tsx │ │ │ │ ├── menu-with-transition.tsx │ │ │ │ ├── menu.tsx │ │ │ │ └── multiple-elements.tsx │ │ │ ├── popover/ │ │ │ │ └── popover.tsx │ │ │ ├── radio-group/ │ │ │ │ └── radio-group.tsx │ │ │ ├── styles.css │ │ │ ├── suspense/ │ │ │ │ └── portal.tsx │ │ │ ├── switch/ │ │ │ │ └── switch-with-pure-tailwind.tsx │ │ │ ├── tabs/ │ │ │ │ └── tabs-with-pure-tailwind.tsx │ │ │ └── transitions/ │ │ │ ├── appear.tsx │ │ │ ├── both-apis.tsx │ │ │ ├── component-examples/ │ │ │ │ ├── dropdown.tsx │ │ │ │ ├── modal.tsx │ │ │ │ ├── nested/ │ │ │ │ │ ├── hidden.tsx │ │ │ │ │ └── unmount.tsx │ │ │ │ └── peek-a-boo.tsx │ │ │ ├── full-page-examples/ │ │ │ │ ├── full-page-transition.tsx │ │ │ │ └── layout-with-sidebar.tsx │ │ │ └── react-hot-toast.tsx │ │ ├── postcss.config.js │ │ ├── tsconfig.json │ │ └── utils/ │ │ ├── class-names.ts │ │ ├── hooks/ │ │ │ └── use-popper.ts │ │ ├── match.ts │ │ └── resolve-all-examples.ts │ └── vue/ │ ├── index.html │ ├── package.json │ ├── postcss.config.js │ ├── src/ │ │ ├── .generated/ │ │ │ └── .gitignore │ │ ├── App.vue │ │ ├── KeyCaster.vue │ │ ├── Layout.vue │ │ ├── components/ │ │ │ ├── Button.vue │ │ │ ├── Home.vue │ │ │ ├── combinations/ │ │ │ │ ├── form.vue │ │ │ │ └── tabs-in-dialog.vue │ │ │ ├── combobox/ │ │ │ │ ├── _virtual-example.vue │ │ │ │ ├── combobox-countries.vue │ │ │ │ ├── combobox-open-on-focus.vue │ │ │ │ ├── combobox-virtual-with-empty-states.vue │ │ │ │ ├── combobox-virtualized.vue │ │ │ │ ├── combobox-with-pure-tailwind.vue │ │ │ │ ├── command-palette-with-groups.vue │ │ │ │ ├── command-palette.vue │ │ │ │ └── multi-select.vue │ │ │ ├── dialog/ │ │ │ │ ├── dialog.vue │ │ │ │ └── slide-over.vue │ │ │ ├── disclosure/ │ │ │ │ └── disclosure.vue │ │ │ ├── focus-trap/ │ │ │ │ └── focus-trap.vue │ │ │ ├── listbox/ │ │ │ │ ├── listbox.vue │ │ │ │ ├── multi-select.vue │ │ │ │ └── multiple-elements.vue │ │ │ ├── menu/ │ │ │ │ ├── menu-with-floating-ui.vue │ │ │ │ ├── menu-with-popper.vue │ │ │ │ ├── menu-with-transition-and-popper.vue │ │ │ │ ├── menu-with-transition.vue │ │ │ │ ├── menu.vue │ │ │ │ └── multiple-elements.vue │ │ │ ├── popover/ │ │ │ │ └── popover.vue │ │ │ ├── portal/ │ │ │ │ └── portal.vue │ │ │ ├── radio-group/ │ │ │ │ └── radio-group.vue │ │ │ ├── switch/ │ │ │ │ └── switch.vue │ │ │ └── tabs/ │ │ │ ├── simple-tabs.vue │ │ │ └── tabs.vue │ │ ├── data.ts │ │ ├── main.ts │ │ ├── playground-utils/ │ │ │ └── hooks/ │ │ │ └── use-popper.js │ │ ├── router.ts │ │ └── styles.css │ ├── tsconfig.json │ ├── vercel.json │ └── vite.config.js └── scripts/ ├── build.sh ├── lint.sh ├── make-nextjs-happy.js ├── package-path.js ├── release-channel.js ├── release-notes.js ├── resolve-files.js ├── rewrite-imports.js ├── test.sh └── watch.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintignore ================================================ /dist /packages/**/dist /node_modules /packages/**/node_modules ================================================ FILE: .github/CONTRIBUTING.md ================================================ # Contributing Thanks for your interest in contributing to Headless UI! Please take a moment to review this document **before submitting a pull request**. - [Pull requests](#pull-requests) - [Monorepo](#monorepo) - [Installation](#installation) - [Coding standards](#coding-standards) - [Running tests](#running-tests) - [Running playgrounds](#running-playgrounds) - [Scripts summary](#scripts-summary) ## Pull requests **Please ask first before starting work on any significant new features.** It's never a fun experience to have your pull request declined after investing a lot of time and effort into a new feature. To avoid this from happening, we request that contributors create [an issue](https://github.com/tailwindlabs/headlessui/issues) to first discuss any significant new features. This includes things like adding new components, exposing internal information, etc. Also make sure that you are making changes to both the `React` and `Vue` versions so that we can ensure feature parity. ## Monorepo The Headless UI repo is a monorepo using `npm` workspaces. ## Installation You only require a `npm install` in the root directory to install everything you need. ```sh npm install ``` ## Coding standards We use `prettier` for making sure that the codebase is formatted consistently. To automatically fix any style violations in your code, you can run: ```sh npm lint ``` **Note**: Whenever you commit, the lint check will run on all staged files. **Note**: In CI, we will only check your code, and not write with the formatted files. If you want to just check, then you can either run `npm run lint-check` or `CI=true npm run lint` ## Running tests You can run the test suite using the following commands: ```sh npm run test ``` You can also run them for React or Vue individually: ```sh npm run react test # or npm run vue test ``` Please ensure that the tests are passing when submitting a pull request. If you're adding new features to Headless UI, please include tests. ## Running playgrounds Currently the `React` playground (located in `packages/playground-react`) is a Next.js app that contains some examples which you can find in the `pages` directory. The `Vue` playground (located in `packages/playground-vue`) is a Vite app that contains some examples which you can find in the `src/components` directory. You can launch them by running: ```sh npm run react playground # or npm run vue playground ``` This will also start the necessary watchers so that you don't have to care about them. ## Scripts summary Global scripts, and some aliases: - `npm install`: install all dependencies for all packages - `npm run clean`: this will call all `npm run {package} clean` commands - `npm run build`: this will call all `npm run {package} build` commands - `npm run lint`: this will `lint` all packages - `npm run test`: this will `test` all packages - `npm run test`: run all jest tests - `npm run test --watch`: run all jest tests in interactive mode - `npm run test tabs`: run all jest tests filtered by `tabs` - `npm run test tabs --watch`: run all jest tests in interactive mode filtered by `tabs` Scripts per package: - `npm run react`: Prefix to run anything in the `@headlessui/react` package - `npm run react test`: run all jest tests - `npm run react test --watch`: run all jest tests in interactive mode - `npm run react test tabs`: run all jest tests filtered by `tabs` - `npm run react test tabs --watch`: run all jest tests in interactive mode filtered by `tabs` - `npm run react build`: build the final artefacts - `npm run react lint`: validate and fix the react codebase using prettier - `npm run react watch`: start a watcher for the react esm build - **Note**: this will be executed for you when using the `npm run react playground` - **Note**: this is not required for jest. You will probably never need this - `npm run react playground`: (alias) start a development server in the `playground-react` package - **Note**: this will also run `npm run react watch` for you, which means that you only need to execute `npm run react playground` - `npm run react clean`: this will remove `dist` files - `npm run vue`: Prefix to run anything in the `@headlessui/vue` package - `npm run vue test`: run all jest tests - `npm run vue test --watch`: run all jest tests in interactive mode - `npm run vue test tabs`: run all jest tests filtered by `tabs` - `npm run vue test tabs --watch`: run all jest tests in interactive mode filtered by `tabs` - `npm run vue build`: build the final artefacts - `npm run vue lint`: validate and fix the vue codebase using prettier - `npm run vue watch`: start a watcher for the vue esm build - **Note**: this will be executed for you when using the `npm run vue playground` - **Note**: this is not required for jest. You will probably never need this - `npm run vue playground`: (alias) start a development server in the `playground-vue` package - **Note**: this will also run `npm run vue watch` for you, which means that you only need to execute `npm run react playground` - `npm run vue clean`: this will remove `dist` files ================================================ FILE: .github/FUNDING.yml ================================================ custom: ['https://tailwindcss.com/sponsor'] ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.md ================================================ --- name: Bug report about: If you've already asked for help with a problem and confirmed something is broken with Headless UI itself, create a bug report. title: '' labels: '' assignees: '' --- **What package within Headless UI are you using?** For example: @headlessui/react **What version of that package are you using?** For example: v0.3.1 **What browser are you using?** For example: Chrome, Safari, or N/A **Reproduction URL** A public GitHub repo that includes a minimal reproduction of the bug. **Please do not link to your actual project**, what we need instead is a _minimal_ reproduction in a fresh project without any unnecessary code. This means it doesn't matter if your real project is private/confidential, since we want a link to a separate, isolated reproduction anyways. Unfortunately we can't provide support without a reproduction, and your issue will be closed with no comment if this is not provided. You can use one of the starting projects on CodeSandbox: - With React and Tailwind CSS: https://codesandbox.io/s/github/tailwindlabs/reproduction-headlessui-react - With Vue and Tailwind CSS: https://codesandbox.io/s/github/tailwindlabs/reproduction-headlessui-vue **Describe your issue** Describe the problem you're seeing, any important steps to reproduce and what behavior you expect instead. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Get Help url: https://github.com/tailwindlabs/headlessui/discussions/new?category=help about: If you can't get something to work the way you expect, open a question in our discussion forums. - name: Feature Request url: https://github.com/tailwindlabs/headlessui/discussions/new?category=ideas about: 'Suggest any ideas you have using our discussion forums.' - name: Documentation Issue url: https://github.com/tailwindlabs/headlessui/issues/new?title=%5BDOCS%5D:%20 about: 'For documentation issues, suggest changes on our documentation repository.' ================================================ FILE: .github/workflows/main.yml ================================================ name: CI on: push: branches: [main] pull_request: concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true env: NODE_VERSION: 18.x jobs: install: runs-on: ubuntu-latest steps: - name: Begin CI... uses: actions/checkout@v4 - name: Use Node ${{ env.NODE_VERSION }} uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - uses: actions/cache@v4 with: path: '**/node_modules' key: ${{ runner.os }}-${{ env.NODE_VERSION }}-modules-${{ hashFiles('**/package-lock.json') }} - name: Install dependencies run: npm ci env: CI: true lint: runs-on: ubuntu-latest needs: [install] steps: - name: Begin CI... uses: actions/checkout@v4 - uses: actions/cache@v4 with: path: '**/node_modules' key: ${{ runner.os }}-${{ env.NODE_VERSION }}-modules-${{ hashFiles('**/package-lock.json') }} - name: Lint run: npm run lint env: CI: true test: runs-on: ubuntu-latest needs: [install] steps: - name: Begin CI... uses: actions/checkout@v4 - uses: actions/cache@v4 with: path: '**/node_modules' key: ${{ runner.os }}-${{ env.NODE_VERSION }}-modules-${{ hashFiles('**/package-lock.json') }} - name: Test run: | npm run test || npm run test || npm run test || exit 1 env: CI: true build: runs-on: ubuntu-latest needs: [install] steps: - name: Begin CI... uses: actions/checkout@v4 - uses: actions/cache@v4 with: path: '**/node_modules' key: ${{ runner.os }}-${{ env.NODE_VERSION }}-modules-${{ hashFiles('**/package-lock.json') }} - name: Build run: npm run build env: CI: true check-types: runs-on: ubuntu-latest needs: [build] steps: - name: Begin CI... uses: actions/checkout@v4 - uses: actions/cache@v4 with: path: '**/node_modules' key: ${{ runner.os }}-${{ env.NODE_VERSION }}-modules-${{ hashFiles('**/package-lock.json') }} - name: Check Types run: npm run lint-types env: CI: true ================================================ FILE: .github/workflows/prepare-release.yml ================================================ name: Prepare Release on: workflow_dispatch: push: tags: - '**' env: CI: true permissions: contents: read jobs: build: permissions: contents: write # for softprops/action-gh-release to create GitHub release runs-on: ubuntu-latest strategy: matrix: node-version: [18] steps: - uses: actions/checkout@v4 - run: git fetch --tags -f - name: Resolve version id: vars run: | echo "TAG_NAME=${{ github.ref_name }}" >> $GITHUB_ENV - name: Get release notes run: | RELEASE_NOTES=$(npm run release-notes $TAG_NAME --silent) echo "RELEASE_NOTES<> $GITHUB_ENV echo "$RELEASE_NOTES" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} registry-url: 'https://registry.npmjs.org' - name: Release uses: softprops/action-gh-release@v2 with: draft: true tag_name: ${{ env.TAG_NAME }} body: | ${{ env.RELEASE_NOTES }} ================================================ FILE: .github/workflows/release-insiders.yml ================================================ name: Release Insiders on: push: branches: [main] concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true permissions: contents: read # https://docs.npmjs.com/generating-provenance-statements#publishing-packages-with-provenance-via-github-actions id-token: write jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: [20] steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} registry-url: 'https://registry.npmjs.org' - name: Use cached node_modules id: cache uses: actions/cache@v4 with: path: '**/node_modules' key: ${{ runner.os }}-${{ env.NODE_VERSION }}-modules-${{ hashFiles('**/package-lock.json') }} restore-keys: | nodeModules- - name: Install dependencies run: npm ci env: CI: true - name: Test run: | npm run test || npm run test || npm run test || exit 1 env: CI: true - name: Resolve version id: vars run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" - name: 'Version based on commit: 0.0.0-insiders.${{ steps.vars.outputs.sha_short }}' run: npm version -w packages 0.0.0-insiders.${{ steps.vars.outputs.sha_short }} --force --no-git-tag-version - name: Publish run: npm publish -w packages --provenance --tag insiders env: CI: true NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: release: types: [published] concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true permissions: contents: read # https://docs.npmjs.com/generating-provenance-statements#publishing-packages-with-provenance-via-github-actions id-token: write env: CI: true jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: [18] steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} registry-url: 'https://registry.npmjs.org' - name: Use cached node_modules id: cache uses: actions/cache@v4 with: path: '**/node_modules' key: ${{ runner.os }}-${{ env.NODE_VERSION }}-modules-${{ hashFiles('**/package-lock.json') }} restore-keys: | nodeModules- - name: Install dependencies run: npm ci env: CI: true - name: Test run: | npm run test || npm run test || npm run test || exit 1 env: CI: true - name: Calculate environment variables run: | echo "TAG_NAME=${{ github.event.tag_name }}" >> $GITHUB_ENV echo "RELEASE_CHANNEL=$(npm run release-channel $TAG_NAME --silent)" >> $GITHUB_ENV echo "PACKAGE_PATH=$(npm run package-path $TAG_NAME --silent)" >> $GITHUB_ENV - name: Publish run: npm publish ${{ env.PACKAGE_PATH }} --provenance --tag ${{ env.RELEASE_CHANNEL }} env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ================================================ FILE: .gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /packages/**/node_modules /playgrounds/**/node_modules # testing /coverage /packages/**/coverage /playgrounds/**/coverage # logs *.log /packages/**/*.log /playgrounds/**/*.log # next.js /.next/ /playgrounds/**/.next/ /out/ /packages/**/out/ /playgrounds/**/out/ # production /dist /packages/**/dist /playgrounds/**/dist # misc .DS_Store *.pem .cache # debug npm-debug.log* yarn-debug.log* yarn-error.log* # local env files .env.local .env.development.local .env.test.local .env.production.local # vercel .vercel ================================================ FILE: .prettierignore ================================================ dist/ node_modules/ coverage/ .next/ ================================================ FILE: .swcrc ================================================ { "minify": false, "jsc": { "parser": { "syntax": "typescript", "tsx": true, "decorators": false, "dynamicImport": false } } } ================================================ FILE: CHANGELOG.md ================================================ # Changelog Each package has its own changelog. - [@headlessui/react](https://github.com/tailwindlabs/headlessui/blob/main/packages/@headlessui-react/CHANGELOG.md) - [@headlessui/vue](https://github.com/tailwindlabs/headlessui/blob/main/packages/@headlessui-vue/CHANGELOG.md) - [@headlessui/tailwindcss](https://github.com/tailwindlabs/headlessui/blob/main/packages/@headlessui-tailwindcss/CHANGELOG.md) ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Tailwind Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

Headless UI

A set of completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.

--- ## Documentation For full documentation, visit [headlessui.com](https://headlessui.com). ### Installing the latest version You can install the latest version by using: - `npm install @headlessui/react@latest` - `npm install @headlessui/vue@latest` ### Installing the insiders version You can install the insiders version (which points to whatever the latest commit on the `main` branch is) by using: - `npm install @headlessui/react@insiders` - `npm install @headlessui/vue@insiders` **Note:** The insiders build doesn't follow semver and therefore doesn't guarantee that the APIs will be the same once they are released. ## Packages | Name | Version | Downloads | | :------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------: | | [`@headlessui/react`](https://github.com/tailwindlabs/headlessui/tree/main/packages/%40headlessui-react) | [![npm version](https://img.shields.io/npm/v/@headlessui/react.svg)](https://www.npmjs.com/package/@headlessui/react) | [![npm downloads](https://img.shields.io/npm/dt/@headlessui/react.svg)](https://www.npmjs.com/package/@headlessui/react) | | [`@headlessui/vue`](https://github.com/tailwindlabs/headlessui/tree/main/packages/%40headlessui-vue) | [![npm version](https://img.shields.io/npm/v/@headlessui/vue.svg)](https://www.npmjs.com/package/@headlessui/vue) | [![npm downloads](https://img.shields.io/npm/dt/@headlessui/vue.svg)](https://www.npmjs.com/package/@headlessui/vue) | | [`@headlessui/tailwindcss`](https://github.com/tailwindlabs/headlessui/tree/main/packages/%40headlessui-tailwindcss) | [![npm version](https://img.shields.io/npm/v/@headlessui/tailwindcss.svg)](https://www.npmjs.com/package/@headlessui/tailwindcss) | [![npm downloads](https://img.shields.io/npm/dt/@headlessui/tailwindcss.svg)](https://www.npmjs.com/package/@headlessui/tailwindcss) | ## Community For help, discussion about best practices, or feature ideas: [Discuss Headless UI on GitHub](https://github.com/tailwindlabs/headlessui/discussions) ## Contributing If you're interested in contributing to Headless UI, please read our [contributing docs](https://github.com/tailwindlabs/headlessui/blob/main/.github/CONTRIBUTING.md) **before submitting a pull request**. ================================================ FILE: jest/create-jest-config.cjs ================================================ module.exports = function createJestConfig(root, options) { let { setupFilesAfterEnv = [], transform = {}, ...rest } = options return Object.assign( { rootDir: root, setupFilesAfterEnv: [ '../../jest/custom-matchers.ts', '../../jest/polyfills.ts', ...setupFilesAfterEnv, ], transform: { '^.+\\.(t|j)sx?$': '@swc/jest', ...transform, }, globals: { __DEV__: true, }, }, rest ) } ================================================ FILE: jest/custom-matchers.ts ================================================ import '@testing-library/jest-dom/extend-expect' // Assuming requestAnimationFrame is roughly 60 frames per second let frame = 1000 / 60 let amountOfFrames = 2 let formatter = new Intl.NumberFormat('en') expect.extend({ toBeWithinRenderFrame(actual, expected) { let min = expected - frame * amountOfFrames let max = expected + frame * amountOfFrames let pass = actual >= min && actual <= max return { message: pass ? () => { return `expected ${actual} not to be within range of a frame ${formatter.format( min )} - ${formatter.format(max)}` } : () => { return `expected ${actual} not to be within range of a frame ${formatter.format( min )} - ${formatter.format(max)}` }, pass, } }, }) ================================================ FILE: jest/polyfills.ts ================================================ import { mockAnimationsApi, mockResizeObserver } from 'jsdom-testing-mocks' mockAnimationsApi() // `Element.prototype.getAnimations` and `CSSTransition` polyfill mockResizeObserver() // `ResizeObserver` polyfill // JSDOM Doesn't implement innerText yet: https://github.com/jsdom/jsdom/issues/1245 // So this is a hacky way of implementing it using `textContent`. // Real implementation doesn't use textContent because: // > textContent gets the content of all elements, including ================================================ FILE: playgrounds/vue/package.json ================================================ { "name": "playground-vue", "private": true, "version": "0.0.0", "directories": { "example": "examples" }, "scripts": { "prebuild": "npm run build --workspace=@headlessui/vue && npm run build --workspace=@headlessui/tailwindcss", "predev": "npm run build --workspace=@headlessui/vue && npm run build --workspace=@headlessui/tailwindcss", "dev:tailwindcss": "npm run watch --workspace=@headlessui/tailwindcss", "dev:headlessui": "npm run watch --workspace=@headlessui/vue", "dev:next": "vite serve", "dev": "npm-run-all -p dev:*", "build": "NODE_ENV=production vite build", "lint-types": "echo", "clean": "rimraf ./dist" }, "dependencies": { "@headlessui/vue": "*", "@heroicons/vue": "^1.0.6", "@tailwindcss/forms": "^0.5.2", "@tailwindcss/postcss": "^4.1.3", "@tailwindcss/typography": "^0.5.2", "postcss": "^8.4.14", "tailwindcss": "^4.1.3", "vue": "^3.4.27", "vue-flatpickr-component": "^9.0.5", "vue-router": "^4.3.2" }, "devDependencies": { "@floating-ui/vue": "^1.0.2", "@vitejs/plugin-vue": "^5.0.5", "vite": "^5.2.12" } } ================================================ FILE: playgrounds/vue/postcss.config.js ================================================ module.exports = { plugins: { '@tailwindcss/postcss': {}, }, } ================================================ FILE: playgrounds/vue/src/.generated/.gitignore ================================================ * !.gitignore ================================================ FILE: playgrounds/vue/src/App.vue ================================================ ================================================ FILE: playgrounds/vue/src/KeyCaster.vue ================================================ ================================================ FILE: playgrounds/vue/src/Layout.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/Button.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/Home.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/combinations/form.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/combinations/tabs-in-dialog.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/combobox/_virtual-example.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/combobox/combobox-countries.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/combobox/combobox-open-on-focus.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/combobox/combobox-virtual-with-empty-states.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/combobox/combobox-virtualized.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/combobox/combobox-with-pure-tailwind.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/combobox/command-palette-with-groups.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/combobox/command-palette.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/combobox/multi-select.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/dialog/dialog.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/dialog/slide-over.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/disclosure/disclosure.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/focus-trap/focus-trap.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/listbox/listbox.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/listbox/multi-select.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/listbox/multiple-elements.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/menu/menu-with-floating-ui.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/menu/menu-with-popper.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/menu/menu-with-transition-and-popper.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/menu/menu-with-transition.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/menu/menu.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/menu/multiple-elements.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/popover/popover.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/portal/portal.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/radio-group/radio-group.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/switch/switch.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/tabs/simple-tabs.vue ================================================ ================================================ FILE: playgrounds/vue/src/components/tabs/tabs.vue ================================================ ================================================ FILE: playgrounds/vue/src/data.ts ================================================ export let countries = [ 'Afghanistan', 'Albania', 'Algeria', 'American Samoa', 'Andorra', 'Angola', 'Anguilla', 'Antarctica', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Aruba', 'Australia', 'Austria', 'Azerbaijan', 'Bahamas (the)', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bermuda', 'Bhutan', 'Bolivia (Plurinational State of)', 'Bonaire, Sint Eustatius and Saba', 'Bosnia and Herzegovina', 'Botswana', 'Bouvet Island', 'Brazil', 'British Indian Ocean Territory (the)', 'Brunei Darussalam', 'Bulgaria', 'Burkina Faso', 'Burundi', 'Cabo Verde', 'Cambodia', 'Cameroon', 'Canada', 'Cayman Islands (the)', 'Central African Republic (the)', 'Chad', 'Chile', 'China', 'Christmas Island', 'Cocos (Keeling) Islands (the)', 'Colombia', 'Comoros (the)', 'Congo (the Democratic Republic of the)', 'Congo (the)', 'Cook Islands (the)', 'Costa Rica', 'Croatia', 'Cuba', 'Curaçao', 'Cyprus', 'Czechia', "Côte d'Ivoire", 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic (the)', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Eswatini', 'Ethiopia', 'Falkland Islands (the) [Malvinas]', 'Faroe Islands (the)', 'Fiji', 'Finland', 'France', 'French Guiana', 'French Polynesia', 'French Southern Territories (the)', 'Gabon', 'Gambia (the)', 'Georgia', 'Germany', 'Ghana', 'Gibraltar', 'Greece', 'Greenland', 'Grenada', 'Guadeloupe', 'Guam', 'Guatemala', 'Guernsey', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Heard Island and McDonald Islands', 'Holy See (the)', 'Honduras', 'Hong Kong', 'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran (Islamic Republic of)', 'Iraq', 'Ireland', 'Isle of Man', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jersey', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', "Korea (the Democratic People's Republic of)", 'Korea (the Republic of)', 'Kuwait', 'Kyrgyzstan', "Lao People's Democratic Republic (the)", 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Macao', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands (the)', 'Martinique', 'Mauritania', 'Mauritius', 'Mayotte', 'Mexico', 'Micronesia (Federated States of)', 'Moldova (the Republic of)', 'Monaco', 'Mongolia', 'Montenegro', 'Montserrat', 'Morocco', 'Mozambique', 'Myanmar', 'Namibia', 'Nauru', 'Nepal', 'Netherlands (the)', 'New Caledonia', 'New Zealand', 'Nicaragua', 'Niger (the)', 'Nigeria', 'Niue', 'Norfolk Island', 'Northern Mariana Islands (the)', 'Norway', 'Oman', 'Pakistan', 'Palau', 'Palestine, State of', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines (the)', 'Pitcairn', 'Poland', 'Portugal', 'Puerto Rico', 'Qatar', 'Republic of North Macedonia', 'Romania', 'Russian Federation (the)', 'Rwanda', 'Réunion', 'Saint Barthélemy', 'Saint Helena, Ascension and Tristan da Cunha', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Martin (French part)', 'Saint Pierre and Miquelon', 'Saint Vincent and the Grenadines', 'Samoa', 'San Marino', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore', 'Sint Maarten (Dutch part)', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'South Africa', 'South Georgia and the South Sandwich Islands', 'South Sudan', 'Spain', 'Sri Lanka', 'Sudan (the)', 'Suriname', 'Svalbard and Jan Mayen', 'Sweden', 'Switzerland', 'Syrian Arab Republic', 'Taiwan', 'Tajikistan', 'Tanzania, United Republic of', 'Thailand', 'Timor-Leste', 'Togo', 'Tokelau', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Turks and Caicos Islands (the)', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates (the)', 'United Kingdom of Great Britain and Northern Ireland (the)', 'United States Minor Outlying Islands (the)', 'United States of America (the)', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Venezuela (Bolivarian Republic of)', 'Viet Nam', 'Virgin Islands (British)', 'Virgin Islands (U.S.)', 'Wallis and Futuna', 'Western Sahara', 'Yemen', 'Zambia', 'Zimbabwe', 'Åland Islands', ] export let timezones: string[] = Intl.supportedValuesOf('timeZone') ================================================ FILE: playgrounds/vue/src/main.ts ================================================ import { createApp } from 'vue' // @ts-expect-error TODO: Properly handle this import App from './App.vue' import router from './router' import './styles.css' createApp(App).use(router).mount('#app') ================================================ FILE: playgrounds/vue/src/playground-utils/hooks/use-popper.js ================================================ import { createPopper } from '@popperjs/core' import { onMounted, ref, watchEffect } from 'vue' export function usePopper(options) { let reference = ref(null) let popper = ref(null) onMounted(() => { watchEffect((onInvalidate) => { if (!popper.value) return if (!reference.value) return let popperEl = popper.value.el || popper.value let referenceEl = reference.value.el || reference.value if (!(referenceEl instanceof HTMLElement)) return if (!(popperEl instanceof HTMLElement)) return let { destroy } = createPopper(referenceEl, popperEl, options) onInvalidate(destroy) }) }) return [reference, popper] } ================================================ FILE: playgrounds/vue/src/router.ts ================================================ import { createRouter, createWebHistory, RouterView } from 'vue-router' type Component = import('vue').Component function buildRoutes() { function titleCase(str) { return str .toLocaleLowerCase() .split('-') .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(' ') } // 1. Get all the components in the src/components directory let files = Object.entries( import.meta.glob('./components/**/*.vue', { eager: true, import: 'default', }) ) as [string, Component][] // 2.a. Swap the file names for route urls // 2.b. Resolve the default import for each component files = files.map(([file, component]) => [ file .replace('./components/', '/') .replace(/\.vue$/, '') .toLocaleLowerCase() .replace(/^\/home$/g, '/'), component, ]) let alreadyAdded = new Set() // 3. Add a route for each directory (if not already added) files = files.flatMap((entry) => { let dirs = entry[0].split('/').slice(1, -1) let paths: [string, Component][] = [] for (const [idx] of dirs.entries()) { let path = `/` + dirs.slice(0, idx + 1).join('/') if (alreadyAdded.has(path)) { continue } paths.push([path, RouterView]) alreadyAdded.add(path) } return [...paths, entry] }) // 4. Sort the routes alphabetically and by length files.sort((a, b) => a[0].localeCompare(b[0])) // 5. Create the nested routes let routes = [] let routesByPath = {} for (let [path, component] of files) { let prefix = path.split('/').slice(0, -1).join('/') let parent = routesByPath[prefix]?.children ?? routes let route = { path, component: component, children: [], meta: { name: titleCase(path.match(/[^/]+$/)?.[0] ?? 'Home'), isRoot: parent === routes, }, } parent.push((routesByPath[path] = route)) } return routes } export default createRouter({ history: createWebHistory(), routes: buildRoutes(), }) ================================================ FILE: playgrounds/vue/src/styles.css ================================================ @import 'tailwindcss'; @plugin '@tailwindcss/forms'; @plugin '@tailwindcss/typography'; @plugin '@headlessui/tailwindcss'; /* The default border color has changed to `currentcolor` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. If we ever want to remove these styles, we need to add an explicit border color utility to any element that depends on these defaults. */ @layer base { *, ::after, ::before, ::backdrop, ::file-selector-button { border-color: var(--color-gray-200, currentcolor); } } ================================================ FILE: playgrounds/vue/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "lib": ["dom", "dom.iterable", "esnext"], "types": ["vite/client"], "allowJs": true, "skipLibCheck": true, "strict": false, "forceConsistentCasingInFileNames": true, "noEmit": true, "incremental": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "node", "downlevelIteration": true, "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve" }, "include": ["**/*.ts", "**/*.tsx", "**/*.vue"], "exclude": ["node_modules"] } ================================================ FILE: playgrounds/vue/vercel.json ================================================ { "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] } ================================================ FILE: playgrounds/vue/vite.config.js ================================================ import vue from '@vitejs/plugin-vue' import { defineConfig } from 'vite' export default defineConfig({ server: { port: 3000 }, plugins: [vue()], }) ================================================ FILE: scripts/build.sh ================================================ #!/usr/bin/env bash set -e SCRIPT_DIR=$(cd ${0%/*} && pwd -P) # Make Next.js barrel file optimizations happy. Using `@swc-node/reigster` because we need to handle # the TypeScript files. # TODO: make this script run automatically. # node -r @swc-node/register "${SCRIPT_DIR}/make-nextjs-happy.js" # Known variables SRC='./src' DST='./dist' name="headlessui" input="./${SRC}/index.ts" # Find executables resolver="${SCRIPT_DIR}/resolve-files.js" rewriteImports="${SCRIPT_DIR}/rewrite-imports.js" # Setup shared options for esbuild sharedOptions=() sharedOptions+=("--platform=browser") sharedOptions+=("--target=es2019") # Generate actual builds # ESM resolverOptions=() resolverOptions+=($SRC) resolverOptions+=('/**/*.{ts,tsx}') resolverOptions+=('--ignore=.test.,__mocks__') INPUT_FILES=$($resolver ${resolverOptions[@]}) NODE_ENV=production npx esbuild $INPUT_FILES --format=esm --outdir=$DST --outbase=$SRC --minify --pure:React.createElement --define:process.env.TEST_BYPASS_TRACKED_POINTER="false" --define:__DEV__="false" ${sharedOptions[@]} & NODE_ENV=production npx esbuild $input --format=esm --outfile=$DST/$name.esm.js --outbase=$SRC --minify --pure:React.createElement --define:process.env.TEST_BYPASS_TRACKED_POINTER="false" --define:__DEV__="false" ${sharedOptions[@]} & # Common JS NODE_ENV=production npx esbuild $input --format=cjs --outfile=$DST/$name.prod.cjs --minify --bundle --pure:React.createElement --define:process.env.TEST_BYPASS_TRACKED_POINTER="false" --define:__DEV__="false" ${sharedOptions[@]} $@ & NODE_ENV=development npx esbuild $input --format=cjs --outfile=$DST/$name.dev.cjs --bundle --pure:React.createElement --define:process.env.TEST_BYPASS_TRACKED_POINTER="false" --define:__DEV__="true" ${sharedOptions[@]} $@ & # Generate ESM types tsc --emitDeclarationOnly --outDir $DST & wait # Generate CJS types # This is a bit of a hack, but it works because the same output works for both cp $DST/index.d.ts $DST/index.d.cts # Copy build files over cp -rf ./build/* $DST/ # Wait for all the scripts to finish wait # Rewrite ESM imports 😤 $rewriteImports "$DST" '/**/*.js' $rewriteImports "$DST" '/**/*.d.ts' # Remove test related files rm -rf `$resolver "$DST" '/**/*.{test,__mocks__,}.*'` rm -rf `$resolver "$DST" '/**/test-utils/*'` ================================================ FILE: scripts/lint.sh ================================================ #!/usr/bin/env bash set -e ROOT_DIR="$(git rev-parse --show-toplevel)/" TARGET_DIR="$(pwd)" RELATIVE_TARGET_DIR="${TARGET_DIR/$ROOT_DIR/}" # INFO: This script is always run from the root of the repository. If we execute this script from a # package then the filters (in this case a path to $RELATIVE_TARGET_DIR) will be applied. pushd $ROOT_DIR > /dev/null prettierArgs=() if ! [ -z "$CI" ]; then prettierArgs+=("--check") else prettierArgs+=("--write") fi # Add default arguments prettierArgs+=('--ignore-unknown') # Passthrough arguments and flags prettierArgs+=($@) # Ensure that a path is passed, otherwise default to the current directory if [ -z "$@" ]; then prettierArgs+=("$RELATIVE_TARGET_DIR") fi # Execute npx prettier "${prettierArgs[@]}" popd > /dev/null ================================================ FILE: scripts/make-nextjs-happy.js ================================================ import fs from 'fs' import path from 'path' import prettier from 'prettier' import * as HUI from '../packages/@headlessui-react/src/index.ts' let customRemaps = { tab: 'tabs', radio: 'radio-group', data: 'data-interactive', focus: 'focus-trap', } let components = Object.keys(HUI) async function run() { for (let component of components) { let name = component.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() let module = name.split('-')[0] module = customRemaps[module] ?? module let filePath = path.resolve( __dirname, '..', 'packages', '@headlessui-react', 'src', 'components', name, `${name}.tsx` ) // Main module path already exists if (!fs.existsSync(filePath)) { fs.mkdirSync(path.dirname(filePath), { recursive: true }) fs.writeFileSync(filePath, await template(component, module)) } } } async function template(name, module) { return await prettier.format( [ '// Next.js barrel file improvements (GENERATED FILE)', `export type * from '../${module}/${module}';`, `export { ${name} } from '../${module}/${module}';`, ].join('\n'), { parser: 'typescript' } ) } run() ================================================ FILE: scripts/package-path.js ================================================ // Given a version, figure out what the release notes are so that we can use this to pre-fill the // relase notes on a GitHub release for the current version. let path = require('path') let { execSync } = require('child_process') let tag = process.argv[2] || execSync(`git describe --tags --abbrev=0`).toString().trim() let pkgPath = path.resolve( __dirname, '..', 'packages', tag.slice(0, tag.indexOf('@', 1)).replace('/', '-') ) console.log('./' + path.relative(process.cwd(), pkgPath)) ================================================ FILE: scripts/release-channel.js ================================================ let path = require('path') let { execSync } = require('child_process') // Given a version, figure out what the release channel is so that we can publish to the correct // channel on npm. // // E.g.: // // 1.2.3 -> latest (default) // 0.0.0-insiders.ffaa88 -> insiders // 4.1.0-alpha.4 -> alpha let tag = process.argv[2] || execSync(`git describe --tags --abbrev=0`).toString().trim() let pkgPath = path.resolve( __dirname, '..', 'packages', tag.slice(0, tag.indexOf('@', 1)).replace('/', '-') ) let version = require(path.resolve(pkgPath, 'package.json')).version let match = /\d+\.\d+\.\d+-(.*)\.\d+/g.exec(version) if (match) { // We want to release alpha to the next channel because it will be the next version if (match[1] === 'alpha') match[1] = 'next' console.log(match[1]) } else { console.log('latest') } ================================================ FILE: scripts/release-notes.js ================================================ // Given a version, figure out what the release notes are so that we can use this to pre-fill the // relase notes on a GitHub release for the current version. let path = require('path') let fs = require('fs') let { execSync } = require('child_process') let tag = process.argv[2] || execSync(`git describe --tags --abbrev=0`).toString().trim() let pkgPath = path.resolve( __dirname, '..', 'packages', tag.slice(0, tag.indexOf('@', 1)).replace('/', '-') ) let version = require(path.resolve(pkgPath, 'package.json')).version let changelog = fs.readFileSync(path.resolve(pkgPath, 'CHANGELOG.md'), 'utf8') let match = new RegExp( `## \\[${version}\\] - (.*)\\n\\n([\\s\\S]*?)\\n(?:(?:##\\s)|(?:\\[))`, 'g' ).exec(changelog) if (match) { let [, , notes] = match console.log(notes.trim()) } else { console.log(`Placeholder release notes for version: v${version}`) } ================================================ FILE: scripts/resolve-files.js ================================================ #!/usr/bin/env node let fastGlob = require('fast-glob') let parts = process.argv.slice(2) let [args, flags] = parts.reduce( ([args, flags], part) => { if (part.startsWith('--')) { flags[part.slice(2, part.indexOf('='))] = part.slice(part.indexOf('=') + 1) } else { args.push(part) } return [args, flags] }, [[], {}] ) flags.ignore = flags.ignore ?? '' flags.ignore = flags.ignore.split(',').filter(Boolean) console.log( fastGlob .sync(args.join('')) .filter((file) => { for (let ignore of flags.ignore) { if (file.includes(ignore)) { return false } } return true }) .join('\n') ) ================================================ FILE: scripts/rewrite-imports.js ================================================ #!/usr/bin/env node let fs = require('fs') let path = require('path') let fastGlob = require('fast-glob') console.time('Rewrote imports in') fastGlob.sync([process.argv.slice(2).join('')]).forEach((file) => { file = path.resolve(process.cwd(), file) let content = fs.readFileSync(file, 'utf8') let result = content.replace(/(import|export)([^"']*?)(["'])\.(.*?)\3/g, (full, a, b, _, d) => { // For idempotency reasons, if `.js` already exists, then we can skip this. This allows us to // run this script over and over again without adding .js files every time. if (d.endsWith('.js')) { return full } return `${a}${b}'.${d}.js'` }) if (result !== content) { fs.writeFileSync(file, result, 'utf8') } }) console.timeEnd('Rewrote imports in') ================================================ FILE: scripts/test.sh ================================================ #!/usr/bin/env bash set -e jestArgs=() # Add default arguments jestArgs+=("--passWithNoTests") # Add arguments based on environment variables if ! [ -z "$CI" ]; then jestArgs+=("--maxWorkers=4") jestArgs+=("--ci") fi # Passthrough arguments and flags jestArgs+=($@) # Execute npx jest "${jestArgs[@]}" ================================================ FILE: scripts/watch.sh ================================================ #!/usr/bin/env bash set -e # Known variables outdir="./dist" name="headlessui" input="./src/index.ts" # Setup shared options for esbuild sharedOptions=() sharedOptions+=("--bundle") sharedOptions+=("--platform=browser") sharedOptions+=("--target=es2020") # Generate actual builds NODE_ENV=development npx esbuild $input --format=esm --outfile=$outdir/$name.esm.js --sourcemap ${sharedOptions[@]} $@ --watch