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

component: Switch #51

Merged
merged 1 commit into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions src/lib/assets/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import Information from './information.svg?component';
import Key from './key.svg?component';
import Link from './link.svg?component';
import LoaderCircle from './loader-circle.svg?component';
import LockClosedSmall from './lock-closed-small.svg?component';
import LockOpenSmall from './lock-open-small.svg?component';
import LogoBitbucketColor from './logo-bitbucket-color.svg?component';
import LogoGeist from './logo-geist.svg?component';
import LogoGithub from './logo-github.svg?component';
Expand Down Expand Up @@ -106,6 +108,8 @@ export const Icons = {
Key,
Link,
LoaderCircle,
LockClosedSmall,
LockOpenSmall,
LogoBitbucketColor,
LogoGeist,
LogoGithub,
Expand Down
3 changes: 3 additions & 0 deletions src/lib/assets/icons/lock-closed-small.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/lib/assets/icons/lock-open-small.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions src/lib/components/ui/switch/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { Icons } from '$lib/assets/icons';
import { Switch as SwitchPrimitive } from 'bits-ui';
import { tv, type VariantProps } from 'tailwind-variants';
import Root from './switch.svelte';

export const switch_variants = tv({
base: 'group inline-flex shrink-0 cursor-pointer items-center rounded-full border border-gray-alpha-400 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-color focus-visible:ring-offset-2 focus-visible:ring-offset-background-200 disabled:cursor-not-allowed data-[state=unchecked]:bg-gray-100 disabled:data-[state=unchecked]:bg-background-100',
variants: {
variant: {
default:
'data-[state=checked]:bg-blue-700 disabled:data-[state=checked]:border-accents-2 disabled:data-[state=checked]:bg-accents-1',
subtle: 'data-[state=checked]:bg-gray-100 disabled:data-[state=checked]:bg-background-100'
},
size: {
md: 'h-[14px] w-[28px]',
lg: 'h-[24px] w-[40px]'
}
},
defaultVariants: {
variant: 'default',
size: 'md'
}
});

type Variant = VariantProps<typeof switch_variants>['variant'];
type Size = VariantProps<typeof switch_variants>['size'];

export type Props = SwitchPrimitive.Props & {
variant?: Variant;
size?: Size;
icon?: {
checked: typeof Icons.Accessibility;
unchecked: typeof Icons.Accessibility;
};
direction?: 'switch-first' | 'switch-last';
};

export { Root, Root as Switch };
75 changes: 75 additions & 0 deletions src/lib/components/ui/switch/switch.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<script lang="ts">
import { cn } from '$lib/utils.js';
import { Switch as SwitchPrimitive } from 'bits-ui';
import { switch_variants, type Props } from '.';

type $$Props = Props;
type $$Events = SwitchPrimitive.Events;

let class_name: $$Props['class'] = undefined;
export let checked: $$Props['checked'] = undefined;
export let variant: $$Props['variant'] = 'default';
export let size: $$Props['size'] = 'md';
export let icon: $$Props['icon'] = undefined;
export let direction: $$Props['direction'] = 'switch-last';
export { class_name as class };
</script>

<label class="flex items-center gap-2">
{#if direction === 'switch-last'}
<span class="whitespace-nowrap text-xs font-medium text-accents-5">
<slot></slot>
</span>
{/if}
<SwitchPrimitive.Root
bind:checked
class={cn(switch_variants({ variant, size, className: class_name }), class_name)}
{...$$restProps}
on:click
on:keydown
>
{#if icon}
<SwitchPrimitive.Thumb asChild let:attrs>
<span
class={cn(
'pointer-events-none grid place-items-center rounded-full bg-gray-700 text-background-100 ring-0 transition-transform [box-shadow:_0_0_4px_rgba(0,0,0,.12),0_1px_1px_rgba(0,0,0,.16)] data-[state=unchecked]:translate-x-0 data-[state=checked]:bg-gray-1000 group-data-[disabled=true]:bg-gray-200 group-data-[disabled=true]:text-gray-700',
{
'size-3 data-[state=checked]:translate-x-[13.5px]': size === 'md',
'size-[22px] data-[state=checked]:translate-x-[15.5px]': size === 'lg'
}
)}
{...attrs}
>
{#if checked}
<svelte:component
this={icon.checked}
aria-hidden="true"
class="size-full max-h-4 max-w-4"
/>
{:else}
<svelte:component
this={icon.unchecked}
aria-hidden="true"
class="size-full max-h-4 max-w-4"
/>
{/if}
</span>
</SwitchPrimitive.Thumb>
{:else}
<SwitchPrimitive.Thumb
class={cn(
'pointer-events-none block rounded-full bg-gray-700 ring-0 transition-transform [box-shadow:_0_0_4px_rgba(0,0,0,.12),0_1px_1px_rgba(0,0,0,.16)] data-[state=unchecked]:translate-x-0 data-[state=checked]:bg-gray-1000 group-data-[disabled=true]:bg-gray-200',
{
'size-3 data-[state=checked]:translate-x-[13.5px]': size === 'md',
'size-[22px] data-[state=checked]:translate-x-[15.5px]': size === 'lg'
}
)}
/>
{/if}
</SwitchPrimitive.Root>
{#if direction === 'switch-first'}
<span class="whitespace-nowrap text-xs font-medium text-accents-5">
<slot></slot>
</span>
{/if}
</label>
2 changes: 1 addition & 1 deletion src/lib/config/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ export const aside_items: Aside = {
{
title: 'Switch',
href: '/switch',
status: 'soon'
status: 'new'
},
{
title: 'Table',
Expand Down
39 changes: 38 additions & 1 deletion src/routes/switch/+page.svelte
Original file line number Diff line number Diff line change
@@ -1 +1,38 @@
<h1>switch</h1>
<script lang="ts">
import Demo from '$lib/components/shared/demo.svelte';
import PageWrapper from '$lib/components/shared/page-wrapper.svelte';
import Default from './default.svelte';
import default_code from './default.svelte?raw';
import Disabled from './disabled.svelte';
import disabled_code from './disabled.svelte?raw';
import Sizes from './sizes.svelte';
import sizes_code from './sizes.svelte?raw';
import SubtleWithIcon from './subtle-with-icon.svelte';
import subtle_with_icon_code from './subtle-with-icon.svelte?raw';
import WithLabel from './with-label.svelte';
import with_label from './with-label.svelte?raw';

export let data;
</script>

<PageWrapper title={data.title} description={data.description}>
<Demo id="default" code={default_code}>
<Default />
</Demo>

<Demo id="disabled" code={disabled_code}>
<Disabled />
</Demo>

<Demo id="sizes" code={sizes_code}>
<Sizes />
</Demo>

<Demo id="subtle-with-icon" code={subtle_with_icon_code}>
<SubtleWithIcon />
</Demo>

<Demo id="with-label" code={with_label}>
<WithLabel />
</Demo>
</PageWrapper>
21 changes: 21 additions & 0 deletions src/routes/switch/+page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { MetaTagsProps } from 'svelte-meta-tags';

export function load() {
const title = 'Switch';
const description = 'Displays a boolean value.';

const pageMetaTags = Object.freeze({
title,
description,
openGraph: {
title,
description
}
}) satisfies MetaTagsProps;

return {
pageMetaTags,
title,
description
};
}
8 changes: 8 additions & 0 deletions src/routes/switch/default.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script lang="ts">
import { Switch } from '$lib/components/ui/switch';
</script>

<div class="grid gap-6">
<Switch aria-label="Enable Firewall" />
<Switch aria-label="Enable Firewall" checked />
</div>
8 changes: 8 additions & 0 deletions src/routes/switch/disabled.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script lang="ts">
import { Switch } from '$lib/components/ui/switch';
</script>

<div class="grid gap-6">
<Switch aria-label="Enable Firewall" disabled />
<Switch aria-label="Enable Firewall" checked disabled />
</div>
10 changes: 10 additions & 0 deletions src/routes/switch/sizes.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script lang="ts">
import { Switch } from '$lib/components/ui/switch';

let checked = false;
</script>

<div class="grid grid-cols-2">
<Switch aria-label="Enable Firewall" bind:checked />
<Switch aria-label="Enable Firewall" bind:checked size="lg" />
</div>
38 changes: 38 additions & 0 deletions src/routes/switch/subtle-with-icon.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script lang="ts">
import { Icons } from '$lib/assets/icons';
import { Switch } from '$lib/components/ui/switch';

let checked = false;
</script>

<div class="grid gap-6">
<Switch
bind:checked
variant="subtle"
aria-label="Enable Firewall"
icon={{ checked: Icons.LockClosedSmall, unchecked: Icons.LockOpenSmall }}
/>
<Switch
bind:checked
disabled
variant="subtle"
aria-label="Enable Firewall"
icon={{ checked: Icons.LockClosedSmall, unchecked: Icons.LockOpenSmall }}
/>

<Switch
bind:checked
variant="subtle"
aria-label="Enable Firewall"
size="lg"
icon={{ checked: Icons.LockClosedSmall, unchecked: Icons.LockOpenSmall }}
/>
<Switch
bind:checked
disabled
variant="subtle"
aria-label="Enable Firewall"
size="lg"
icon={{ checked: Icons.LockClosedSmall, unchecked: Icons.LockOpenSmall }}
/>
</div>
78 changes: 78 additions & 0 deletions src/routes/switch/with-label.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<script lang="ts">
import { Icons } from '$lib/assets/icons';
import { Switch } from '$lib/components/ui/switch';

let checked = false;
const Closed = Icons.LockClosedSmall;
const Open = Icons.LockOpenSmall;
</script>

<div class="grid w-fit grid-cols-2 gap-6">
<Switch bind:checked>Enable Firewall</Switch>
<Switch bind:checked direction="switch-first">Enable Firewall</Switch>

<Switch bind:checked size="lg">Enable Firewall</Switch>
<Switch bind:checked direction="switch-first" size="lg">Enable Firewall</Switch>

<Switch bind:checked icon={{ checked: Closed, unchecked: Open }}>Enable Firewall</Switch>
<Switch bind:checked direction="switch-first" icon={{ checked: Closed, unchecked: Open }}>
Enable Firewall
</Switch>

<Switch bind:checked size="lg" icon={{ checked: Closed, unchecked: Open }}>
Enable Firewall
</Switch>
<Switch
bind:checked
direction="switch-first"
size="lg"
icon={{ checked: Closed, unchecked: Open }}
>
Enable Firewall
</Switch>

<Switch bind:checked variant="subtle" icon={{ checked: Closed, unchecked: Open }}>
Enable Firewall
</Switch>
<Switch
bind:checked
direction="switch-first"
variant="subtle"
icon={{ checked: Closed, unchecked: Open }}
>
Enable Firewall
</Switch>

<Switch bind:checked size="lg" variant="subtle" icon={{ checked: Closed, unchecked: Open }}>
Enable Firewall
</Switch>
<Switch
bind:checked
direction="switch-first"
size="lg"
variant="subtle"
icon={{ checked: Closed, unchecked: Open }}
>
Enable Firewall
</Switch>

<Switch
bind:checked
size="lg"
variant="subtle"
disabled
icon={{ checked: Closed, unchecked: Open }}
>
Enable Firewall
</Switch>
<Switch
bind:checked
direction="switch-first"
size="lg"
variant="subtle"
disabled
icon={{ checked: Closed, unchecked: Open }}
>
Enable Firewall
</Switch>
</div>