diff --git a/.gitignore b/.gitignore index 0a2dd09968370e07cc533053b466ec4f7345e420..f588112edcfa041a031a0d4a39ca931911ba575d 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ Thumbs.db # Vite vite.config.js.timestamp-* vite.config.ts.timestamp-* + +# Storybook +storybook-static/ \ No newline at end of file diff --git a/.storybook/main.ts b/.storybook/main.ts index b55c2e918620c1a8a5b60c5f2c271fae47d7bb91..2998fb18a61a3d5aee34b9f4084587ff8b80b95b 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -2,12 +2,7 @@ import type { StorybookConfig } from '@storybook/sveltekit'; const config: StorybookConfig = { stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|ts|svelte)'], - addons: [ - '@storybook/addon-svelte-csf', - '@storybook/addon-essentials', - '@chromatic-com/storybook', - '@storybook/addon-interactions' - ], + addons: ['@storybook/addon-svelte-csf', '@storybook/addon-essentials'], framework: { name: '@storybook/sveltekit', options: {} diff --git a/.storybook/preview.ts b/.storybook/preview.ts index dbd1c7ac230a22cec028535e7fc084e94c432e6d..6d349b43ddeb792c3f5fab238cd24a7132ac8f34 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -3,6 +3,7 @@ import type { Preview } from '@storybook/svelte'; const preview: Preview = { parameters: { controls: { + disableSaveFromUI: false, matchers: { color: /(background|color)$/i, date: /Date$/i diff --git a/package.json b/package.json index e70aa0acd82e7394f92c06fd92ba28b56f6f80e2..5cffefd6648285211575ad78e7306da31dbefed1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,9 @@ { "name": "barbi", "version": "0.0.1", + "publishConfig": { + "registry": "https://npm.weirdboi.dev" + }, "scripts": { "dev": "vite dev", "build": "vite build && npm run prepack", @@ -51,10 +54,8 @@ "svelte": "^5.0.0" }, "devDependencies": { - "@chromatic-com/storybook": "^3.2.4", "@playwright/test": "^1.49.1", "@storybook/addon-essentials": "^8.5.3", - "@storybook/addon-interactions": "^8.5.3", "@storybook/addon-svelte-csf": "^5.0.0-next.23", "@storybook/blocks": "^8.5.3", "@storybook/svelte": "^8.5.3", diff --git a/src/lib/atoms/Button.svelte b/src/lib/atoms/Button.svelte index 8fccc59219b65579617d3fad38d82fc5a59da8c5..4a0ee56ab56201562b1f681f7768d9a3d3031e73 100644 --- a/src/lib/atoms/Button.svelte +++ b/src/lib/atoms/Button.svelte @@ -21,8 +21,7 @@ {...props} {tag} hover - active -> + active> {@render children?.()} </Panel> diff --git a/src/lib/atoms/Input.stories.svelte b/src/lib/atoms/Input.stories.svelte new file mode 100644 index 0000000000000000000000000000000000000000..9578d853e32eff52ef08dd66613f8da48d9ae130 --- /dev/null +++ b/src/lib/atoms/Input.stories.svelte @@ -0,0 +1,24 @@ +<script module> + import { BarbiTheme, Input } from '$lib'; + import { defineMeta } from '@storybook/addon-svelte-csf'; + + const { Story } = defineMeta({ + title: 'Components/Atoms/Input', + component: Input + }); +</script> + +<BarbiTheme /> +<div> + <style> + .sb-show-main { + padding: var(--spacing-md) !important; + } + </style> +</div> + +<Story name="Simple Text Input" args={{ placeholder: 'Simple Text Input' }} /> +<Story name="Disabled Input" args={{ placeholder: 'Cant edit this', disabled: true }} /> +<Story name="Number Input" args={{ value: 0, type: 'number' }} /> +<Story name="Range Input" args={{ value: 0, type: 'range', min: 0, max: 100, step: 10 }} /> +<Story name="Checkbox Input" args={{ type: 'checkbox' }} /> diff --git a/src/lib/atoms/Input.svelte b/src/lib/atoms/Input.svelte new file mode 100644 index 0000000000000000000000000000000000000000..8a542803495b77035988c00695c4b9995d0d8ea6 --- /dev/null +++ b/src/lib/atoms/Input.svelte @@ -0,0 +1,140 @@ +<script lang="ts" module> + export type TextInputProps = { + value?: string; + autocomplete?: 'on' | 'off'; + }; + export type NumberInputProps = { + value?: number; + min?: number; + max?: number; + step?: number; + }; + + export type BaseInputProps<inputType, additonal> = { + type: inputType; + id?: string; + name?: string; + disabled?: boolean; + required?: boolean; + placeholder?: string; + } & additonal; + + export type Props = + | BaseInputProps<'text' | undefined, TextInputProps> + | BaseInputProps<'number', NumberInputProps>; +</script> + +<script lang="ts"> + let { value = $bindable(undefined), class: className = '', ...props }: Props = $props(); +</script> + +<input bind:value class={['input', className]} {...props} /> + +<style> + .input { + --component-background: var(--colour-white); + --component-colour: var(--colour-text); + --component-placeholder: var(--colour-steel); + + --border-colour: var(--colour-steel); + + color: var(--component-colour); + background-color: var(--component-background); + + font-family: var(--font-family-body); + font-size: var(--font-size-base); + + border: var(--border-width) var(--border-style) var(--border-colour); + padding: var(--spacing-sm) var(--spacing-md); + transition: all var(--transition-fast); + + box-shadow: var(--shadow-md); + outline: none; + } + + .input:hover { + --component-background: var(--colour-background); + --border-colour: var(--colour-steel); + } + + .input:focus { + --border-colour: var(--colour-primary); + } + + .input:disabled { + --component-background: var(--colour-concrete); + opacity: 0.9; + cursor: not-allowed; + } + + /* Placeholder styles */ + .input::placeholder { + color: var(--component-placeholder); + } + + /* Specific styles for different .input types */ + .input[type='checkbox'], + .input[type='radio'] { + width: var(--spacing-md); + height: var(--spacing-md); + margin-right: var(--spacing-sm); + } + + .input[type='color'] { + padding: var(--spacing-xs); + height: calc(var(--font-size-base) * 2); + } + + .input[type='range'] { + --component-background: transparent; + -webkit-appearance: none; + border: none; + box-shadow: none; + padding: 0; + appearance: none; + cursor: pointer; + } + + .input[type='range']::-webkit-slider-runnable-track, + .input[type='range']::-moz-range-track { + --border-colour: var(--colour-contrast); + background: var(--colour-white); + height: var(--spacing-sm); + border: var(--border-width) var(--border-style) var(--border-colour); + transition: border-color var(--transition-fast); + } + + .input[type='range']::-webkit-slider-container, + .input[type='range']::-moz-range-progress { + --border-colour: var(--colour-contrast); + background: var(--colour-primary); + height: var(--spacing-sm); + border: var(--border-width) var(--border-style) var(--border-colour); + transition: border-color var(--transition-fast); + } + .input[type='range']:focus::-webkit-slider-container, + .input[type='range']:focus::-moz-range-progress { + --border-colour: var(--colour-primary); + } + + .input[type='range']:focus { + outline: none; + } + + .input[type='range']:focus::-webkit-slider-runnable-track, + .input[type='range']:focus::-moz-range-track { + --border-colour: var(--colour-primary); + } + + .input[type='range']::-webkit-slider-thumb, + .input[type='range']::-moz-range-thumb { + --border-colour: var(--colour-contrast); + -webkit-appearance: none; + appearance: none; + border-radius: 0; + border: var(--border-width) var(--border-style) var(--border-colour); + width: var(--spacing-sm); + height: var(--spacing-md); + background: var(--colour-background); + } +</style> diff --git a/src/lib/atoms/Panel.svelte b/src/lib/atoms/Panel.svelte index 2483a0e762b1dc71cc3eff872dbfd89eecb2d448..657cc09df4589e7896af24b038207c403943d176 100644 --- a/src/lib/atoms/Panel.svelte +++ b/src/lib/atoms/Panel.svelte @@ -11,7 +11,7 @@ disabled?: boolean; flat?: boolean; full?: boolean; - } & HTMLElement; + } & IntrinsicElements[Tag]; const { // @ts-ignore @@ -55,8 +55,7 @@ variant ]} {disabled} - {...rest} -> + {...rest}> {@render children?.()} </svelte:element> diff --git a/src/lib/atoms/popover/Popover.svelte b/src/lib/atoms/popover/Popover.svelte index 156f8bb4bb04f25176d8bb14a646c41d08829ac9..57313a7b4bf45770b7bb3f27261e1b65c7cce082 100644 --- a/src/lib/atoms/popover/Popover.svelte +++ b/src/lib/atoms/popover/Popover.svelte @@ -78,8 +78,7 @@ <div class={['popover-wrapper', wrapperClass]} - style={`${style ? style : ''}; --component-gap-vertical: ${spaceVertical}; --component-gap-horizontal: ${spaceHorizontal};`} -> + style={`${style ? style : ''}; --component-gap-vertical: ${spaceVertical}; --component-gap-horizontal: ${spaceHorizontal};`}> <div bind:this={triggerElement} onclick={openPopover}> {@render children?.()} </div> diff --git a/src/lib/components/modal/CustomIcon.internal.svelte b/src/lib/components/modal/CustomIcon.internal.svelte index ebe5de82666d85b875c77398e03c7ad86cd60c90..f91593592da9668b39d425d9c5dc3acf1af92820 100644 --- a/src/lib/components/modal/CustomIcon.internal.svelte +++ b/src/lib/components/modal/CustomIcon.internal.svelte @@ -2,7 +2,10 @@ const { class: className, ...props } = $props(); </script> -<svg viewBox="0 0 200 110" xmlns="http://www.w3.org/2000/svg" - class={['icon', className]} {...props}> - <path d="m1.636 5.368 196.73 1.5-97.764 97.764-98.964-99.264z" fill="var(--colour-icon)"/> +<svg + viewBox="0 0 200 110" + xmlns="http://www.w3.org/2000/svg" + class={['icon', className]} + {...props}> + <path d="m1.636 5.368 196.73 1.5-97.764 97.764-98.964-99.264z" fill="var(--colour-icon)" /> </svg> diff --git a/src/lib/components/modal/Dropdown.stories.svelte b/src/lib/components/modal/Dropdown.stories.svelte index 81e21295459d625cc273fbcc83299d0250a78ba9..18d81ab5708683fc8e1265b4555a5779fe219124 100644 --- a/src/lib/components/modal/Dropdown.stories.svelte +++ b/src/lib/components/modal/Dropdown.stories.svelte @@ -9,11 +9,11 @@ argTypes: { alternate: { name: 'Alternate Style', - control: 'boolean', + control: 'boolean' }, flat: { name: 'Flat Style', - control: 'boolean', + control: 'boolean' }, options: { table: { @@ -29,11 +29,16 @@ args={{ label: "I'm a dropdown", options: [ - { value: '1', label: 'I log to the console', onClick() { console.log("First button") } }, - { value: '2', label: "I'm an external link", href: 'https://example.com' }, + { + value: '1', + label: 'I log to the console', + onClick() { + console.log('First button'); + } + }, + { value: '2', label: "I'm an external link", href: 'https://example.com' } ] - }} -> + }}> {#snippet children(args)} <BarbiTheme /> <div class="storybook-center"> @@ -48,11 +53,16 @@ label: "This dropdown uses variant='secondary'", alternate: true, options: [ - { value: '1', label: 'I log to the console', onClick() { console.log("First button") } }, - { value: '2', label: "I'm an external link", href: 'https://example.com' }, + { + value: '1', + label: 'I log to the console', + onClick() { + console.log('First button'); + } + }, + { value: '2', label: "I'm an external link", href: 'https://example.com' } ] - }} -> + }}> {#snippet children(args)} <BarbiTheme /> <div class="storybook-center"> @@ -67,11 +77,16 @@ label: "I'm Flat!", flat: true, options: [ - { value: '1', label: 'I log to the console', onClick() { console.log("First button") } }, - { value: '2', label: "I'm an external link", href: 'https://example.com' }, + { + value: '1', + label: 'I log to the console', + onClick() { + console.log('First button'); + } + }, + { value: '2', label: "I'm an external link", href: 'https://example.com' } ] - }} -> + }}> {#snippet children(args)} <BarbiTheme /> <div class="storybook-center"> @@ -83,13 +98,18 @@ <Story name="Only Icon" args={{ - label: "", + label: '', options: [ - { value: '1', label: 'I log to the console', onClick() { console.log("First button") } }, - { value: '2', label: "I'm an external link", href: 'https://example.com' }, + { + value: '1', + label: 'I log to the console', + onClick() { + console.log('First button'); + } + }, + { value: '2', label: "I'm an external link", href: 'https://example.com' } ] - }} -> + }}> {#snippet children(args)} <BarbiTheme /> <div class="storybook-center"> @@ -105,16 +125,23 @@ label: "It won't spin", icon: CustomIcon, options: [ - { value: '1', label: 'I log to the console', onClick() { console.log("First button") } }, - { value: '2', label: "I'm an external link", href: 'https://example.com' }, + { + value: '1', + label: 'I log to the console', + onClick() { + console.log('First button'); + } + }, + { value: '2', label: "I'm an external link", href: 'https://example.com' } ] - }} -> + }}> {#snippet children(args)} <BarbiTheme /> <div class="storybook-center"> <Dropdown {...args} /> - <span>Pass a component to the 'icon' prop that uses the global '.icon' class, and merges it with a 'class' property</span> + <span + >Pass a component to the 'icon' prop that uses the global '.icon' class, and merges + it with a 'class' property</span> </div> {/snippet} </Story> diff --git a/src/lib/components/modal/Dropdown.svelte b/src/lib/components/modal/Dropdown.svelte index 6ab9d745de6f4508846929b0b6601f04071784ca..ca810603a5fd7603251c9103759193fffedc3a24 100644 --- a/src/lib/components/modal/Dropdown.svelte +++ b/src/lib/components/modal/Dropdown.svelte @@ -4,9 +4,9 @@ export type Props = { label?: string; options?: DropdownOption[]; - alternate?: boolean, - flat?: boolean, - icon?: Component<any> + alternate?: boolean; + flat?: boolean; + icon?: Component<any>; }; export type DropdownOption = { @@ -23,9 +23,15 @@ import Popover from '../../atoms/popover/Popover.svelte'; import AngleDownIcon from '../..//patterns/icons/AngleDownIcon.svelte'; - const { label = '', options = [], alternate = false, flat = false, icon: Icon = AngleDownIcon }: Props = $props(); + const { + label = '', + options = [], + alternate = false, + flat = false, + icon: Icon = AngleDownIcon + }: Props = $props(); let open = $state(false); - let shouldAnimate = $derived.by(() => Icon === AngleDownIcon) + let shouldAnimate = $derived.by(() => Icon === AngleDownIcon); function wrapOnClick(option: DropdownOption) { if (option.onClick) { @@ -43,15 +49,21 @@ </script> <Popover bind:open> - <Button variant={alternate ? 'secondary' : 'primary'} flat={flat}> + <Button variant={alternate ? 'secondary' : 'primary'} {flat}> <span class="dropdown-label">{label}</span> - <Icon style="--size-icon: 1.25rem;" class={['dropdown-arrow', shouldAnimate && 'dropdown-arrow-animate', { open }]} /> + <Icon + style="--size-icon: 1.25rem;" + class={['dropdown-arrow', shouldAnimate && 'dropdown-arrow-animate', { open }]} /> </Button> {#snippet popover()} - <Panel space="none" class="dropdown-popover-list" flat={flat}> + <Panel space="none" class="dropdown-popover-list" {flat}> {#each options as option} - <Button variant={alternate ? 'success' : 'secondary'} flat onclick={wrapOnClick(option)} href={option.href}> + <Button + variant={alternate ? 'success' : 'secondary'} + flat + onclick={wrapOnClick(option)} + href={option.href}> {option.label} </Button> {/each} diff --git a/src/lib/index.ts b/src/lib/index.ts index 49856ce060898e7e586db600e46d9483d114085f..011c4828cf64534da8ff14091b382e62c789407b 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -5,6 +5,7 @@ import CheckerboardPattern from './patterns/CheckerboardPattern.svelte'; import Popover from './atoms/popover/Popover.svelte'; import { Anchor } from './atoms/popover/utils.js'; import Dropdown from './components/modal/Dropdown.svelte'; +import Input from './atoms/Input.svelte'; import type { AnchorName } from './atoms/popover/utils.ts'; @@ -12,9 +13,7 @@ import type { AnchorName } from './atoms/popover/utils.ts'; export { BarbiTheme, CheckerboardPattern, Anchor }; // Atoms -export { - Panel, Button, Popover, -} +export { Panel, Button, Popover, Input }; // Components export { Dropdown }; diff --git a/src/lib/patterns/CheckerboardPattern.stories.svelte b/src/lib/patterns/CheckerboardPattern.stories.svelte index 937d657dd5510fa7e6d533e1c38945a4bca80cc7..e8825f0cd3e76f5798d514d3a8bfff8d92aa0131 100644 --- a/src/lib/patterns/CheckerboardPattern.stories.svelte +++ b/src/lib/patterns/CheckerboardPattern.stories.svelte @@ -32,8 +32,7 @@ <Story name="Modified Colours" - args={{ style: '--pattern-light: #ECA400; --pattern-dark: #27476E;', scale: 2 }} -> + args={{ style: '--pattern-light: #ECA400; --pattern-dark: #27476E;', scale: 2 }}> {#snippet children(args)} <BarbiTheme /> <CheckerboardPattern {...args} /> diff --git a/src/lib/patterns/CheckerboardPattern.svelte b/src/lib/patterns/CheckerboardPattern.svelte index 6fb0bfdae6c170f515d6752a1fc077afd446324a..871f864dcca9298f0a594985a0b949b40a6c82e3 100644 --- a/src/lib/patterns/CheckerboardPattern.svelte +++ b/src/lib/patterns/CheckerboardPattern.svelte @@ -18,16 +18,14 @@ class={className} bind:clientHeight={height} bind:clientWidth={width} - {...props} -> + {...props}> <defs> <pattern id="checks" patternUnits="userSpaceOnUse" width="16" height="16" - class={scrollDirection ? ['scroll', scrollDirection] : []} - > + class={scrollDirection ? ['scroll', scrollDirection] : []}> <rect width="16" height="16" fill="var(--pattern-light)" /> <rect width="8" height="8" x="0" y="0" fill="var(--pattern-dark)" /> <rect width="8" height="8" x="8" y="8" fill="var(--pattern-dark)" /> diff --git a/src/lib/patterns/icons/AngleDownIcon.svelte b/src/lib/patterns/icons/AngleDownIcon.svelte index acd641496db2b7afab6c8335bd78b42d267a0741..2513bc8d6cc57c9ddac16912236aea96811322bc 100644 --- a/src/lib/patterns/icons/AngleDownIcon.svelte +++ b/src/lib/patterns/icons/AngleDownIcon.svelte @@ -6,10 +6,8 @@ xmlns="http://www.w3.org/2000/svg" class={['icon', className]} viewBox="0 0 448 512" - {...props} -> + {...props}> <path style="fill: var(--colour-icon)" - d="M201.4 374.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 306.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z" - /> + d="M201.4 374.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 306.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z" /> </svg> diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index fb0d6e86a2f6cf2c290b0415cacf6614e2212d80..e6aea9a28e90939238dc9e7e412ebd28de616307 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -13,12 +13,11 @@ label: 'First Option', value: '123', onClick() { - console.log("FOO") + console.log('FOO'); } }, { label: 'Second Option', value: '456', href: 'https://example.com' } - ]} - /> + ]} /> </div> <style>