-
Notifications
You must be signed in to change notification settings - Fork 137
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
df815cf
commit 7a5f627
Showing
7 changed files
with
354 additions
and
5 deletions.
There are no files selected for viewing
31 changes: 31 additions & 0 deletions
31
apps/website/src/routes/docs/headless/switch/examples/hero.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { component$, useStyles$ } from '@builder.io/qwik'; | ||
import { Select } from '@qwik-ui/headless'; | ||
|
||
export default component$(() => { | ||
useStyles$(styles); | ||
const users = ['Tim', 'Ryan', 'Jim', 'Jessie', 'Abby']; | ||
|
||
return ( | ||
<Select.Root class="select"> | ||
<Select.Label>Logged in users</Select.Label> | ||
<Select.Trigger class="select-trigger"> | ||
<Select.DisplayValue placeholder="Select an option" /> | ||
</Select.Trigger> | ||
<Select.Popover class="select-popover"> | ||
{users.map((user) => ( | ||
<Select.Item class="select-item" key={user}> | ||
<Select.ItemLabel>{user}</Select.ItemLabel> | ||
<Select.ItemIndicator> | ||
<LuCheck /> | ||
</Select.ItemIndicator> | ||
</Select.Item> | ||
))} | ||
</Select.Popover> | ||
</Select.Root> | ||
); | ||
}); | ||
|
||
// internal | ||
import styles from '../snippets/select.css?inline'; | ||
import { LuCheck } from '@qwikest/icons/lucide'; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,274 @@ | ||
--- | ||
title: Qwik UI | Tabs | ||
--- | ||
|
||
import { statusByComponent } from '~/_state/component-statuses'; | ||
|
||
<StatusBanner status={statusByComponent.headless.Tabs} /> | ||
|
||
# Tabs | ||
|
||
Explore and switch between different views using tabs | ||
|
||
<Showcase name="first" /> | ||
|
||
## Building blocks | ||
|
||
### The full version | ||
|
||
<CodeSnippet name="long" /> | ||
|
||
### The short version | ||
|
||
<CodeSnippet name="short" /> | ||
|
||
> The "short version" 👆 will be automatically transformed into the full ARIA compliant version | ||
## Vertical | ||
|
||
by default, the tabs are horizontal, but you can adjust the underlying behavior to be vertical by setting the `vertical` prop to true. | ||
|
||
<Showcase name="vertical" /> | ||
|
||
## Disabled | ||
|
||
You can disable a tab by setting the `disabled` prop to true. | ||
|
||
<Showcase name="disabled" /> | ||
|
||
## Behavior | ||
|
||
### Automatic | ||
|
||
The default behavior of the tabs is manual, which means that the tabs will automatically switch to the next tab when the user hovers over the tab. You can change this behavior by setting the `behavior` prop to `automatic`. | ||
|
||
<Showcase name="automatic" /> | ||
|
||
### Manual | ||
|
||
You can set the behavior to manual, which means that the tabs will not automatically switch to the next tab when the user presses the right arrow key, and to the previous tab when the user presses the left arrow key. | ||
|
||
<Showcase name="manual" /> | ||
|
||
## Dynamic | ||
|
||
<Showcase name="dynamic" /> | ||
|
||
## onSelectedIndexChange$ | ||
|
||
You can listen to changes in the selected index by subscribing to the `onSelectedIndexChange$` store. | ||
|
||
<Showcase name="on-selected-index-change" /> | ||
|
||
## bind:selectedIndex | ||
|
||
You can provide a signal for the selected index with the `bind:selectedIndex={yourSignal}` and it will be used directly. This is a more efficient version of `onSelectedIndexChange$`. | ||
|
||
## bind:selectedTabId | ||
|
||
You can provide a signal for the selected tab id with the `bind:selectedTabId={yourSignal}` | ||
and it will be used directly. | ||
|
||
💡 You can manually set the `tabId` prop on each tab to be able to select any tab by its ID. | ||
|
||
<Showcase name="selected-tab-id" /> | ||
|
||
## Add a "selected" prop | ||
|
||
You can add a "selected" prop to the Tab component to make it selected by default. | ||
|
||
<Showcase name="selected-prop" /> | ||
|
||
## onClick$ | ||
|
||
You can pass a custom `onClick$` handler. | ||
|
||
<Showcase name="on-click" /> | ||
|
||
## Creating reusable Tabs | ||
|
||
In order to remove the need for a linking `value` prop for each Tab and Tabs.Panel pair, we have chosen to create the Tabs component as an [inline component](https://qwik.builder.io/docs/components/overview/#inline-components). | ||
|
||
By consequence, the Tabs component needs to be aware of its children. If you want to create your custom reusable Tabs.List/Tab/Tabs.Panel components, you will have to pass them to the Tabs component through its `Tabs.List`, `Tab`, and `Tabs.Panel` props: | ||
|
||
```tsx | ||
const CustomTabs = (props: PropsOf<typeof Tabs.Root>) => ( | ||
<Tabs.Root | ||
{...props} | ||
tabListComponent={CustomTabsList} | ||
tabComponent={CustomTab} | ||
tabPanelComponent={CustomTabsPanel} | ||
/> | ||
); | ||
``` | ||
|
||
<br /> | ||
|
||
<Note status="warning"> | ||
If you don't do the above, the Tabs component cannot know if your CustomTab component is | ||
a Tab component. | ||
</Note> | ||
|
||
<Showcase name="reusable" /> | ||
|
||
## Accessibility | ||
|
||
### Keyboard interaction | ||
|
||
<KeyboardInteractionTable | ||
keyDescriptors={[ | ||
{ | ||
keyTitle: 'Tab', | ||
description: 'Moves focus to the selected panel.', | ||
}, | ||
{ | ||
keyTitle: 'Shift + Tab', | ||
description: 'Moves focus to the selected tab.', | ||
}, | ||
{ | ||
keyTitle: 'ArrowRight', | ||
description: 'Moves focus to the next tab.', | ||
}, | ||
{ | ||
keyTitle: 'ArrowLeft', | ||
description: 'Moves focus to the previous tab.', | ||
}, | ||
{ | ||
keyTitle: 'ArrowDown', | ||
description: 'In "vertical mode", moves focus to the next tab.', | ||
}, | ||
{ | ||
keyTitle: 'ArrowUp', | ||
description: 'In "vertical mode", moves focus to the previous tab.', | ||
}, | ||
{ | ||
keyTitle: 'Home', | ||
description: 'Moves focus to the first tab.', | ||
}, | ||
{ | ||
keyTitle: 'PageUp', | ||
description: 'Moves focus to the first tab.', | ||
}, | ||
{ | ||
keyTitle: 'End', | ||
description: 'Moves focus to the last tab.', | ||
}, | ||
{ | ||
keyTitle: 'PageDown', | ||
description: 'Moves focus to the first tab.', | ||
}, | ||
]} | ||
/> | ||
|
||
## API | ||
|
||
### Tabs | ||
|
||
<APITable | ||
propDescriptors={[ | ||
{ | ||
name: 'behavior', | ||
type: 'string', | ||
description: | ||
'Toggle between automatic or manual. The automatic behavior moves between tabs when hover. The manual behavior moves between tabs on click.', | ||
}, | ||
{ | ||
name: 'selectedIndex', | ||
type: 'number', | ||
description: 'A way to set the selected index programmatically', | ||
}, | ||
{ | ||
name: 'selectedTabId', | ||
type: 'number', | ||
description: | ||
'A way to set the selected tabId programmatically. The tabId is set by the `key` prop of the Tab or Tabs.Panel', | ||
}, | ||
{ | ||
name: 'vertical', | ||
type: 'boolean', | ||
description: | ||
'If set to true, the behavior of UpArrow and DownArrow will navigate between tabs vertically instead of horizontally.', | ||
}, | ||
{ | ||
name: 'onSelectedIndexChange$', | ||
type: 'function', | ||
info: '(index: number) => void', | ||
description: 'An event hook that gets notified whenever the selected index changes', | ||
}, | ||
{ | ||
name: 'onSelectedTabIdChange$', | ||
type: 'function', | ||
info: '(index: string) => void', | ||
description: 'An event hook that gets notified whenever the selected tabId changes', | ||
}, | ||
{ | ||
name: 'bind:selectedIndex', | ||
type: 'signal', | ||
info: 'Signal<number | undefined>', | ||
description: | ||
'A signal that binds the selected index. This is a more efficient version of `selectedIndex` + `onSelectedIndexChange$`', | ||
}, | ||
{ | ||
name: 'bind:selectedTabId', | ||
type: 'signal', | ||
info: 'Signal<string | undefined>', | ||
description: | ||
'A signal that binds the selected tabId. This is a more efficient version of `selectedTabId` + `onSelectedTabIdChange$`', | ||
}, | ||
{ | ||
name: 'tabClass', | ||
type: 'string', | ||
description: 'The class name to apply to tabs', | ||
}, | ||
{ | ||
name: 'panelClass', | ||
type: 'string', | ||
description: 'The class name to apply to panels', | ||
}, | ||
]} | ||
/> | ||
|
||
### Tab | ||
|
||
<APITable | ||
propDescriptors={[ | ||
{ | ||
name: 'selectedClassName', | ||
type: 'string', | ||
description: 'The class name to apply when the tab is selected', | ||
}, | ||
{ | ||
name: 'selected', | ||
type: 'boolean', | ||
description: 'Select this tab by default', | ||
}, | ||
{ | ||
name: 'disabled', | ||
type: 'boolean', | ||
description: 'Set the disabled state of a specific tab', | ||
}, | ||
{ | ||
name: 'tabId', | ||
type: 'string', | ||
description: | ||
'Set the tab id, can be used with `bind:selectedTabId` to select a tab programmatically', | ||
}, | ||
]} | ||
/> | ||
|
||
### Tabs.Panel | ||
|
||
<APITable | ||
propDescriptors={[ | ||
{ | ||
name: 'label', | ||
type: 'element', | ||
description: 'Shorthand for `<Tab>{label}</Tab>`', | ||
}, | ||
{ | ||
name: 'selected', | ||
type: 'boolean', | ||
description: 'Select this tab by default', | ||
}, | ||
]} | ||
/> |
Empty file.
12 changes: 12 additions & 0 deletions
12
packages/kit-headless/src/components/switch/switch-context.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { createContextId, type Signal } from '@builder.io/qwik'; | ||
|
||
export interface SwitchState { | ||
'bind:checked': Signal<boolean>; | ||
defaultChecked: boolean; | ||
disabled: boolean; | ||
label: string; | ||
} | ||
// | ||
export type SwitchContextState = Omit<SwitchState, 'bind:checked'> & { bindChecked: Signal<boolean> } | ||
|
||
export const SwitchContext = createContextId<SwitchContextState>('SwitchContext'); |
27 changes: 27 additions & 0 deletions
27
packages/kit-headless/src/components/switch/switch-root.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { | ||
component$, | ||
Slot, | ||
useContextProvider, | ||
type PropsOf | ||
} from '@builder.io/qwik'; | ||
import { type SwitchContextState, type SwitchState, SwitchContext } from './switch-context'; | ||
export type SwitchProps = PropsOf<'div'> & SwitchState; | ||
|
||
|
||
|
||
export const SwitchRoot = component$(({defaultChecked, disabled, label, ...rest}: SwitchProps) => { | ||
const context: SwitchContextState = { | ||
defaultChecked, | ||
disabled, | ||
label, | ||
bindChecked: rest['bind:checked'] | ||
} | ||
|
||
useContextProvider(SwitchContext, context) | ||
|
||
return ( | ||
<div {...rest}> | ||
<Slot></Slot> | ||
</div> | ||
); | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters