Releases: qwikifiers/qwik-ui
[email protected]
@qwik-ui/[email protected]
Minor Changes
-
Combobox v2, New Dropdown Component, and Progress bar reaches beta! (by @thejackshelton in #838)
0.5 continues our move towards a 1.0 release. It includes a few breaking changes to the Combobox in order to make sure that the components have a clear API.
Below is a migration guide of API's for the Combobox.
Combobox
The combobox has been refactored from the ground up, including new features, components, and QOL updates.
Anatomy changes
The new Combobox anatomy is as follows:
import { component$ } from '@builder.io/qwik'; import { Combobox } from '@qwik-ui/headless'; import { LuCheck } from '@qwikest/icons/lucide'; export default component$(() => { return ( <Combobox.Root> <Combobox.Label>label</Combobox.Label> <Combobox.Control> <Combobox.Input /> <Combobox.Trigger>trigger</Combobox.Trigger> </Combobox.Control> <Combobox.Popover> <Combobox.Item> <Combobox.ItemLabel>item label</Combobox.ItemLabel> <Combobox.ItemIndicator> <LuCheck /> </Combobox.ItemIndicator> </Combobox.Item> </Combobox.Popover> </Combobox.Root> ); });
Anatomy Changes
-
Combobox.Option has been renamed to Combobox.Item:
- The item is no longer restricted to a string value; any UI can be placed inside the item.
- Use the
Combobox.ItemLabel
component to display the item's label, which becomes the item's value if novalue
prop is passed to theCombobox.Item
. (required)
-
Combobox.Listbox has been deprecated.
-
Combobox.ItemLabel has been added:
- Move the string value that was once inside
Combobox.Option
intoCombobox.ItemLabel
. (required)
- Move the string value that was once inside
-
Combobox.ItemIndicator has been added:
- This component is used to render UI based on the selected state of the item. (optional)
-
Combobox.Description has been added:
- The text rendered inside the description component is displayed to screen readers as an accessible description of the combobox. (optional)
-
Combobox.ErrorMessage has been added:
- When this component is rendered, the Combobox will be marked as invalid. (optional)
-
Combobox.HiddenNativeSelect has been added:
- A native select element allows the submission of forms with the combobox. This component is visually hidden and hidden from screen readers. (optional)
-
Combobox.Group has been added:
- Used to visually group related items together. (optional)
-
Combobox.GroupLabel has been added:
- Provides an accessible name for the group. (optional)
-
Combobox.Empty has been added:
- Displays a message when there are no items to display.
- Previously, an empty popup was displayed when the combobox was empty. The new default behavior is to close the popup unless the
Combobox.Empty
component is rendered. (optional)
API Changes
Rendering Items (required)
The
optionRenderer# Changelog prop on the
Combobox.Listbox` component has been deprecated.Instead:
- pass a
<Combobox.Item />
as a child of the<Combobox.Popover />
component. - pass a
Combobox.ItemLabel
as a child of the<Combobox.Item />
component.
It should look something like this:
<Combobox.Popover> <Combobox.Item> <Combobox.ItemLabel>item label</Combobox.ItemLabel> {/* other content */} </Combobox.Item> </Combobox.Popover>
You are now in full control of how the item is rendered. Because you control the rendering of the item, there is no need for the previous API's including the data's key values.
optionDisabledKey
,optionValueKey
, andoptionLabelKey
props have been removed.There is also no need to pass an
index
prop to theCombobox.Item
component. It is handled automatically.Pass in distinct values
The
value
prop has been added to theCombobox.Item
component to allow for passing in a distinct value for the combobox.For example, identifying a user by their ID, rather than the display username.
Add your own filter
Filters are an important part of the combobox. It was a design decision in this refactor to make filtering data as easy as possible to integrate with other tools and libraries.
The
filter# Changelog prop has been replaced. Instead, items are by default filtered by the
includes` function.To opt-out of the default filter, add the
filter={false}
prop to theCombobox.Root
component, which will disable the default filter.import { component$, useSignal, useStyles$, useTask$ } from '@builder.io/qwik'; import { Combobox } from '@qwik-ui/headless'; import { LuCheck, LuChevronDown } from '@qwikest/icons/lucide'; import { matchSorter } from 'match-sorter'; export default component$(() => { useStyles$(styles); const inputValue = useSignal(''); const filteredItems = useSignal<string[]>([]); const fruits = [ 'Apple', 'Apricot', 'Bilberry', 'Blackberry', 'Blackcurrant', 'Currant', 'Cherry', 'Coconut', ]; useTask$(({ track }) => { track(() => inputValue.value); filteredItems.value = matchSorter(fruits, inputValue.value); }); return ( <Combobox.Root filter={false}> <Combobox.Label>Fruits</Combobox.Label> <Combobox.Control> <Combobox.Input bind:value={inputValue} /> <Combobox.Trigger> <LuChevronDown /> </Combobox.Trigger> </Combobox.Control> <Combobox.Popover gutter={8}> {filteredItems.value.map((fruit) => ( <Combobox.Item key={fruit}> <Combobox.ItemLabel>{fruit}</Combobox.ItemLabel> <Combobox.ItemIndicator> <LuCheck /> </Combobox.ItemIndicator> </Combobox.Item> ))} </Combobox.Popover> </Combobox.Root> ); });
The above example uses the
matchSorter
function from thematch-sorter
library to filter the items.bind:value
instead ofbind:selectedIndex
bind:value has been added in favor of what was previously used to reactively control the combobox, bind:selectedIndex.
This change was needed to create a more consistent API across components, but also keeping the state in the case of custom filters.
onChange# Changelog has been added to the
Combobox.Root` component so that you can listen to when the selected value changes.Add initial values to the combobox
The
value
prop has been added to theCombobox.Root
component to select the initial value of the combobox when it is rendered.defaultLabel
has been removed, as it does not reflect the selected state of the combobox.Input state management
bind:inputValue
(on the Root) has been replaced by using thebind:value
prop on the<Combobox.Input />
component instead.You can also now listen to when the input value changes by using the
onInput# Changelog prop on the
<Combobox.Root />` component.Passing refs to the combobox (experimental)
The combobox is the first component to support passing refs! You can now pass a ref of your own to any component inside the combobox. This is an experimental feature, and we are still working on it, use at your own risk.
const inputRef = useSignal<HTMLInputElement>(); <Combobox.Input ref={inputRef} /> <button onClick$={() => (inputRef.value?.focus())}>Focus input</button>
Multiple selections
You can now select multiple items by passing the
multiple
prop to the<Combobox.Root />
component.removeOnBackspace
When in multiple selection mode, and the
removeOnBackspace
prop has been added to theCombobox.Root
component, selected items can be removed by pressing the backspace key. (when the input is empty)Managing display values
bind:displayValue
has been added to theCombobox.Root
component to allow for grabbing the updated display values of the combobox.This allows for full control over each display item. For example, a couple of display values shown as pills.
Item indicators
The item indicator shows when the item is selected. Inside can be the UI of choice.
bind:open
instead ofbind:isListboxOpen
bind:open has been added to control the open state of the listbox, replacing bind:isListboxOpen.
onOpenChange# Changelog has been added to the
Combobox.Root` component so that you can listen to when the popup opens or closes.Focus State Management
bind:isInputFocused has been deprecated. Instead, if you decide to manage focus state using event handlers like onFocus$ and onBlur$. OR pass a ref to the
Combobox.Input
component.Placeholders
The placeholder prop has been added to the
Combobox.Root
component to allow for a custom placeholder.Environment
Like many of the latest components in Qwik UI, each function of the Combobox has been optimized to run in both SSR or CSR automatically depending on the environment.
Looping
Looping is now opt-in by default. To enable looping, add the
loop
prop to theCombobox.Root
component.Scrolling
When a scrollbar is present, the combobox will now properly scroll to the selected item. The scroll behavior can be customized using the
scrollOptions
p... -
@qwik-ui/[email protected]
Patch Changes
-
✨ new Select.ErrorMessage component (by @thejackshelton in #825)
feat: data-invalid attribute to style when the select is invalid
feat: new Select.Description component
@qwik-ui/[email protected]
Patch Changes
-
🐞🩹 select validates correctly with modular forms (by @thejackshelton in #814)
-
refactor: improved select focus navigation (by @thejackshelton in #822)
-
🐞🩹 select can now be reactively disabled (by @thejackshelton in #823)
@qwik-ui/[email protected]
Patch Changes
- ✨ accordion items can now open without a value using the
open
prop. use only in multiple mode. (by @thejackshelton in #803)
@qwik-ui/[email protected]
@qwik-ui/[email protected]
Minor Changes
-
tailwind.config.cjs (by @maiieul in #753)
tailwindcss-animate
The tailwind config now uses tailwindcss-animate
plugins: [ require('tailwindcss-animate'), ... ],
Instead of manually defined animations through a custom plugin like
plugins: [ plugin(function ({ addUtilities }) { addUtilities({ '.appear': { opacity: 1, }, '.disappear': { opacity: 0, }, }); }), ];
New keyframes
We added
animation: { 'accordion-up': 'collapsible-up 0.2s ease-out 0s 1 normal forwards', 'accordion-down': 'collapsible-down 0.2s ease-out 0s 1 normal forwards', }, keyframes: { 'collapsible-down': { from: { height: '0' }, to: { height: 'var(--qwikui-collapsible-content-height)' }, }, 'collapsible-up': { from: { height: 'var(--qwikui-collapsible-content-height)' }, to: { height: '0' }, }, },
to the tailwind config. You will need those for the styled accordion to be animated.
Modal refactor
Modal.Panel
The Panel now uses tailwindcss-animate and comes built-in with 5
position
variant propsexport const panelVariants = cva( [ 'fixed w-full bg-background p-6 text-foreground transition-all backdrop:brightness-50 backdrop:backdrop-blur-sm', 'data-[closing]:duration-300 data-[open]:duration-300 data-[open]:animate-in data-[closing]:animate-out', 'backdrop:data-[closing]:duration-300 backdrop:data-[open]:duration-300 backdrop:data-[open]:animate-in backdrop:data-[closing]:animate-out backdrop:data-[closing]:fade-out backdrop:data-[open]:fade-in', ], { variants: { position: { center: 'max-w-lg rounded-base shadow-lg data-[state=closed]:fade-out data-[state=open]:fade-in data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=open]:slide-in-from-bottom-2 backdrop:data-[closing]:fade-out backdrop:data-[open]:fade-in', top: 'inset-x-0 top-0 mt-0 rounded-b-base border-b data-[closing]:slide-out-to-top data-[open]:slide-in-from-top', bottom: 'inset-x-0 bottom-0 mb-0 rounded-t-base border-t data-[closing]:slide-out-to-bottom data-[open]:slide-in-from-bottom', left: 'inset-y-0 left-0 ml-0 h-full max-w-sm rounded-r-base border-r data-[closing]:slide-out-to-left data-[open]:slide-in-from-left', right: 'inset-y-0 right-0 mr-0 h-full max-w-sm rounded-l-base border-l data-[closing]:slide-out-to-right data-[open]:slide-in-from-right', }, }, defaultVariants: { position: 'center', }, }, ); type PanelProps = PropsOf<typeof HeadlessModal.Panel> & VariantProps<typeof panelVariants>; const Panel = component$<PanelProps>(({ position, ...props }) => { return ( <HeadlessModal.Panel {...props} class={cn(panelVariants({ position }), props.class)} > <Slot /> </HeadlessModal.Panel> ); });
over previous tailwind.config.js home-made plugin
'.appear': { opacity: 1, }, '.disappear': { opacity: 0, },
to avoid re-inventing the wheel.
Modal.Title
Title now holds
text-lg font-semibold
classes.Modal.Description
Description now holds
text-muted-foreground
class.Accordion
We changed the accordion animations. So make sure to update your taiwind config accordingly (explained at the beginning of the changeset!).
Patch Changes
@qwik-ui/[email protected]
Minor Changes
-
100% Lazy execution (by @thejackshelton in #737)
The entire Qwik UI library does not execute code until interaction. Your components are HTML, until the user decides to interact with them.
Bundling improvements
We have reduced the bundle size significantly of the headless library. If you are a Qwik library author, please refer to this issue as it may impact your bundle size as well.
Dot notation
The biggest change of v0.4 is the addition of dot notation to the API. Also known as "namespaced components".
This includes our largest breaking change to Qwik UI yet. We hope it is the largest ever, and want to ensure a much smoother transition going forward. Before we can do that, we need to make sure the API's are consistent across the library.
The component API's have been updated to use dot notation.
We believe that dot notation will significantly improve the developer experience. Below are some of the benefits of dot notation.
Simple Imports
In previous versions, imports needed to be defined for each component.
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@qwik-ui/headless';
While this is trivial with three components, it can be a pain with the more "pieces" a component has.
import { Combobox, ComboboxControl, ComboboxIcon, ComboboxInput, ComboboxLabel, ComboboxListbox, ComboboxOption, ComboboxPopover, ComboboxTrigger, ResolvedOption, } from '@qwik-ui/headless';
In v0.4, the new import syntax is the following:
import { Collapsible, Combobox } from '@qwik-ui/headless';
TypeScript Autocomplete
The searchability of available components has also been improved. You can now use the autocomplete feature to find a specific sub-component.
Improved legibility
For longer component names, the dot notation is arguably more legible. For example,
Combobox.Listbox
vs.ComboboxListbox
.Migration Cheat Sheet
As an easier way to migrate, we've created a mini cheat sheet below. If you have any questions, or need help migrating, don't hesistate to reach out to us on Discord.
Components named , like are now <Accordion.Root />
Except for and , which is now <Modal.Panel /> and <Popover.Panel /> respectively.
With new root components, the main props have been moved to the root component. (for example, props previously on the Popover and Modal panels).
Ex:
<Modal bind:show> -> <Modal.Root bind:show>
For further reference, read the RFC on dot notation.
Popover Refactor
Based on feedback we have received from the community, we have also improved the developer experience of the Popover component.
import { component$ } from '@builder.io/qwik'; import { Popover } from '@qwik-ui/headless'; export default component$(() => { return ( <Popover.Root gutter={4}> <Popover.Trigger class="popover-trigger">Click me</Popover.Trigger> <Popover.Panel class="popover-panel"> I am anchored to the popover trigger! </Popover.Panel> </Popover.Root> ); });
- By default, the popover is now anchored to its trigger. The API surface should also be simpler.
- A new
hover
prop has also been introduced on the root, useful for things like tooltips. - Programmatically toggling the popover is still possible, make sure to put the same id on the
<Popover.Root />
that is passed to theusePopover
hook. Refer to the docs for more info. - popover-showing and popover-closing classes have been deprecated. Please use the
data-open
and `data-closing
attributes instead. - The
data-open
,data-closing
, anddata-closed
data attributes have been added to the popover.
<Popover.Root />
There is a new root compomnent. Configurable props have been moved to the root component.
Deprecated Props
- You no longer need to style the popover open state with
:popover-open
. Instead, use thedata-open
attribute for it to style across browsers.
.popover-panel[data-open] { background: green; }
- You no longer need to pass a
popovertarget
prop to the<Popover.Trigger />
component. Same with an id prop on the<Popover.Panel />
component. - The
placement
prop has been deprecated, and combined with thefloating
prop.
For example,
floating="right
will now float the popover to the right.Opting out of the floating library
To opt-out of the floating library, set the
floating={false}
on the<Popover.Root />
component.May 2024, Chrome will be adding support for the CSS anchor API. This will allow us to remove the floating UI library entirely when that gains more support across browsers.
Docs Improvements
A couple of docs improvements have been made:
- The docs have been updated to reflect the new API.
- The headless docs no longer include styles in the examples. There is an example CSS section in each component page. If you do not find one, please open an issue on GitHub.
- Part of the Accordion and Modal docs have been simplified
- The examples now include icons from the
qwikest/icons
package rather than an abstract component you could not use.
-
Modal API Changes (by @thejackshelton in #734)
In a previous release, the following components have been deprecated:
- ModalHeader
- ModalContent
- ModalFooter
These components were native header, div, and footer elements and did nothing special under the hood. We are deprecating them in order to simplify the API and make it more consistent with the rest of the components.
The new components are:
<Modal.Root>
This is the main container of the modal, and now holds the major props and configuration. Examples include:
- 'bind:show'?: Signal;
- closeOnBackdropClick?: boolean;
- alert?: boolean;
- onShow$?: QRL<() => void>;
- onClose$?: QRL<() => void>;
<Modal.Panel>
Previously
<Modal />
the modal panel is the dialog element that is rendered on top of the page. Its props have since been moved to the<Modal.Root />
component, please refer to the docs for more information.<Modal.Trigger>
The modal now comes with a default trigger, which will open the modal when clicked.
<Modal.Title>
This computes the accessible name from the string children of the modal.
<Modal.Description>
This computes the accessible description from the string children of the modal.
<Modal.Close>
This is a button that closes the modal when clicked.
-
Select API Changes (by @thejackshelton in #724)
<SelectOption />
has been renamed to<Select.ItemLabel />
.<Select.Value />
has been renamed to<Select.DisplayValue />
.
<Select.Item />
A new component that allows for customize of the list item.
<Select.ItemIndicator />
This component is used to render an icon or other visual element that is displayed next to the
<Select.ItemLabel />
whenever an item is selected.Multi-select
To opt-in to the multi-select mode, set the
multiple
prop totrue
. Please refer to theMultiple Selections
section in the docs for more information.The previous API did not allow for customization of list items. The new API introduces a couple new components:
<Select.Item> <Select.ItemLabel>My Display Option!</Select.ItemLabel> <Select.ItemIndicator> {/* anything goes here */} </Select.ItemIndicator> <Select.Item>
You can now put anything you'd like in your
<Select.Item />
, just like a normal li tag!There is a new reactive signal called
bind:displayValue
that can be used to read the value of the display text. There is a new docs example that shows this in action with item pills.bind syntax
We have been exploring more with the
bind
syntax.bind:x
is a convention based onbind:value
andbind:checked
, where a signal is passed and two way data binding is enabled.This is much more performant than previous two way data binding, thanks to signals.
As a general note:
name -> initial value
anything that does not start with
bind:
is a static value.bind:name -> reactive signal
anything that starts with
bind:
is a reactive signal.If you find yourself curious to explore the bind syntax more, try typing
bind:
on a root component in Qwik UI, you should see a list of available things you can reactively customize! -
Tooltip (by @thejackshelton in #733)
The Tooltip component has been refactored from the ground up to be more accessible and performant.
It is now built on top of the popover primitive, and has a similar API.
It remains in
draft
status, and is not yet ready for production use. We will be working on it more deeply in the near future.Accordion
The Accordion has been refactored from the ground up to be more accessible and performant.
...
@qwik-ui/[email protected]
Patch Changes
-
🐞🩹 popover opening immediately in CSR (by @thejackshelton in #717)
-
🐞🩹 regression with popover polyfill executing unnecessary code (by @thejackshelton in #717)
@qwik-ui/[email protected]
Patch Changes
-
🐞🩹 race condition in the popover when programmatically opening on supported browsers (by @thejackshelton in #716)
-
✨ Adding the bind:open signal prop to the select component can now reactively control the select listbox open state (by @thejackshelton in #707)