From 7319c680c10ed6d0c6b9bff19bd85f43b41ebf66 Mon Sep 17 00:00:00 2001 From: SHYAKA Davis <87414827+shyakadavis@users.noreply.github.com> Date: Wed, 17 Jul 2024 21:53:55 +0200 Subject: [PATCH] component: Checkbox (#9) Co-authored-by: Davis SHYAKA <87414827+davis-shyaka@users.noreply.github.com> --- src/lib/assets/icons/index.ts | 2 + src/lib/assets/icons/minus.svg | 3 ++ .../components/ui/checkbox/checkbox.svelte | 48 +++++++++++++++++++ src/lib/components/ui/checkbox/index.ts | 6 +++ src/lib/components/ui/label/index.ts | 7 +++ src/lib/components/ui/label/label.svelte | 21 ++++++++ src/lib/config/sitemap.ts | 2 +- src/routes/checkbox/+page.svelte | 27 ++++++++++- src/routes/checkbox/+page.ts | 21 ++++++++ src/routes/checkbox/default.svelte | 7 +++ src/routes/checkbox/disabled.svelte | 9 ++++ src/routes/checkbox/indeterminate.svelte | 5 ++ 12 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 src/lib/assets/icons/minus.svg create mode 100644 src/lib/components/ui/checkbox/checkbox.svelte create mode 100644 src/lib/components/ui/checkbox/index.ts create mode 100644 src/lib/components/ui/label/index.ts create mode 100644 src/lib/components/ui/label/label.svelte create mode 100644 src/routes/checkbox/+page.ts create mode 100644 src/routes/checkbox/default.svelte create mode 100644 src/routes/checkbox/disabled.svelte create mode 100644 src/routes/checkbox/indeterminate.svelte diff --git a/src/lib/assets/icons/index.ts b/src/lib/assets/icons/index.ts index 13c209d..9600c25 100644 --- a/src/lib/assets/icons/index.ts +++ b/src/lib/assets/icons/index.ts @@ -34,6 +34,7 @@ import LogoNext from './logo-next.svg?component'; import LogoTurborepo from './logo-turborepo.svg?component'; import LogoV0 from './logo-v0.svg?component'; import LogoVercel from './logo-vercel.svg?component'; +import Minus from './minus.svg?component'; import Notification from './notification.svg?component'; import Paperclip from './paperclip.svg?component'; import PencilEdit from './pencil-edit.svg?component'; @@ -82,6 +83,7 @@ export const Icons = { LogoTurborepo, LogoV0, LogoVercel, + Minus, Notification, Paperclip, PencilEdit, diff --git a/src/lib/assets/icons/minus.svg b/src/lib/assets/icons/minus.svg new file mode 100644 index 0000000..924aaff --- /dev/null +++ b/src/lib/assets/icons/minus.svg @@ -0,0 +1,3 @@ +<svg stroke-linejoin="round" color="currentColor" viewBox="0 0 16 16"> + <path fill-rule="evenodd" clip-rule="evenodd" d="M2 7.25H2.75H13.25H14V8.75H13.25H2.75H2V7.25Z" fill="currentColor"></path> +</svg> \ No newline at end of file diff --git a/src/lib/components/ui/checkbox/checkbox.svelte b/src/lib/components/ui/checkbox/checkbox.svelte new file mode 100644 index 0000000..8d69a96 --- /dev/null +++ b/src/lib/components/ui/checkbox/checkbox.svelte @@ -0,0 +1,48 @@ +<script lang="ts"> + import { Icons } from '$lib/assets/icons'; + import { cn } from '$lib/utils.js'; + import { Checkbox as CheckboxPrimitive } from 'bits-ui'; + import { Label } from '../label'; + + type $$Props = CheckboxPrimitive.Props; + type $$Events = CheckboxPrimitive.Events; + + let className: $$Props['class'] = undefined; + export let checked: $$Props['checked'] = false; + export let id: $$Props['id'] = undefined; + export let aria_labelledby: $$Props['aria-labelledby'] = undefined; + export { className as class }; +</script> + +<!-- +TODO: How to properly document these styles? +Here are considerations: there are base classes for the likes such as focus, but the ugly ones are the data-[state=checked] and data-[disabled=true] classes. +Some are for individual states, e.g disabled, checked, while others are combinations of states, e.g checked and disabled. + --> +<div class="inline-flex items-center gap-2"> + <CheckboxPrimitive.Root + class={cn( + 'peer box-content size-4 shrink-0 items-center gap-2 rounded-[4px] border border-gray-700 bg-background-100 ring-offset-background-200 transition-[border-color,background,box-shadow] delay-0 duration-200 ease-in-out hover:bg-gray-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-color focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:border-gray-500 disabled:bg-gray-100 disabled:text-gray-500 data-[disabled=true]:cursor-not-allowed data-[disabled=true]:border-gray-500 data-[state=checked]:border-gray-1000 data-[state=checked]:data-[disabled=true]:border-gray-600 data-[disabled=true]:bg-gray-100 data-[state=checked]:bg-gray-1000 data-[state=checked]:data-[disabled=true]:bg-gray-600 data-[disabled=true]:text-gray-500 data-[state=checked]:data-[disabled=true]:text-background-200 data-[state=checked]:text-background-200', + className + )} + {id} + bind:checked + {...$$restProps} + on:click + > + <CheckboxPrimitive.Indicator + class={cn('flex size-4 items-center justify-center p-0.5 text-current')} + let:isChecked + let:isIndeterminate + > + {#if isChecked} + <Icons.Check aria-hidden="true" class="size-4" /> + {:else if isIndeterminate} + <Icons.Minus aria-hidden="true" class="size-4" /> + {/if} + </CheckboxPrimitive.Indicator> + </CheckboxPrimitive.Root> + <Label id={aria_labelledby} for={id}> + <slot></slot> + </Label> +</div> diff --git a/src/lib/components/ui/checkbox/index.ts b/src/lib/components/ui/checkbox/index.ts new file mode 100644 index 0000000..5c27671 --- /dev/null +++ b/src/lib/components/ui/checkbox/index.ts @@ -0,0 +1,6 @@ +import Root from './checkbox.svelte'; +export { + Root, + // + Root as Checkbox +}; diff --git a/src/lib/components/ui/label/index.ts b/src/lib/components/ui/label/index.ts new file mode 100644 index 0000000..808d141 --- /dev/null +++ b/src/lib/components/ui/label/index.ts @@ -0,0 +1,7 @@ +import Root from './label.svelte'; + +export { + Root, + // + Root as Label +}; diff --git a/src/lib/components/ui/label/label.svelte b/src/lib/components/ui/label/label.svelte new file mode 100644 index 0000000..1916bde --- /dev/null +++ b/src/lib/components/ui/label/label.svelte @@ -0,0 +1,21 @@ +<script lang="ts"> + import { cn } from '$lib/utils.js'; + import { Label as LabelPrimitive } from 'bits-ui'; + + type $$Props = LabelPrimitive.Props; + type $$Events = LabelPrimitive.Events; + + let className: $$Props['class'] = undefined; + export { className as class }; +</script> + +<LabelPrimitive.Root + class={cn( + 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:text-gray-600', + className + )} + {...$$restProps} + on:mousedown +> + <slot /> +</LabelPrimitive.Root> diff --git a/src/lib/config/sitemap.ts b/src/lib/config/sitemap.ts index 615d8e2..143ef51 100644 --- a/src/lib/config/sitemap.ts +++ b/src/lib/config/sitemap.ts @@ -80,7 +80,7 @@ export const aside_items: Aside = { { title: 'Checkbox', href: '/checkbox', - status: 'soon' + status: 'draft' }, { title: 'Choicebox', diff --git a/src/routes/checkbox/+page.svelte b/src/routes/checkbox/+page.svelte index 1769534..4338fe8 100644 --- a/src/routes/checkbox/+page.svelte +++ b/src/routes/checkbox/+page.svelte @@ -1 +1,26 @@ -<h1>checkbox</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 Indeterminate from './indeterminate.svelte'; + import indeterminate_code from './indeterminate.svelte?raw'; + + export let data; +</script> + +<PageWrapper title={data.title} description={data.description}> + <Demo id="default" class="space-y-2" code={default_code}> + <Default /> + </Demo> + + <Demo id="disabled" class="space-y-2" code={disabled_code}> + <Disabled /> + </Demo> + + <Demo id="indeterminate" class="space-y-2" code={indeterminate_code}> + <Indeterminate /> + </Demo> +</PageWrapper> diff --git a/src/routes/checkbox/+page.ts b/src/routes/checkbox/+page.ts new file mode 100644 index 0000000..c4f8995 --- /dev/null +++ b/src/routes/checkbox/+page.ts @@ -0,0 +1,21 @@ +import type { MetaTagsProps } from 'svelte-meta-tags'; + +export function load() { + const title = 'Checkbox'; + const description = 'A control that toggles between two options, checked or unchecked.'; + + const pageMetaTags = Object.freeze({ + title, + description, + openGraph: { + title, + description + } + }) satisfies MetaTagsProps; + + return { + pageMetaTags, + title, + description + }; +} diff --git a/src/routes/checkbox/default.svelte b/src/routes/checkbox/default.svelte new file mode 100644 index 0000000..8db8a0a --- /dev/null +++ b/src/routes/checkbox/default.svelte @@ -0,0 +1,7 @@ +<script lang="ts"> + import { Checkbox } from '$lib/components/ui/checkbox'; + + let checked = false; +</script> + +<Checkbox id="option-1" bind:checked aria-labelledby="options-label">Option 1</Checkbox> diff --git a/src/routes/checkbox/disabled.svelte b/src/routes/checkbox/disabled.svelte new file mode 100644 index 0000000..5cc7225 --- /dev/null +++ b/src/routes/checkbox/disabled.svelte @@ -0,0 +1,9 @@ +<script lang="ts"> + import { Checkbox } from '$lib/components/ui/checkbox'; +</script> + +<div class="flex flex-col gap-4"> + <Checkbox disabled>Disabled</Checkbox> + <Checkbox checked disabled>Disabled Checked</Checkbox> + <Checkbox disabled checked="indeterminate">Disabled Indeterminate</Checkbox> +</div> diff --git a/src/routes/checkbox/indeterminate.svelte b/src/routes/checkbox/indeterminate.svelte new file mode 100644 index 0000000..2006724 --- /dev/null +++ b/src/routes/checkbox/indeterminate.svelte @@ -0,0 +1,5 @@ +<script lang="ts"> + import { Checkbox } from '$lib/components/ui/checkbox'; +</script> + +<Checkbox id="option-2" checked="indeterminate" aria-labelledby="options-label">Option 2</Checkbox>