Skip to content
Snippets Groups Projects
Verified Commit d2e92767 authored by Louis's avatar Louis :fire:
Browse files

Start building out input element

parent edabc671
No related branches found
No related tags found
No related merge requests found
Showing
with 263 additions and 67 deletions
...@@ -23,3 +23,6 @@ Thumbs.db ...@@ -23,3 +23,6 @@ Thumbs.db
# Vite # Vite
vite.config.js.timestamp-* vite.config.js.timestamp-*
vite.config.ts.timestamp-* vite.config.ts.timestamp-*
# Storybook
storybook-static/
\ No newline at end of file
...@@ -2,12 +2,7 @@ import type { StorybookConfig } from '@storybook/sveltekit'; ...@@ -2,12 +2,7 @@ import type { StorybookConfig } from '@storybook/sveltekit';
const config: StorybookConfig = { const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|ts|svelte)'], stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|ts|svelte)'],
addons: [ addons: ['@storybook/addon-svelte-csf', '@storybook/addon-essentials'],
'@storybook/addon-svelte-csf',
'@storybook/addon-essentials',
'@chromatic-com/storybook',
'@storybook/addon-interactions'
],
framework: { framework: {
name: '@storybook/sveltekit', name: '@storybook/sveltekit',
options: {} options: {}
......
...@@ -3,6 +3,7 @@ import type { Preview } from '@storybook/svelte'; ...@@ -3,6 +3,7 @@ import type { Preview } from '@storybook/svelte';
const preview: Preview = { const preview: Preview = {
parameters: { parameters: {
controls: { controls: {
disableSaveFromUI: false,
matchers: { matchers: {
color: /(background|color)$/i, color: /(background|color)$/i,
date: /Date$/i date: /Date$/i
......
{ {
"name": "barbi", "name": "barbi",
"version": "0.0.1", "version": "0.0.1",
"publishConfig": {
"registry": "https://npm.weirdboi.dev"
},
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
"build": "vite build && npm run prepack", "build": "vite build && npm run prepack",
...@@ -51,10 +54,8 @@ ...@@ -51,10 +54,8 @@
"svelte": "^5.0.0" "svelte": "^5.0.0"
}, },
"devDependencies": { "devDependencies": {
"@chromatic-com/storybook": "^3.2.4",
"@playwright/test": "^1.49.1", "@playwright/test": "^1.49.1",
"@storybook/addon-essentials": "^8.5.3", "@storybook/addon-essentials": "^8.5.3",
"@storybook/addon-interactions": "^8.5.3",
"@storybook/addon-svelte-csf": "^5.0.0-next.23", "@storybook/addon-svelte-csf": "^5.0.0-next.23",
"@storybook/blocks": "^8.5.3", "@storybook/blocks": "^8.5.3",
"@storybook/svelte": "^8.5.3", "@storybook/svelte": "^8.5.3",
......
...@@ -21,8 +21,7 @@ ...@@ -21,8 +21,7 @@
{...props} {...props}
{tag} {tag}
hover hover
active active>
>
{@render children?.()} {@render children?.()}
</Panel> </Panel>
......
<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' }} />
<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>
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
disabled?: boolean; disabled?: boolean;
flat?: boolean; flat?: boolean;
full?: boolean; full?: boolean;
} & HTMLElement; } & IntrinsicElements[Tag];
const { const {
// @ts-ignore // @ts-ignore
...@@ -55,8 +55,7 @@ ...@@ -55,8 +55,7 @@
variant variant
]} ]}
{disabled} {disabled}
{...rest} {...rest}>
>
{@render children?.()} {@render children?.()}
</svelte:element> </svelte:element>
......
...@@ -78,8 +78,7 @@ ...@@ -78,8 +78,7 @@
<div <div
class={['popover-wrapper', wrapperClass]} 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}> <div bind:this={triggerElement} onclick={openPopover}>
{@render children?.()} {@render children?.()}
</div> </div>
......
...@@ -2,7 +2,10 @@ ...@@ -2,7 +2,10 @@
const { class: className, ...props } = $props(); const { class: className, ...props } = $props();
</script> </script>
<svg viewBox="0 0 200 110" xmlns="http://www.w3.org/2000/svg" <svg
class={['icon', className]} {...props}> viewBox="0 0 200 110"
<path d="m1.636 5.368 196.73 1.5-97.764 97.764-98.964-99.264z" fill="var(--colour-icon)"/> 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> </svg>
...@@ -9,11 +9,11 @@ ...@@ -9,11 +9,11 @@
argTypes: { argTypes: {
alternate: { alternate: {
name: 'Alternate Style', name: 'Alternate Style',
control: 'boolean', control: 'boolean'
}, },
flat: { flat: {
name: 'Flat Style', name: 'Flat Style',
control: 'boolean', control: 'boolean'
}, },
options: { options: {
table: { table: {
...@@ -29,11 +29,16 @@ ...@@ -29,11 +29,16 @@
args={{ args={{
label: "I'm a dropdown", label: "I'm a dropdown",
options: [ 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)} {#snippet children(args)}
<BarbiTheme /> <BarbiTheme />
<div class="storybook-center"> <div class="storybook-center">
...@@ -48,11 +53,16 @@ ...@@ -48,11 +53,16 @@
label: "This dropdown uses variant='secondary'", label: "This dropdown uses variant='secondary'",
alternate: true, alternate: true,
options: [ 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)} {#snippet children(args)}
<BarbiTheme /> <BarbiTheme />
<div class="storybook-center"> <div class="storybook-center">
...@@ -67,11 +77,16 @@ ...@@ -67,11 +77,16 @@
label: "I'm Flat!", label: "I'm Flat!",
flat: true, flat: true,
options: [ 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)} {#snippet children(args)}
<BarbiTheme /> <BarbiTheme />
<div class="storybook-center"> <div class="storybook-center">
...@@ -83,13 +98,18 @@ ...@@ -83,13 +98,18 @@
<Story <Story
name="Only Icon" name="Only Icon"
args={{ args={{
label: "", label: '',
options: [ 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)} {#snippet children(args)}
<BarbiTheme /> <BarbiTheme />
<div class="storybook-center"> <div class="storybook-center">
...@@ -105,16 +125,23 @@ ...@@ -105,16 +125,23 @@
label: "It won't spin", label: "It won't spin",
icon: CustomIcon, icon: CustomIcon,
options: [ 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)} {#snippet children(args)}
<BarbiTheme /> <BarbiTheme />
<div class="storybook-center"> <div class="storybook-center">
<Dropdown {...args} /> <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> </div>
{/snippet} {/snippet}
</Story> </Story>
......
...@@ -4,9 +4,9 @@ ...@@ -4,9 +4,9 @@
export type Props = { export type Props = {
label?: string; label?: string;
options?: DropdownOption[]; options?: DropdownOption[];
alternate?: boolean, alternate?: boolean;
flat?: boolean, flat?: boolean;
icon?: Component<any> icon?: Component<any>;
}; };
export type DropdownOption = { export type DropdownOption = {
...@@ -23,9 +23,15 @@ ...@@ -23,9 +23,15 @@
import Popover from '../../atoms/popover/Popover.svelte'; import Popover from '../../atoms/popover/Popover.svelte';
import AngleDownIcon from '../..//patterns/icons/AngleDownIcon.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 open = $state(false);
let shouldAnimate = $derived.by(() => Icon === AngleDownIcon) let shouldAnimate = $derived.by(() => Icon === AngleDownIcon);
function wrapOnClick(option: DropdownOption) { function wrapOnClick(option: DropdownOption) {
if (option.onClick) { if (option.onClick) {
...@@ -43,15 +49,21 @@ ...@@ -43,15 +49,21 @@
</script> </script>
<Popover bind:open> <Popover bind:open>
<Button variant={alternate ? 'secondary' : 'primary'} flat={flat}> <Button variant={alternate ? 'secondary' : 'primary'} {flat}>
<span class="dropdown-label">{label}</span> <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> </Button>
{#snippet popover()} {#snippet popover()}
<Panel space="none" class="dropdown-popover-list" flat={flat}> <Panel space="none" class="dropdown-popover-list" {flat}>
{#each options as option} {#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} {option.label}
</Button> </Button>
{/each} {/each}
......
...@@ -5,6 +5,7 @@ import CheckerboardPattern from './patterns/CheckerboardPattern.svelte'; ...@@ -5,6 +5,7 @@ import CheckerboardPattern from './patterns/CheckerboardPattern.svelte';
import Popover from './atoms/popover/Popover.svelte'; import Popover from './atoms/popover/Popover.svelte';
import { Anchor } from './atoms/popover/utils.js'; import { Anchor } from './atoms/popover/utils.js';
import Dropdown from './components/modal/Dropdown.svelte'; import Dropdown from './components/modal/Dropdown.svelte';
import Input from './atoms/Input.svelte';
import type { AnchorName } from './atoms/popover/utils.ts'; import type { AnchorName } from './atoms/popover/utils.ts';
...@@ -12,9 +13,7 @@ import type { AnchorName } from './atoms/popover/utils.ts'; ...@@ -12,9 +13,7 @@ import type { AnchorName } from './atoms/popover/utils.ts';
export { BarbiTheme, CheckerboardPattern, Anchor }; export { BarbiTheme, CheckerboardPattern, Anchor };
// Atoms // Atoms
export { export { Panel, Button, Popover, Input };
Panel, Button, Popover,
}
// Components // Components
export { Dropdown }; export { Dropdown };
......
...@@ -32,8 +32,7 @@ ...@@ -32,8 +32,7 @@
<Story <Story
name="Modified Colours" 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)} {#snippet children(args)}
<BarbiTheme /> <BarbiTheme />
<CheckerboardPattern {...args} /> <CheckerboardPattern {...args} />
......
...@@ -18,16 +18,14 @@ ...@@ -18,16 +18,14 @@
class={className} class={className}
bind:clientHeight={height} bind:clientHeight={height}
bind:clientWidth={width} bind:clientWidth={width}
{...props} {...props}>
>
<defs> <defs>
<pattern <pattern
id="checks" id="checks"
patternUnits="userSpaceOnUse" patternUnits="userSpaceOnUse"
width="16" width="16"
height="16" height="16"
class={scrollDirection ? ['scroll', scrollDirection] : []} class={scrollDirection ? ['scroll', scrollDirection] : []}>
>
<rect width="16" height="16" fill="var(--pattern-light)" /> <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="0" y="0" fill="var(--pattern-dark)" />
<rect width="8" height="8" x="8" y="8" fill="var(--pattern-dark)" /> <rect width="8" height="8" x="8" y="8" fill="var(--pattern-dark)" />
......
...@@ -6,10 +6,8 @@ ...@@ -6,10 +6,8 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class={['icon', className]} class={['icon', className]}
viewBox="0 0 448 512" viewBox="0 0 448 512"
{...props} {...props}>
>
<path <path
style="fill: var(--colour-icon)" 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> </svg>
...@@ -13,12 +13,11 @@ ...@@ -13,12 +13,11 @@
label: 'First Option', label: 'First Option',
value: '123', value: '123',
onClick() { onClick() {
console.log("FOO") console.log('FOO');
} }
}, },
{ label: 'Second Option', value: '456', href: 'https://example.com' } { label: 'Second Option', value: '456', href: 'https://example.com' }
]} ]} />
/>
</div> </div>
<style> <style>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment