Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add a new switch component #960

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
dbe9fc4
feat: add a new switch component
JerryWu1234 Sep 13, 2024
40c8fba
Merge branch 'main' into add_component_switch
JerryWu1234 Sep 19, 2024
a219d8f
init debug
JerryWu1234 Sep 19, 2024
df815cf
Merge branch 'main' into add_component_switch
JerryWu1234 Sep 23, 2024
7a5f627
switch is developing
JerryWu1234 Sep 24, 2024
5850f72
headless Switch
JerryWu1234 Sep 24, 2024
61e5eb4
add css style
JerryWu1234 Sep 25, 2024
5d18929
Merge branch 'main' into add_component_switch
JerryWu1234 Oct 4, 2024
e167210
Merge branch 'main' into add_component_switch
JerryWu1234 Oct 7, 2024
fa4d78d
Merge branch 'main' into add_component_switch
JerryWu1234 Oct 15, 2024
75b226e
switch function
JerryWu1234 Oct 15, 2024
6da7f8f
Merge branch 'add_component_switch' of https://github.com/JerryWu1234…
JerryWu1234 Oct 15, 2024
6c112bb
add more properties
JerryWu1234 Oct 17, 2024
80a69b0
Merge branch 'main' into add_component_switch
JerryWu1234 Oct 24, 2024
924a6a7
fix bug
JerryWu1234 Oct 24, 2024
d30ee27
fix conflict
JerryWu1234 Oct 31, 2024
4aeca25
add test
JerryWu1234 Nov 4, 2024
9cc504c
add switch component in headless
JerryWu1234 Nov 6, 2024
db01981
Merge branch 'main' into add_component_switch
JerryWu1234 Nov 14, 2024
2839aa6
fix switch
JerryWu1234 Nov 14, 2024
c62ddda
fix switch
JerryWu1234 Nov 15, 2024
521c3b0
developing styled
JerryWu1234 Nov 15, 2024
2f54039
developing styled
JerryWu1234 Nov 15, 2024
a132016
css
JerryWu1234 Nov 18, 2024
b51919a
css
JerryWu1234 Nov 18, 2024
e78c0c4
add css
JerryWu1234 Nov 18, 2024
1d7562a
Merge branch 'main' into add_component_switch
JerryWu1234 Nov 20, 2024
8aae55d
test
JerryWu1234 Nov 21, 2024
32cf8bc
Merge branch 'add_component_switch' of https://github.com/JerryWu1234…
JerryWu1234 Nov 21, 2024
eb3f351
test
JerryWu1234 Nov 21, 2024
2324120
Merge branch 'main' into add_component_switch
JerryWu1234 Nov 26, 2024
482d258
fix css
JerryWu1234 Nov 27, 2024
89bddb5
Merge branch 'main' into add_component_switch
JerryWu1234 Nov 29, 2024
4ae9b4e
fix
JerryWu1234 Nov 29, 2024
28bbe12
Merge branch 'add_component_switch' of https://github.com/JerryWu1234…
JerryWu1234 Nov 29, 2024
d58521e
fix
JerryWu1234 Dec 3, 2024
d2eb94a
fix
JerryWu1234 Dec 3, 2024
db37e73
switch
JerryWu1234 Dec 3, 2024
b176836
switch
JerryWu1234 Dec 3, 2024
a10ddbb
Merge branch 'main' into add_component_switch
JerryWu1234 Dec 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/poor-spiders-clap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@qwik-ui/headless': major
'@qwik-ui/styled': major
---

add a new switch component
4 changes: 4 additions & 0 deletions apps/component-tests/src/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,13 @@
--alert: 0 84.2% 60.2%;
--alert-foreground: 210 40% 98%;
--ring: 222.2 47.4% 11.2%;
--switch-thumb-color-highlight: 0, 0%, 72%, 0.25;
--switch-track-color-inactive: 80 0% 80%;
}

.dark {
--switch-thumb-color-highlight: 0, 0%, 100%, 0.25;
--switch-track-color-inactive: 240, 10%, 50%;
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { component$ } from '@builder.io/qwik';
import { ShowcaseTest } from '../../../../components/showcase-test/showcase-test';

export default component$(() => {
// Need to center the content in the screen
// so that tests like popover placement can
Expand Down
18 changes: 16 additions & 2 deletions apps/component-tests/tailwind.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,26 @@ module.exports = {
plugins: [
// PLUGIN-START
require('tailwindcss-animate'),
plugin(function ({ addUtilities }) {
plugin(function ({ addUtilities, theme, e }) {
addUtilities({
'.press': {
transform: 'var(--transform-press)',
},
});
const sizelist = theme('spacing');
const blockSizeUtilities = Object.keys(sizelist).reduce((acc, key) => {
const value = sizelist[key];
acc[`.${e(`block-size-${key}`)}`] = {
'block-size': value,
};
acc[`.${e(`inline-size-${key}`)}`] = {
'inline-size': value,
};
return acc;
}, {});

addUtilities(blockSizeUtilities, ['responsive', 'hover']);
}),
// PLUGIN-END
],
darkMode: 'class',
theme: {
Expand All @@ -35,6 +47,8 @@ module.exports = {
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
switchInactive: 'hsl(var(--switch-track-color-inactive))',
switchThumb: 'hsl(var(--switch-thumb-color-highlight))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
Expand Down
6 changes: 5 additions & 1 deletion apps/website/src/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,13 @@
--alert: 0 84.2% 60.2%;
--alert-foreground: 210 40% 98%;
--ring: 222.2 47.4% 11.2%;
--switch-thumb-color-highlight: 0, 0%, 72%, 0.25;
--switch-track-color-inactive: 80 0% 80%;
}

.dark {
--switch-thumb-color-highlight: 0, 0%, 100%, 0.25;
--switch-track-color-inactive: 240, 10%, 50%;
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
Expand Down Expand Up @@ -1363,7 +1367,7 @@ body {
min-height: 100%;
}

/* Utilities layer for animations. The current arbitrary & docs tailwind animation guidelines are not maintainable long term.
/* Utilities layer for animations. The current arbitrary & docs tailwind animation guidelines are not maintainable long term.
It would make more sense to supply the user with the animation declaration in the docs.
*/
@layer utilities {
Expand Down
1 change: 1 addition & 0 deletions apps/website/src/routes/docs/headless/menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@
- [Tooltip](/docs/headless/tooltip)
- [Toggle](/docs/headless/toggle)
- [Toggle Group](/docs/headless/toggle-group)
- [Switch](/docs/headless/switch)
15 changes: 15 additions & 0 deletions apps/website/src/routes/docs/headless/switch/examples/checked.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { component$, useStyles$, useSignal } from '@builder.io/qwik';
import { Switch } from '@qwik-ui/headless';

export default component$(() => {
const checked = useSignal(true);
useStyles$(styles);
return (
<Switch.Root class="switch" bind:checked={checked}>
<Switch.Label>test</Switch.Label>
<Switch.Input />
</Switch.Root>
);
});

import styles from '../snippets/switch.css?inline';
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { component$, useSignal } from '@builder.io/qwik';
import { Switch } from '@qwik-ui/headless';

export default component$(() => {
const checked = useSignal(false);
return (
<Switch.Root class="switch" defaultChecked bind:checked={checked}>
<Switch.Label>test</Switch.Label>
<Switch.Input />
</Switch.Root>
);
});
15 changes: 15 additions & 0 deletions apps/website/src/routes/docs/headless/switch/examples/disabled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { component$, useSignal, useStyles$ } from '@builder.io/qwik';
import { Switch } from '@qwik-ui/headless';

export default component$(() => {
const checked = useSignal(false);
useStyles$(styles);
return (
<Switch.Root class="switch" disabled bind:checked={checked}>
<Switch.Label>test</Switch.Label>
<Switch.Input />
</Switch.Root>
);
});

import styles from '../snippets/switch.css?inline';
17 changes: 17 additions & 0 deletions apps/website/src/routes/docs/headless/switch/examples/hero.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { component$, useSignal, useStyles$ } from '@builder.io/qwik';
import { Switch } from '@qwik-ui/headless';

export default component$(() => {
const checked = useSignal(false);
const count = useSignal(0);
useStyles$(styles);

return (
<Switch.Root class="switch" bind:checked={checked} onChange$={() => count.value++}>
<Switch.Label>test{count.value}</Switch.Label>
<Switch.Input />
</Switch.Root>
);
});

import styles from '../snippets/switch.css?inline';
143 changes: 143 additions & 0 deletions apps/website/src/routes/docs/headless/switch/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
---
title: Qwik UI | Switch
---

import { FeatureList } from '~/components/feature-list/feature-list';

import { statusByComponent } from '~/_state/component-statuses';

<StatusBanner status={statusByComponent.headless.Switch} />

# Switch

A toggleable control for user interactions.

<Showcase name="hero" />

## ✨ Features

<FeatureList
features={[
'WAI ARIA Switch design pattern',
'Single toggle state (on/off)',
'Reactive state changes',
'Disabled state',
'Keyboard accessibility (Space and Enter keys)',
'Custom styling options',
'Support for labels and descriptions',
'Focus management',
'Accessibility support for screen readers',
]}
roadmap={['Opt-in native form support', 'RTL support']}
/>


## Building blocks

<CodeSnippet name="building-blocks" />

## Anatomy

<AnatomyTable
propDescriptors={[
{
name: 'Switch.Root',
description:
'Defines the component boundary and exposes its internal logic. Must wrap over all other parts.',
},
{
name: 'Switch.Input',
description:
'The actual switch element that users interact with. Can be toggled on or off.',
},
{
name: 'Switch.Label',
description:
'Provides a label for the switch, enhancing accessibility and usability.',
},
]}
/>

## Why use a headless Switch?

The native `<input type="checkbox">` element presents several challenges regarding styling, behavior, and user experience.

### Native Switch pain points

<FeatureList
issues={[
'Limited styling options',
'Inconsistent appearance across browsers',
'Lack of custom animations and transitions',
'Accessibility challenges with native elements',
]}
/>

### Native effort

While there are efforts to enhance the native checkbox element, such as the [Open UI group](https://open-ui.org/components/switch/), these solutions often fall short in terms of flexibility and customization. A headless Switch component allows developers to create a fully tailored user experience without the constraints of native elements.

## Behavior Tests

### Mouse Interaction

<Showcase name="hero" />

- **Toggle State**: Ensures that clicking the switch toggles its checked state correctly.
- **Trigger onChange**: Verifies that the onChange callback is triggered when the switch is clicked.

### Keyboard Interaction

<Showcase name="hero" />

- **Enter Key**: Tests that pressing the Enter key toggles the switch's state.
- **Space Key**: Checks that pressing the Space key toggles the switch's state.

### Default Properties

<Showcase name="checked" />

- **Checked by Default**: Confirms that the switch is checked upon initial render if set so.
- **Disabled State**: Ensures that the switch is disabled and does not respond to user interactions when set so.

## API

### Switch.Root

<AnatomyTable
propDescriptors={[
{
name: 'bind:checked',
description:
'Two-way data bind of the checked state of the switch to a user-defined signal.',
},
{
name: 'onChange$',
description:
'Callback function that is triggered when the checked state of the switch changes.',
},
{
name: 'disabled',
description:
'Disables the switch, preventing any user interaction.',
},
{
name: 'onClick$',
description:
'Callback function that is triggered when the switch is clicked.',
},
{
name: 'defaultChecked',
description:
'Sets the switch to the checked state by default.',
},
{
name: 'autoFocus',
description:
'Sets the switch to auto focus.',
},
]}
/>



Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { component$, useSignal, useStyles$ } from '@builder.io/qwik';
import { Switch } from '@qwik-ui/headless';

export default component$(() => {
const checked = useSignal(false);
useStyles$(styles);

return (
<Switch.Root bind:checked={checked}>
<Switch.Label>test</Switch.Label>
<Switch.Input />
</Switch.Root>
);
});

import styles from '../snippets/switch.css?inline';
67 changes: 67 additions & 0 deletions apps/website/src/routes/docs/headless/switch/snippets/switch.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/* Define default light theme colors */
.switch {
flex-direction: row-reverse;
display: flex;
align-items: center;
gap: 1ch;
justify-content: space-between;
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
& > input {
--thumb-position: 0%;
--thumb-transition-duration: 0.25s;
padding: 4px;
background: hsla(var(--switch-track-color-inactive));
inline-size: 4rem;
block-size: 2rem;
border-radius: 4rem;
appearance: none;
pointer-events: none;
touch-action: pan-y;
border: none;
outline-offset: 5px;
box-sizing: content-box;
flex-shrink: 0;
display: grid;
align-items: center;
grid: [track] 1fr / [track] 1fr;
transition: background-color 0.25s ease;

&::before {
content: '';
cursor: pointer;
pointer-events: auto;
inline-size: 2rem;
block-size: 2rem;
background: hsla(var(--background));
box-shadow: 0 0 0 0 hsla(var(--switch-thumb-color-highlight));
border-radius: 50%;
transform: translateX(0%);
}

&:not(:disabled):hover::before {
box-shadow: 0 0 0 0.5rem hsla(var(--switch-thumb-color-highlight));
}

&:checked {
background: hsla(var(--primary));
&::before {
transform: translateX(100%);
}
}
&:disabled {
cursor: not-allowed;
opacity: 0.35;
&::before {
cursor: not-allowed;
box-shadow: inset 0 0 0 2px hsl(0 0% 100% / 10%);
}
}

&:focus {
outline: 2px solid hsl(var(--primary));
outline-offset: 2px;
}
}
}
1 change: 1 addition & 0 deletions apps/website/src/routes/docs/styled/menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@
- [Textarea](/docs/styled/textarea)
- [Toggle](/docs/styled/toggle)
- [ToggleGroup](/docs/styled/toggle-group)
- [Switch](/docs/styled/switch)
Loading
Loading