refactor: use $$restProps API

- add ref prop for applicable components (#196)
- add slot to Content Switcher `Switch` component (#183)
- remove fillArray, css utilities
This commit is contained in:
Eric Liu 2020-07-18 20:00:20 -07:00
commit e886d772c7
288 changed files with 4681 additions and 4498 deletions

View file

@ -0,0 +1,8 @@
<style>
body {
padding: 3rem;
display: flex;
flex-direction: column;
min-height: 100vh;
}
</style>

View file

@ -1 +1,4 @@
<link rel="stylesheet" href="https://unpkg.com/carbon-components/css/carbon-components.min.css" /> <link
rel="stylesheet"
href="https://unpkg.com/carbon-components/css/carbon-components.min.css"
/>

View file

@ -16,7 +16,7 @@
}, },
"sideEffects": false, "sideEffects": false,
"dependencies": { "dependencies": {
"carbon-icons-svelte": "10.9.3", "carbon-icons-svelte": "^10.13.0",
"flatpickr": "4.6.3" "flatpickr": "4.6.3"
}, },
"devDependencies": { "devDependencies": {

View file

@ -1,40 +1,39 @@
<script> <script>
let className = undefined;
export { className as class };
export let count = 4; export let count = 4;
export let open = true; export let open = true;
export let style = undefined;
import ChevronRight16 from 'carbon-icons-svelte/lib/ChevronRight16'; import ChevronRight16 from "carbon-icons-svelte/lib/ChevronRight16";
import { cx, fillArray } from '../../lib'; import { SkeletonText } from "../SkeletonText";
import SkeletonText from '../SkeletonText';
</script> </script>
<ul <ul
class:bx--accordion={true}
class:bx--skeleton={true}
{...$$restProps}
on:click on:click
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave>
class={cx('--accordion', '--skeleton', className)}
{style}>
{#if open} {#if open}
<li class={cx('--accordion__item', '--accordion__item--active')}> <li
<span class={cx('--accordion__heading')}> class:bx--accordion__item={true}
<ChevronRight16 class={cx('--accordion__arrow')} /> class:bx--accordion__item--active={true}>
<SkeletonText class={cx('--accordion__title')} /> <span class:bx--accordion__heading={true}>
<ChevronRight16 class="bx--accordion__arrow" />
<SkeletonText class="bx--accordion__title" />
</span> </span>
<div class={cx('--accordion__content')}> <div class="bx--accordion__content">
<SkeletonText width="90%" /> <SkeletonText width="90%" />
<SkeletonText width="80%" /> <SkeletonText width="80%" />
<SkeletonText width="95%" /> <SkeletonText width="95%" />
</div> </div>
</li> </li>
{/if} {/if}
{#each fillArray(open ? count - 1 : count) as item, i (item)} {#each Array.from({ length: open ? count - 1 : count }, (_, i) => i) as item, i (item)}
<li class={cx('--accordion__item')}> <li class="bx--accordion__item">
<span class={cx('--accordion__heading')}> <span class="bx--accordion__heading">
<ChevronRight16 class={cx('--accordion__arrow')} /> <ChevronRight16 class="bx--accordion__arrow" />
<SkeletonText class={cx('--accordion__title')} /> <SkeletonText class="bx--accordion__title" />
</span> </span>
</li> </li>
{/each} {/each}

View file

@ -4,38 +4,39 @@
export let open = undefined; export let open = undefined;
export let count = undefined; export let count = undefined;
import Layout from '../../internal/ui/Layout.svelte'; import Accordion from "./Accordion.svelte";
import Accordion from './Accordion.svelte'; import AccordionItem from "./AccordionItem.svelte";
import AccordionItem from './AccordionItem.svelte'; import AccordionSkeleton from "./Accordion.Skeleton.svelte";
import AccordionSkeleton from './Accordion.Skeleton.svelte';
</script> </script>
<Layout> {#if story === 'skeleton'}
{#if story === 'skeleton'}
<div style="width: 500px"> <div style="width: 500px">
<AccordionSkeleton {open} {count} /> <AccordionSkeleton {open} {count} />
</div> </div>
{:else} {:else}
<Accordion> <Accordion>
<AccordionItem {title} {open}> <AccordionItem {title} {open}>
<p> <p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
ullamco laboris nisi ut aliquip ex ea commodo consequat. veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
commodo consequat.
</p> </p>
</AccordionItem> </AccordionItem>
<AccordionItem title="Section 2 title"> <AccordionItem title="Section 2 title">
<p> <p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
ullamco laboris nisi ut aliquip ex ea commodo consequat. veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
commodo consequat.
</p> </p>
</AccordionItem> </AccordionItem>
<AccordionItem title="Section 3 title"> <AccordionItem title="Section 3 title">
<p> <p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
ullamco laboris nisi ut aliquip ex ea commodo consequat. veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
commodo consequat.
</p> </p>
</AccordionItem> </AccordionItem>
<AccordionItem> <AccordionItem>
@ -45,11 +46,11 @@
) )
</div> </div>
<p> <p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
ullamco laboris nisi ut aliquip ex ea commodo consequat. veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
commodo consequat.
</p> </p>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
{/if} {/if}
</Layout>

View file

@ -1,21 +1,32 @@
import { withKnobs, text, boolean, number } from '@storybook/addon-knobs'; import {
import Component from './Accordion.Story.svelte'; withKnobs,
text,
boolean,
number,
select,
} from "@storybook/addon-knobs";
import Component from "./Accordion.Story.svelte";
export default { title: 'Accordion', decorators: [withKnobs] }; export default { title: "Accordion", decorators: [withKnobs] };
export const Default = () => ({ export const Default = () => ({
Component, Component,
props: { props: {
title: text('The title (title)', 'Section 1 title'), align: select(
open: boolean('Open the section (open)', false) "Accordion heading alignment (align)",
} ["start", "end"],
"end"
),
title: text("The title (title)", "Section 1 title"),
open: boolean("Open the section (open)", false),
},
}); });
export const Skeleton = () => ({ export const Skeleton = () => ({
Component, Component,
props: { props: {
story: 'skeleton', story: "skeleton",
open: boolean('Show first item opened (open)', true), open: boolean("Show first item opened (open)", true),
count: number('Set number of items (count)', 4) count: number("Set number of items (count)", 4),
} },
}); });

View file

@ -1,28 +1,21 @@
<script> <script>
let className = undefined; export let align = "end"; // "start" | "end"
export { className as class };
export let style = undefined;
export let skeleton = false; export let skeleton = false;
export let count = 4;
export let open = true;
import { cx } from '../../lib'; import AccordionSkeleton from "./Accordion.Skeleton.svelte";
import AccordionSkeleton from './Accordion.Skeleton.svelte';
</script> </script>
{#if skeleton} {#if skeleton}
<AccordionSkeleton {count} {open} class={className} {style} /> <AccordionSkeleton {...$$restProps} />
{/if} {:else}
{#if !skeleton}
<ul <ul
class:bx--accordion={true}
class="bx--accordion--{align}"
{...$$restProps}
on:click on:click
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave>
class={cx('--accordion', className)}
{style}>
<slot /> <slot />
</ul> </ul>
{/if} {/if}

View file

@ -1,27 +1,25 @@
<script> <script>
let className = undefined; export let title = "title";
export { className as class };
export let iconDescription = 'Expand/Collapse';
export let open = false; export let open = false;
export let style = undefined; export let iconDescription = "Expand/Collapse";
export let title = undefined;
import ChevronRight16 from 'carbon-icons-svelte/lib/ChevronRight16'; import ChevronRight16 from "carbon-icons-svelte/lib/ChevronRight16";
import { cx } from '../../lib';
let animation = undefined; $: animation = undefined;
</script> </script>
<li <li
class={cx('--accordion__item', open && '--accordion__item--active', animation && `--accordion__item--${animation}`, className)} class:bx--accordion__item={true}
class:bx--accordion__item--active={open}
class="bx--accordion__item--${animation}"
{...$$restProps}
on:animationend on:animationend
on:animationend={() => { on:animationend={() => {
animation = undefined; animation = undefined;
}} }}>
{style}>
<button <button
type="button" type="button"
class={cx('--accordion__heading')} class:bx--accordion__heading={true}
title={iconDescription} title={iconDescription}
aria-expanded={open} aria-expanded={open}
on:click on:click
@ -38,12 +36,12 @@
open = false; open = false;
} }
}}> }}>
<ChevronRight16 class={cx('--accordion__arrow')} aria-label={iconDescription} /> <ChevronRight16 class="bx--accordion__arrow" aria-label={iconDescription} />
<div class={cx('--accordion__title')}> <div class="bx--accordion__title">
<slot name="title">{title}</slot> <slot name="title">{title}</slot>
</div> </div>
</button> </button>
<div class={cx('--accordion__content')}> <div class="bx--accordion__content">
<slot /> <slot />
</div> </div>
</li> </li>

View file

@ -1,5 +1,3 @@
import Accordion from './Accordion.svelte'; export { default as Accordion } from "./Accordion.svelte";
export { default as AccordionItem } from "./AccordionItem.svelte";
export default Accordion; export { default as AccordionSkeleton } from "./Accordion.Skeleton.svelte";
export { default as AccordionItem } from './AccordionItem.svelte';
export { default as AccordionSkeleton } from './Accordion.Skeleton.svelte';

View file

@ -1,21 +1,14 @@
<script>
let className = undefined;
export { className as class };
export let style = undefined;
import { cx } from '../../lib';
</script>
<div <div
class:bx--breadcrumb={true}
class:bx--skeleton={true}
{...$$restProps}
on:click on:click
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave>
class={cx('--breadcrumb', '--skeleton', className)}
{style}>
{#each [0, 1, 2] as item, i (item)} {#each [0, 1, 2] as item, i (item)}
<div class={cx('--breadcrumb-item')}> <div class:bx--breadcrumb-item={true}>
<span class={cx('--link')}>&nbsp;</span> <span class:bx--link={true}>&nbsp;</span>
</div> </div>
{/each} {/each}
</div> </div>

View file

@ -2,22 +2,20 @@
export let story = undefined; export let story = undefined;
export let noTrailingSlash = undefined; export let noTrailingSlash = undefined;
import Layout from '../../internal/ui/Layout.svelte'; import Breadcrumb from "./Breadcrumb.svelte";
import Breadcrumb from './Breadcrumb.svelte'; import BreadcrumbItem from "./BreadcrumbItem.svelte";
import BreadcrumbItem from './BreadcrumbItem.svelte'; import BreadcrumbSkeleton from "./Breadcrumb.Skeleton.svelte";
import BreadcrumbSkeleton from './Breadcrumb.Skeleton.svelte';
</script> </script>
<Layout> {#if story === 'current page'}
{#if story === 'current page'} <Breadcrumb noTrailingSlash aria-label="Breadcrumb header">
<Breadcrumb noTrailingSlash>
<BreadcrumbItem let:props> <BreadcrumbItem let:props>
<a {...props} href="/#">Breadcrumb 1</a> <a {...props} href="/#">Breadcrumb 1</a>
</BreadcrumbItem> </BreadcrumbItem>
<BreadcrumbItem href="#">Breadcrumb 2</BreadcrumbItem> <BreadcrumbItem href="#">Breadcrumb 2</BreadcrumbItem>
<BreadcrumbItem href="#" isCurrentPage>Breadcrumb 3</BreadcrumbItem> <BreadcrumbItem href="#" isCurrentPage>Breadcrumb 3</BreadcrumbItem>
</Breadcrumb> </Breadcrumb>
{:else if story === 'current page with aria-current'} {:else if story === 'current page with aria-current'}
<Breadcrumb noTrailingSlash> <Breadcrumb noTrailingSlash>
<BreadcrumbItem let:props> <BreadcrumbItem let:props>
<a {...props} href="/#">Breadcrumb 1</a> <a {...props} href="/#">Breadcrumb 1</a>
@ -25,9 +23,9 @@
<BreadcrumbItem href="#">Breadcrumb 2</BreadcrumbItem> <BreadcrumbItem href="#">Breadcrumb 2</BreadcrumbItem>
<BreadcrumbItem href="#" aria-current="page">Breadcrumb 3</BreadcrumbItem> <BreadcrumbItem href="#" aria-current="page">Breadcrumb 3</BreadcrumbItem>
</Breadcrumb> </Breadcrumb>
{:else if story === 'skeleton'} {:else if story === 'skeleton'}
<BreadcrumbSkeleton /> <BreadcrumbSkeleton />
{:else} {:else}
<Breadcrumb {noTrailingSlash}> <Breadcrumb {noTrailingSlash}>
<BreadcrumbItem let:props> <BreadcrumbItem let:props>
<a {...props} href="/#">Breadcrumb 1</a> <a {...props} href="/#">Breadcrumb 1</a>
@ -35,5 +33,4 @@
<BreadcrumbItem href="#">Breadcrumb 2</BreadcrumbItem> <BreadcrumbItem href="#">Breadcrumb 2</BreadcrumbItem>
<BreadcrumbItem href="#">Breadcrumb 3</BreadcrumbItem> <BreadcrumbItem href="#">Breadcrumb 3</BreadcrumbItem>
</Breadcrumb> </Breadcrumb>
{/if} {/if}
</Layout>

View file

@ -1,24 +1,26 @@
import { withKnobs, boolean } from '@storybook/addon-knobs'; import { withKnobs, boolean } from "@storybook/addon-knobs";
import Component from './Breadcrumb.Story.svelte'; import Component from "./Breadcrumb.Story.svelte";
export default { title: 'Breadcrumb', decorators: [withKnobs] }; export default { title: "Breadcrumb", decorators: [withKnobs] };
export const Default = () => ({ export const Default = () => ({
Component, Component,
props: { noTrailingSlash: boolean('No Trailing Slash (noTrailingSlash)', false) } props: {
noTrailingSlash: boolean("No Trailing Slash (noTrailingSlash)", false),
},
}); });
export const Skeleton = () => ({ export const Skeleton = () => ({
Component, Component,
props: { story: 'skeleton' } props: { story: "skeleton" },
}); });
export const CurrentPage = () => ({ export const CurrentPage = () => ({
Component, Component,
props: { story: 'current page' } props: { story: "current page" },
}); });
export const CurrentPageWithAriaCurrent = () => ({ export const CurrentPageWithAriaCurrent = () => ({
Component, Component,
props: { story: 'current page with aria-current' } props: { story: "current page with aria-current" },
}); });

View file

@ -1,21 +1,17 @@
<script> <script>
let className = undefined;
export { className as class };
export let noTrailingSlash = false; export let noTrailingSlash = false;
export let style = undefined;
import { cx } from '../../lib';
</script> </script>
<nav <nav
aria-label="Breadcrumb"
{...$$restProps}
on:click on:click
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave>
aria-label={$$props['aria-label'] || 'Breadcrumb'} <ol
class={className} class:bx--breadcrumb={true}
{style}> class:bx--breadcrumb--no-trailing-slash={noTrailingSlash}>
<ol class={cx('--breadcrumb', noTrailingSlash && '--breadcrumb--no-trailing-slash')}>
<slot /> <slot />
</ol> </ol>
</nav> </nav>

View file

@ -1,18 +1,20 @@
import { render } from '@testing-library/svelte'; import { render } from "@testing-library/svelte";
import Component from './Breadcrumb.Story.svelte'; import Component from "./Breadcrumb.Story.svelte";
describe('Breadcrumb', () => { describe("Breadcrumb", () => {
function getLastBreadcrumbItem(container) { function getLastBreadcrumbItem(container) {
const breadcrumbItems = container.querySelectorAll('.bx--breadcrumb-item'); const breadcrumbItems = container.querySelectorAll(".bx--breadcrumb-item");
return breadcrumbItems[breadcrumbItems.length - 1]; return breadcrumbItems[breadcrumbItems.length - 1];
} }
test('default', () => { test("default", () => {
const { getByText, container, rerender } = render(Component, { noTrailingSlash: false }); const { getByText, container, rerender } = render(Component, {
const selector = '.bx--breadcrumb--no-trailing-slash'; noTrailingSlash: false,
expect(getByText('Breadcrumb 1')).toBeInTheDocument(); });
expect(getByText('Breadcrumb 2')).toBeInTheDocument(); const selector = ".bx--breadcrumb--no-trailing-slash";
expect(getByText('Breadcrumb 3')).toBeInTheDocument(); expect(getByText("Breadcrumb 1")).toBeInTheDocument();
expect(getByText("Breadcrumb 2")).toBeInTheDocument();
expect(getByText("Breadcrumb 3")).toBeInTheDocument();
expect(container.querySelector(selector)).not.toBeInTheDocument(); expect(container.querySelector(selector)).not.toBeInTheDocument();
expect(container.innerHTML).toMatchSnapshot(); expect(container.innerHTML).toMatchSnapshot();
@ -21,19 +23,23 @@ describe('Breadcrumb', () => {
expect(container.innerHTML).toMatchSnapshot(); expect(container.innerHTML).toMatchSnapshot();
}); });
test('skeleton', () => { test("skeleton", () => {
const { container } = render(Component, { story: 'skeleton' }); const { container } = render(Component, { story: "skeleton" });
expect(container.innerHTML).toMatchSnapshot(); expect(container.innerHTML).toMatchSnapshot();
}); });
test('current page', () => { test("current page", () => {
const { container } = render(Component, { story: 'current page' }); const { container } = render(Component, { story: "current page" });
const lastItem = getLastBreadcrumbItem(container); const lastItem = getLastBreadcrumbItem(container);
expect(lastItem.classList.contains('bx--breadcrumb-item--current')).toEqual(true); expect(lastItem.classList.contains("bx--breadcrumb-item--current")).toEqual(
true
);
}); });
test('current page with aria-current', () => { test("current page with aria-current", () => {
const { container } = render(Component, { story: 'current page with aria-current' }); const { container } = render(Component, {
story: "current page with aria-current",
});
const lastItem = getLastBreadcrumbItem(container); const lastItem = getLastBreadcrumbItem(container);
expect(lastItem.querySelector('[aria-current="page"]')).toBeTruthy(); expect(lastItem.querySelector('[aria-current="page"]')).toBeTruthy();
}); });

View file

@ -1,28 +1,24 @@
<script> <script>
let className = undefined;
export { className as class };
export let href = undefined; export let href = undefined;
export let isCurrentPage = false; export let isCurrentPage = false;
export let style = undefined;
import { cx } from '../../lib'; import { Link } from "../Link";
import Link from '../Link';
$: ariaCurrent = $$props['aria-current'];
</script> </script>
<li <li
class:bx--breadcrumb-item={true}
class:bx--breadcrumb-item--current={isCurrentPage && $$restProps['aria-current'] !== 'page'}
{...$$restProps}
on:click on:click
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave>
class={cx('--breadcrumb-item', isCurrentPage && ariaCurrent !== 'page' && '--breadcrumb-item--current', className)}
{style}>
{#if href} {#if href}
<Link {href} aria-current={ariaCurrent}> <Link {href} aria-current={$$restProps['aria-current']}>
<slot /> <slot />
</Link> </Link>
{:else} {:else}
<slot props={{ 'aria-current': ariaCurrent, class: cx('--link') }} /> <slot
props={{ 'aria-current': $$restProps['aria-current'], class: 'bx--link' }} />
{/if} {/if}
</li> </li>

View file

@ -1,5 +1,3 @@
import Breadcrumb from './Breadcrumb.svelte'; export { default as Breadcrumb } from "./Breadcrumb.svelte";
export { default as BreadcrumbItem } from "./BreadcrumbItem.svelte";
export default Breadcrumb; export { default as BreadcrumbSkeleton } from "./Breadcrumb.Skeleton.svelte";
export { default as BreadcrumbItem } from './BreadcrumbItem.svelte';
export { default as BreadcrumbSkeleton } from './Breadcrumb.Skeleton.svelte';

View file

@ -1,31 +1,30 @@
<script> <script>
let className = undefined;
export { className as class };
export let href = undefined; export let href = undefined;
export let small = false; export let small = false;
export let style = undefined;
import { cx } from '../../lib';
</script> </script>
{#if href} {#if href}
<a <a
{href}
role="button" role="button"
class:bx--skeleton={true}
class:bx--btn={true}
class:bx--btn--sm={small}
{...$$restProps}
on:click on:click
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave>
class={cx('--skeleton', '--btn', small && '--btn--sm', className)}
{style}
{href}>
{''} {''}
</a> </a>
{:else} {:else}
<div <div
class:bx--skeleton={true}
class:bx--btn={true}
class:bx--btn--sm={small}
{...$$restProps}
on:click on:click
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave />
class={cx('--skeleton', '--btn', small && '--btn--sm', className)}
{style} />
{/if} {/if}

View file

@ -1,11 +1,9 @@
<script> <script>
export let story = undefined; export let story = undefined;
import { cx } from '../../lib'; import Button from "./Button.svelte";
import Layout from '../../internal/ui/Layout.svelte'; import ButtonSkeleton from "./Button.Skeleton.svelte";
import Button from './Button.svelte'; import Add16 from "carbon-icons-svelte/lib/Add16";
import ButtonSkeleton from './Button.Skeleton.svelte';
import Add16 from 'carbon-icons-svelte/lib/Add16';
const { const {
kind, kind,
@ -39,8 +37,7 @@
const setProps = { disabled, small, size, iconDescription }; const setProps = { disabled, small, size, iconDescription };
</script> </script>
<Layout> <div>
<div>
{#if story === 'skeleton'} {#if story === 'skeleton'}
<ButtonSkeleton /> <ButtonSkeleton />
&nbsp; &nbsp;
@ -52,7 +49,7 @@
{:else if story === 'icon-only buttons'} {:else if story === 'icon-only buttons'}
<Button {...iconOnlyProps} /> <Button {...iconOnlyProps} />
{:else if story === 'set of buttons'} {:else if story === 'set of buttons'}
<div class={cx('--btn-set')}> <div class="bx--btn-set">
<Button kind="secondary" {...setProps}>Secondary button</Button> <Button kind="secondary" {...setProps}>Secondary button</Button>
<Button kind="primary" {...setProps}>Primary button</Button> <Button kind="primary" {...setProps}>Primary button</Button>
</div> </div>
@ -70,5 +67,4 @@
<a {...props}>Custom component</a> <a {...props}>Custom component</a>
</Button> </Button>
{/if} {/if}
</div> </div>
</Layout>

View file

@ -1,62 +1,62 @@
import { withKnobs, select, boolean, text } from '@storybook/addon-knobs'; import { withKnobs, select, boolean, text } from "@storybook/addon-knobs";
import Component from './Button.Story.svelte'; import Component from "./Button.Story.svelte";
export default { title: 'Button', decorators: [withKnobs] }; export default { title: "Button", decorators: [withKnobs] };
const kinds = { const kinds = {
'Primary button (primary)': 'primary', "Primary button (primary)": "primary",
'Secondary button (secondary)': 'secondary', "Secondary button (secondary)": "secondary",
'Danger button (danger)': 'danger', "Danger button (danger)": "danger",
'Ghost button (ghost)': 'ghost' "Ghost button (ghost)": "ghost",
}; };
const sizes = { const sizes = {
Default: 'default', Default: "default",
Field: 'field', Field: "field",
Small: 'small' Small: "small",
}; };
export const Default = () => ({ export const Default = () => ({
Component, Component,
props: { props: {
kind: select('Button kind (kind)', kinds, 'primary'), kind: select("Button kind (kind)", kinds, "primary"),
disabled: boolean('Disabled (disabled)', false), disabled: boolean("Disabled (disabled)", false),
size: select('Button size (size)', sizes, 'default'), size: select("Button size (size)", sizes, "default"),
iconDescription: text('Icon description (iconDescription)', 'Button icon'), iconDescription: text("Icon description (iconDescription)", "Button icon"),
small: boolean('Small (small) - Deprecated in favor of `size`', false) small: boolean("Small (small) - Deprecated in favor of `size`", false),
} },
}); });
export const IconOnlyButtons = () => ({ export const IconOnlyButtons = () => ({
Component, Component,
props: { props: {
story: 'icon-only buttons', story: "icon-only buttons",
kind: select('Button kind (kind)', kinds, 'primary'), kind: select("Button kind (kind)", kinds, "primary"),
disabled: boolean('Disabled (disabled)', false), disabled: boolean("Disabled (disabled)", false),
size: select('Button size (size)', sizes, 'default'), size: select("Button size (size)", sizes, "default"),
iconDescription: text('Icon description (iconDescription)', 'Button icon'), iconDescription: text("Icon description (iconDescription)", "Button icon"),
tooltipPosition: select( tooltipPosition: select(
'Tooltip position (tooltipPosition)', "Tooltip position (tooltipPosition)",
['top', 'right', 'bottom', 'left'], ["top", "right", "bottom", "left"],
'bottom' "bottom"
), ),
tooltipAlignment: select( tooltipAlignment: select(
'Tooltip alignment (tooltipAlignment)', "Tooltip alignment (tooltipAlignment)",
['start', 'center', 'end'], ["start", "center", "end"],
'center' "center"
) ),
} },
}); });
export const SetOfButtons = () => ({ export const SetOfButtons = () => ({
Component, Component,
props: { props: {
story: 'set of buttons', story: "set of buttons",
disabled: boolean('Disabled (disabled)', false), disabled: boolean("Disabled (disabled)", false),
small: boolean('Small (small)', false), small: boolean("Small (small)", false),
size: select('Button size (size)', sizes, 'default'), size: select("Button size (size)", sizes, "default"),
iconDescription: text('Icon description (iconDescription)', 'Button icon') iconDescription: text("Icon description (iconDescription)", "Button icon"),
} },
}); });
export const Skeleton = () => ({ Component, props: { story: 'skeleton' } }); export const Skeleton = () => ({ Component, props: { story: "skeleton" } });

View file

@ -1,50 +1,49 @@
<script> <script>
let className = undefined;
export { className as class };
export let as = undefined; export let as = undefined;
export let disabled = false; export let disabled = false;
export let href = undefined; export let href = undefined;
export let icon = undefined; export let icon = undefined;
export let iconDescription = undefined; export let iconDescription = undefined;
export let hasIconOnly = false; export let hasIconOnly = false;
export let kind = 'primary'; export let kind = "primary";
export let size = 'default'; export let size = "default";
export let style = undefined; export let tabindex = "0";
export let tabindex = '0';
export let tooltipAlignment = undefined; export let tooltipAlignment = undefined;
export let tooltipPosition = undefined; export let tooltipPosition = undefined;
export let type = 'button'; export let type = "button";
export let ref = null;
import { getContext } from 'svelte'; import { getContext } from "svelte";
import { cx } from '../../lib';
const ctx = getContext('ComposedModal'); const ctx = getContext("ComposedModal");
let buttonRef = undefined; $: if (ctx && ref) {
ctx.declareRef(ref);
$: if (ctx && buttonRef) {
ctx.declareRef(buttonRef);
} }
$: buttonProps = { $: buttonProps = {
role: 'button', role: "button",
type: href && !disabled ? undefined : type, type: href && !disabled ? undefined : type,
tabindex, tabindex,
disabled, disabled,
href, href,
style, style: $$restProps.style,
class: cx( class: [
'--btn', "bx--btn",
size === 'field' && '--btn--field', size === "field" && "bx--btn--field",
size === 'small' && '--btn--sm', size === "small" && "bx--btn--sm",
kind && `--btn--${kind}`, kind && `bx--btn--${kind}`,
disabled && '--btn--disabled', disabled && "bx--btn--disabled",
hasIconOnly && '--btn--icon-only', hasIconOnly && "bx--btn--icon-only",
hasIconOnly && '--tooltip__trigger', hasIconOnly && "bx--tooltip__trigger",
hasIconOnly && '--tooltip--a11y', hasIconOnly && "bx--tooltip--a11y",
hasIconOnly && tooltipPosition && `--tooltip--${tooltipPosition}`, hasIconOnly && tooltipPosition && `bx--tooltip--${tooltipPosition}`,
hasIconOnly && tooltipAlignment && `--tooltip--align-${tooltipAlignment}`, hasIconOnly &&
className tooltipAlignment &&
) `bx--tooltip--align-${tooltipAlignment}`,
$$restProps.class
]
.filter(Boolean)
.join(" ")
}; };
</script> </script>
@ -52,30 +51,42 @@
<slot props={buttonProps} /> <slot props={buttonProps} />
{:else if href && !disabled} {:else if href && !disabled}
<!-- svelte-ignore a11y-missing-attribute --> <!-- svelte-ignore a11y-missing-attribute -->
<a {...buttonProps} on:click on:mouseover on:mouseenter on:mouseleave> <a
bind:this={ref}
{...buttonProps}
on:click
on:mouseover
on:mouseenter
on:mouseleave>
{#if hasIconOnly} {#if hasIconOnly}
<span class={cx('--assistive-text')}>{iconDescription}</span> <span class:bx--assistive-text={true}>{iconDescription}</span>
{/if} {/if}
<slot /> <slot />
{#if icon} {#if icon}
<svelte:component <svelte:component
this={icon} this={icon}
aria-hidden="true" aria-hidden="true"
class={cx('--btn__icon')} class="bx--btn__icon"
aria-label={iconDescription} /> aria-label={iconDescription} />
{/if} {/if}
</a> </a>
{:else} {:else}
<button {...buttonProps} bind:this={buttonRef} on:click on:mouseover on:mouseenter on:mouseleave> <button
bind:this={ref}
{...buttonProps}
on:click
on:mouseover
on:mouseenter
on:mouseleave>
{#if hasIconOnly} {#if hasIconOnly}
<span class={cx('--assistive-text')}>{iconDescription}</span> <span class:bx--assistive-text={true}>{iconDescription}</span>
{/if} {/if}
<slot /> <slot />
{#if icon} {#if icon}
<svelte:component <svelte:component
this={icon} this={icon}
aria-hidden="true" aria-hidden="true"
class={cx('--btn__icon')} class="bx--btn__icon"
aria-label={iconDescription} /> aria-label={iconDescription} />
{/if} {/if}
</button> </button>

View file

@ -1,4 +1,2 @@
import Button from './Button.svelte'; export { default as Button } from "./Button.svelte";
export { default as ButtonSkeleton } from "./Button.Skeleton.svelte";
export default Button;
export { default as ButtonSkeleton } from './Button.Skeleton.svelte';

View file

@ -1,17 +1,11 @@
<script>
let className = undefined;
export { className as class };
export let style = undefined;
import { cx } from '../../lib';
</script>
<div <div
class:bx--form-item={true}
class:bx--checkbox-wrapper={true}
class:bx--checkbox-label={true}
{...$$restProps}
on:click on:click
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave>
class={cx('--form-item', '--checkbox-wrapper', '--checkbox-label', className)} <span class:bx--checkbox-label-text={true} class:bx--skeleton={true} />
{style}>
<span class={cx('--checkbox-label-text', '--skeleton')} />
</div> </div>

View file

@ -1,31 +1,40 @@
<script> <script>
export let story = undefined; export let story = undefined;
import { cx } from '../../lib'; import Checkbox from "./Checkbox.svelte";
import Layout from '../../internal/ui/Layout.svelte'; import CheckboxSkeleton from "./Checkbox.Skeleton.svelte";
import Checkbox from './Checkbox.svelte';
import CheckboxSkeleton from './Checkbox.Skeleton.svelte';
const { labelText, indeterminate, disabled, hideLabel, wrapperClassName } = $$props; const {
const checkboxProps = { labelText, indeterminate, disabled, hideLabel, wrapperClassName }; labelText,
indeterminate,
disabled,
hideLabel,
wrapperClassName
} = $$props;
const checkboxProps = {
labelText,
indeterminate,
disabled,
hideLabel,
wrapperClassName
};
let checked = true; let checked = true;
</script> </script>
<Layout> {#if story === 'skeleton'}
{#if story === 'skeleton'}
<div> <div>
<CheckboxSkeleton /> <CheckboxSkeleton />
</div> </div>
{:else if story === 'unchecked'} {:else if story === 'unchecked'}
<fieldset class={cx('--fieldset')}> <fieldset class="bx--fieldset">
<legend class={cx('--label')}>Checkbox heading</legend> <legend class="bx--label">Checkbox heading</legend>
<Checkbox {...checkboxProps} id="checkbox-label-1" /> <Checkbox {...checkboxProps} id="checkbox-label-1" />
<Checkbox {...checkboxProps} id="checkbox-label-2" /> <Checkbox {...checkboxProps} id="checkbox-label-2" />
</fieldset> </fieldset>
{:else} {:else}
<fieldset class={cx('--fieldset')}> <fieldset class="bx--fieldset">
<legend class={cx('--label')}>Checkbox heading</legend> <legend class="bx--label">Checkbox heading</legend>
<Checkbox <Checkbox
{...checkboxProps} {...checkboxProps}
id="checkbox-label-1" id="checkbox-label-1"
@ -35,5 +44,4 @@
}} /> }} />
<Checkbox {...checkboxProps} id="checkbox-label-2" checked /> <Checkbox {...checkboxProps} id="checkbox-label-2" checked />
</fieldset> </fieldset>
{/if} {/if}
</Layout>

View file

@ -1,27 +1,27 @@
import { withKnobs, boolean, text } from '@storybook/addon-knobs'; import { withKnobs, boolean, text } from "@storybook/addon-knobs";
import Component from './Checkbox.Story.svelte'; import Component from "./Checkbox.Story.svelte";
export default { title: 'Checkbox', decorators: [withKnobs] }; export default { title: "Checkbox", decorators: [withKnobs] };
export const Checked = () => ({ export const Checked = () => ({
Component, Component,
props: { props: {
labelText: text('Label text (labelText)', 'Checkbox label'), labelText: text("Label text (labelText)", "Checkbox label"),
indeterminate: boolean('Intermediate (indeterminate)', false), indeterminate: boolean("Intermediate (indeterminate)", false),
disabled: boolean('Disabled (disabled)', false), disabled: boolean("Disabled (disabled)", false),
hideLabel: boolean('Hide label (hideLabel)', false) hideLabel: boolean("Hide label (hideLabel)", false),
} },
}); });
export const Unchecked = () => ({ export const Unchecked = () => ({
Component, Component,
props: { props: {
story: 'unchecked', story: "unchecked",
labelText: text('Label text (labelText)', 'Checkbox label'), labelText: text("Label text (labelText)", "Checkbox label"),
indeterminate: boolean('Intermediate (indeterminate)', false), indeterminate: boolean("Intermediate (indeterminate)", false),
disabled: boolean('Disabled (disabled)', false), disabled: boolean("Disabled (disabled)", false),
hideLabel: boolean('Hide label (hideLabel)', false) hideLabel: boolean("Hide label (hideLabel)", false),
} },
}); });
export const Skeleton = () => ({ Component, props: { story: 'skeleton' } }); export const Skeleton = () => ({ Component, props: { story: "skeleton" } });

View file

@ -1,48 +1,49 @@
<script> <script>
let className = undefined; export let indeterminate = false;
export { className as class }; export let readonly = false;
export let checked = false; export let checked = false;
export let disabled = false; export let disabled = false;
export let labelText = "";
export let hideLabel = false; export let hideLabel = false;
export let id = Math.random(); export let id = "ccs-" + Math.random().toString(36);
export let indeterminate = false; export let name = "";
export let labelText = ''; export let title = undefined;
export let name = ''; export let ref = null;
export let readonly = false;
export let style = undefined;
export let title = '';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from "svelte";
import { cx } from '../../lib';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
$: { $: dispatch("check", checked);
dispatch('check', checked);
}
</script> </script>
<div <div
class:bx--form-item={true}
class:bx--checkbox-wrapper={true}
{...$$restProps}
on:click on:click
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave>
class={cx('--form-item', '--checkbox-wrapper', className)}
{style}>
<input <input
bind:this={ref}
type="checkbox" type="checkbox"
class={cx('--checkbox')} {checked}
{disabled}
{id}
{indeterminate}
{name}
{readonly}
class:bx--checkbox={true}
on:change on:change
on:change={() => { on:change={() => {
checked = !checked; checked = !checked;
}} }} />
{indeterminate} <label class:bx--checkbox-label={true} for={id} {title}>
{disabled} <span
{checked} class:bx--checkbox-label-text={true}
{name} class:bx--visually-hidden={hideLabel}>
{id} {labelText}
{readonly} /> </span>
<label class={cx('--checkbox-label')} for={id} title={title || undefined}>
<span class={cx('--checkbox-label-text', hideLabel && '--visually-hidden')}>{labelText}</span>
</label> </label>
</div> </div>

View file

@ -1,4 +1,2 @@
import Checkbox from './Checkbox.svelte'; export { default as Checkbox } from "./Checkbox.svelte";
export { default as CheckboxSkeleton } from "./Checkbox.Skeleton.svelte";
export default Checkbox;
export { default as CheckboxSkeleton } from './Checkbox.Skeleton.svelte';

View file

@ -1,20 +1,18 @@
<script> <script>
let className = undefined; export let type = "single"; // "single" | "multi"
export { className as class };
export let style = undefined;
export let type = 'single';
import { cx } from '../../lib';
</script> </script>
<div <div
class:bx--snippet={true}
class:bx--skeleton={true}
class:bx--snippet--single={type === 'single'}
class:bx--snippet--multi={type === 'multi'}
{...$$restProps}
on:click on:click
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave>
class={cx('--snippet', '--skeleton', type === 'single' && '--snippet--single', type === 'multi' && '--snippet--multi', className)} <div class:bx--snippet-container={true}>
{style}>
<div class={cx('--snippet-container')}>
{#if type === 'single'} {#if type === 'single'}
<span /> <span />
{:else if type === 'multi'} {:else if type === 'multi'}

View file

@ -1,31 +1,24 @@
<script> <script>
export let story = undefined; export let story = undefined;
const { light, feedback, copyLabel, copyButtonDescription, showLessText, showMoreText } = $$props;
import Layout from '../../internal/ui/Layout.svelte'; import CodeSnippet from "./CodeSnippet.svelte";
import CodeSnippet from './CodeSnippet.svelte'; import CodeSnippetSkeleton from "./CodeSnippet.Skeleton.svelte";
import CodeSnippetSkeleton from './CodeSnippet.Skeleton.svelte';
</script> </script>
<Layout> <div>
<div>
{#if story === 'skeleton'} {#if story === 'skeleton'}
<div style="width: 800px"> <div style="width: 800px">
<CodeSnippetSkeleton type="single" style="margin-bottom: 8px" /> <CodeSnippetSkeleton type="single" style="margin-bottom: 8px" />
<CodeSnippetSkeleton type="multi" /> <CodeSnippetSkeleton type="multi" />
</div> </div>
{:else if story === 'inline'} {:else if story === 'inline'}
<CodeSnippet type="inline" {light} {feedback} {copyLabel}>{'node -v'}</CodeSnippet> <CodeSnippet {...$$restProps} type="inline">{'node -v'}</CodeSnippet>
{:else if story === 'single line'} {:else if story === 'single line'}
<CodeSnippet <CodeSnippet {...$$restProps} type="single">
type="single"
{feedback}
{copyButtonDescription}
aria-label={$$props['aria-label']}>
{'node -v Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, veritatis voluptate id incidunt molestiae officia possimus, quasi itaque alias, architecto hic, dicta fugit? Debitis delectus quidem explicabo vitae fuga laboriosam!'} {'node -v Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, veritatis voluptate id incidunt molestiae officia possimus, quasi itaque alias, architecto hic, dicta fugit? Debitis delectus quidem explicabo vitae fuga laboriosam!'}
</CodeSnippet> </CodeSnippet>
{:else if story === 'multi line'} {:else if story === 'multi line'}
<CodeSnippet type="multi" {feedback} {showLessText} {showMoreText}> <CodeSnippet {...$$restProps} type="multi">
{`@mixin grid-container { {`@mixin grid-container {
width: 100%; width: 100%;
padding-right: padding(mobile); padding-right: padding(mobile);
@ -43,11 +36,10 @@ $z-indexes: (
dropdown : 7000, dropdown : 7000,
header : 6000, header : 6000,
footer : 5000, footer : 5000,
hidden : - 1, hidden : -1,
overflowHidden: - 1, overflowHidden: -1,
floating: 10000 floating: 10000
);`} );`}
</CodeSnippet> </CodeSnippet>
{/if} {/if}
</div> </div>
</Layout>

View file

@ -1,39 +1,52 @@
import { withKnobs, boolean, text } from '@storybook/addon-knobs'; import { withKnobs, boolean, text } from "@storybook/addon-knobs";
import Component from './CodeSnippet.Story.svelte'; import Component from "./CodeSnippet.Story.svelte";
export default { title: 'CodeSnippet', decorators: [withKnobs] }; export default { title: "CodeSnippet", decorators: [withKnobs] };
export const Inline = () => ({ export const Inline = () => ({
Component, Component,
props: { props: {
story: 'inline', story: "inline",
light: boolean('Light variant (light)', false), light: boolean("Light variant (light)", false),
feedback: text('Feedback text (feedback)', 'Feedback Enabled 👍'), feedback: text("Feedback text (feedback)", "Feedback Enabled 👍"),
copyLabel: text('ARIA label for the snippet/copy button (copyLabel)', 'copyable code snippet') copyLabel: text(
} "ARIA label for the snippet/copy button (copyLabel)",
"copyable code snippet"
),
},
}); });
export const SingleLine = () => ({ export const SingleLine = () => ({
Component, Component,
props: { props: {
story: 'single line', story: "single line",
feedback: text('Feedback text (feedback)', 'Feedback Enabled 👍'), light: boolean("Light variant (light)", false),
feedback: text("Feedback text (feedback)", "Feedback Enabled 👍"),
copyButtonDescription: text( copyButtonDescription: text(
'Copy icon description (copyButtonDescription)', "Copy icon description (copyButtonDescription)",
'copyable code snippet' "copyable code snippet"
), ),
'aria-label': text('ARIA label of the container (ariaLabel)', 'Container label') "aria-label": text(
} "ARIA label of the container (ariaLabel)",
"Container label"
),
},
}); });
export const MultiLine = () => ({ export const MultiLine = () => ({
Component, Component,
props: { props: {
story: 'multi line', story: "multi line",
feedback: text('Feedback text (feedback)', 'Feedback Enabled 👍'), feedback: text("Feedback text (feedback)", "Feedback Enabled 👍"),
showMoreText: text('Text for "show more" button (showMoreText)', 'Show more'), showMoreText: text(
showLessText: text('Text for "show less" button (showLessText)', 'Show less') 'Text for "show more" button (showMoreText)',
} "Show more"
),
showLessText: text(
'Text for "show less" button (showLessText)',
"Show less"
),
},
}); });
export const Skeleton = () => ({ Component, props: { story: 'skeleton' } }); export const Skeleton = () => ({ Component, props: { story: "skeleton" } });

View file

@ -1,97 +1,95 @@
<script> <script>
let className = undefined; export let type = "single"; // "single" | "inline" | "multi"
export { className as class };
export let code = undefined; export let code = undefined;
export let light = false;
export let skeleton = false;
export let copyButtonDescription = undefined; export let copyButtonDescription = undefined;
export let copyLabel = undefined; export let copyLabel = undefined;
export let feedback = undefined; export let feedback = "Copied!";
export let feedbackTimeout = undefined; export let feedbackTimeout = 2000;
export let id = Math.random(); export let showLessText = "Show less";
export let light = false; export let showMoreText = "Show more";
export let showLessText = 'Show less'; export let id = "ccs-" + Math.random().toString(36);
export let showMoreText = 'Show more'; export let ref = null;
export let skeleton = false;
export let style = undefined;
export let type = 'single';
import { afterUpdate } from 'svelte'; import { afterUpdate } from "svelte";
import ChevronDown16 from 'carbon-icons-svelte/lib/ChevronDown16'; import ChevronDown16 from "carbon-icons-svelte/lib/ChevronDown16";
import { cx } from '../../lib'; import { Button } from "../Button";
import Button from '../Button'; import { Copy } from "../Copy";
import Copy from '../Copy'; import { CopyButton } from "../CopyButton";
import CopyButton from '../CopyButton'; import CodeSnippetSkeleton from "./CodeSnippet.Skeleton.svelte";
import CodeSnippetSkeleton from './CodeSnippet.Skeleton.svelte';
let codeRef = undefined; $: showMoreLess = false;
let expanded = false; $: expanded = false;
let showMoreLess = false; $: expandText = expanded ? showLessText : showMoreText;
afterUpdate(() => { afterUpdate(() => {
if (type === 'multi' && codeRef) { if (type === "multi" && ref) {
showMoreLess = codeRef.getBoundingClientRect().height > 255; showMoreLess = ref.getBoundingClientRect().height > 255;
} }
}); });
$: expandText = expanded ? showLessText : showMoreText;
</script> </script>
{#if skeleton} {#if skeleton}
<CodeSnippetSkeleton class={className} {type} {style} /> <CodeSnippetSkeleton {type} {...$$restProps} />
{/if} {:else}
{#if !skeleton}
{#if type === 'inline'} {#if type === 'inline'}
<Copy <Copy
aria-label={$$props['aria-label'] || copyLabel} aria-label={copyLabel}
aria-describedby={id} aria-describedby={id}
class={cx('--snippet', type && `--snippet--${type}`, type === 'inline' && '--btn--copy', expanded && '--snippet--expand', light && '--snippet--light', className)} class="bx--snippet {type && `bx--snippet--${type}`}
on:click {type === 'inline' && 'bx--btn--copy'}
{expanded && 'bx--snippet--expand'}
{light && 'bx--snippet--light'}"
{...$$restProps}
on:clicks
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave>
{feedback}
{feedbackTimeout}
{style}>
<code {id}> <code {id}>
<slot>{code}</slot> <slot>{code}</slot>
</code> </code>
</Copy> </Copy>
{:else} {:else}
<div <div
class:bx--snippet={true}
class={type && `bx--snippet--${type}`}
class:bx--btn--copy={type === 'inline'}
class:bx--snippet--expand={expanded}
class:bx--snippet--light={light}
{...$$restProps}
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave>
class={cx('--snippet', type && `--snippet--${type}`, type === 'inline' && '--btn--copy', expanded && '--snippet--expand', light && '--snippet--light', className)}
{style}>
<div <div
role="textbox" role={type === 'single' ? 'textbox' : undefined}
tabindex="0" tabindex={type === 'single' ? '0' : undefined}
class={cx('--snippet-container')} class:bx--snippet-container={true}
aria-label={$$props['aria-label'] || copyLabel || 'code-snippet'}> aria-label={$$restProps['aria-label'] || copyLabel || 'code-snippet'}>
<code> <code>
<pre bind:this={codeRef}> <pre bind:this={ref}>
<slot>{code}</slot> <slot>{code}</slot>
</pre> </pre>
</code> </code>
</div> </div>
<CopyButton <CopyButton
iconDescription={copyButtonDescription}
class={cx('--snippet-button')}
on:click
{feedback} {feedback}
{feedbackTimeout} /> {feedbackTimeout}
iconDescription={copyButtonDescription}
on:click
on:animationend />
{#if showMoreLess} {#if showMoreLess}
<Button <Button
kind="ghost" kind="ghost"
size="small" size="small"
class={cx('--snippet-btn--expand')} class="bx--snippet-btn--expand"
on:click={() => { on:click={() => {
expanded = !expanded; expanded = !expanded;
}}> }}>
<span class={cx('--snippet-btn--text')}>{expandText}</span> <span class:bx--snippet-btn--text={true}>{expandText}</span>
<ChevronDown16 <ChevronDown16
aria-label={expandText} class="bx--icon-chevron--down bx--snippet__icon"
class={cx('--icon-chevron--down', '--snippet__icon')} /> aria-label={expandText} />
</Button> </Button>
{/if} {/if}
</div> </div>

View file

@ -1,4 +1,2 @@
import CodeSnippet from './CodeSnippet.svelte'; export { default as CodeSnippet } from "./CodeSnippet.svelte";
export { default as CodeSnippetSkeleton } from "./CodeSnippet.Skeleton.svelte";
export default CodeSnippet;
export { default as CodeSnippetSkeleton } from './CodeSnippet.Skeleton.svelte';

View file

@ -1,23 +1,25 @@
<script> <script>
import Layout from '../../internal/ui/Layout.svelte'; import { ToggleSmall } from "../ToggleSmall";
import ToggleSmall from '../ToggleSmall'; import { Button } from "../Button";
import Button from '../Button'; import ComboBox from "./ComboBox.svelte";
import ComboBox from './ComboBox.svelte';
let items = [ let items = [
{ id: 'option-0', text: 'Option 1' }, { id: "option-0", text: "Option 1" },
{ id: 'option-1', text: 'Option 2' }, { id: "option-1", text: "Option 2" },
{ id: 'option-2', text: 'Option 3' }, { id: "option-2", text: "Option 3" },
{ id: 'option-3', text: 'Option 4' }, { id: "option-3", text: "Option 4" },
{ {
id: 'option-4', id: "option-4",
text: 'An example option that is really long to show what should be done to handle long text' text:
"An example option that is really long to show what should be done to handle long text"
} }
]; ];
let toggled = false; $: toggled = false;
let value = undefined; $: value = undefined;
let selectedIndex = -1; $: selectedIndex = -1;
$: ref = null;
$: ref && ref.focus();
function shouldFilterItem(item, value) { function shouldFilterItem(item, value) {
if (!toggled || !value) { if (!toggled || !value) {
@ -28,17 +30,19 @@
} }
</script> </script>
<Layout> <p>Currently, this component does not support items as slots.</p>
<p>Currently, this component does not support items as slots.</p> <p>
<p>
<code>items</code> <code>items</code>
must be an array of objects; mandatory fields are `id` and `text`. must be an array of objects; mandatory fields are `id` and `text`.
</p> </p>
<pre style="margin-top: 1rem;"> <pre style="margin-top: 1rem;">
<code>{'items = Array<{ id: string; text: string; }>'}</code> <code>{'items = Array<{ id: string; text: string; }>'}</code>
</pre> </pre>
<div style="margin-top: 2rem;"> <div style="margin-top: 2rem;">
<ToggleSmall labelText="Enable filtering" bind:toggled /> <ToggleSmall
labelText="Enable filtering"
bind:toggled
style="margin-top: 1rem;" />
<Button <Button
size="small" size="small"
on:click={() => { on:click={() => {
@ -46,8 +50,14 @@
}}> }}>
Set item to 'Option 2' Set item to 'Option 2'
</Button> </Button>
</div> </div>
<div style="width: 300px; margin-top: 2rem;"> <div style="width: 300px; margin-top: 2rem;">
<ComboBox {...$$props} id="combobox" bind:value bind:selectedIndex {items} {shouldFilterItem} /> <ComboBox
</div> {...$$props}
</Layout> id="combobox"
bind:ref
bind:value
bind:selectedIndex
{items}
{shouldFilterItem} />
</div>

View file

@ -1,25 +1,27 @@
import { withKnobs, select, boolean, text } from '@storybook/addon-knobs'; import { withKnobs, select, boolean, text } from "@storybook/addon-knobs";
import Component from './ComboBox.Story.svelte'; import Component from "./ComboBox.Story.svelte";
export default { title: 'ComboBox', decorators: [withKnobs] }; export default { title: "ComboBox", decorators: [withKnobs] };
const sizes = { const sizes = {
'Extra large size (xl)': 'xl', "Extra large size (xl)": "xl",
'Regular size (lg)': '', "Regular size (lg)": "",
'Small size (sm)': 'sm' "Small size (sm)": "sm",
}; };
export const Default = () => ({ export const Default = () => ({
Component, Component,
props: { props: {
size: select('Field size (size)', sizes, ''), size: select("Field size (size)", sizes, ""),
placeholder: text('Placeholder text (placeholder)', 'Filter...'), placeholder: text("Placeholder text (placeholder)", "Filter..."),
titleText: text('Title (titleText)', 'Combobox title'), titleText: text("Title (titleText)", "Combobox title"),
helperText: text('Helper text (helperText)', 'Optional helper text here'), light: boolean("Light (light)", false),
light: boolean('Light (light)', false), disabled: boolean("Disabled (disabled)", false),
disabled: boolean('Disabled (disabled)', false), invalid: boolean("Invalid (invalid)", false),
invalid: boolean('Invalid (invalid)', false), invalidText: text(
invalidText: text('Invalid text (invalidText)', 'A valid value is required'), "Invalid text (invalidText)",
name: 'combo-box-name' "A valid value is required"
} ),
name: "combo-box-name",
},
}); });

View file

@ -1,39 +1,36 @@
<script> <script>
let className = undefined;
export { className as class };
export let disabled = false; export let disabled = false;
export let helperText = ''; export let helperText = "";
export let id = Math.random(); export let id = "ccs-" + Math.random().toString(36);
export let invalid = false; export let invalid = false;
export let invalidText = ''; export let invalidText = "";
export let items = []; export let items = [];
export let itemToString = item => item.text || item.id; export let itemToString = item => item.text || item.id;
export let light = false; export let light = false;
export let open = false; export let open = false;
export let placeholder = ''; export let placeholder = "";
export let selectedIndex = -1; export let selectedIndex = -1;
export let shouldFilterItem = () => true; export let shouldFilterItem = () => true;
export let size = undefined; export let size = undefined;
export let style = undefined; export let titleText = "";
export let titleText = '';
export let translateWithId = undefined; export let translateWithId = undefined;
export let value = ''; export let value = "";
export let name = undefined; export let name = undefined;
export let ref = null;
import { afterUpdate } from 'svelte'; import { afterUpdate } from "svelte";
import WarningFilled16 from 'carbon-icons-svelte/lib/WarningFilled16'; import WarningFilled16 from "carbon-icons-svelte/lib/WarningFilled16";
import { cx } from '../../lib'; import {
import ListBox, { ListBox,
ListBoxField, ListBoxField,
ListBoxMenu, ListBoxMenu,
ListBoxMenuIcon, ListBoxMenuIcon,
ListBoxMenuItem, ListBoxMenuItem,
ListBoxSelection ListBoxSelection
} from '../ListBox'; } from "../ListBox";
let selectedId = undefined; let selectedId = undefined;
let inputRef = undefined; let inputValue = "";
let inputValue = '';
let highlightedIndex = -1; let highlightedIndex = -1;
function change(direction) { function change(direction) {
@ -50,18 +47,20 @@
afterUpdate(() => { afterUpdate(() => {
if (open) { if (open) {
inputRef.focus(); ref.focus();
filteredItems = items.filter(item => shouldFilterItem(item, value)); filteredItems = items.filter(item => shouldFilterItem(item, value));
} else { } else {
highlightedIndex = -1; highlightedIndex = -1;
inputValue = selectedItem ? selectedItem.text : ''; inputValue = selectedItem ? selectedItem.text : "";
} }
}); });
$: ariaLabel = $$props['aria-label'] || 'Choose an item'; $: ariaLabel = $$props["aria-label"] || "Choose an item";
$: menuId = `menu-${id}`; $: menuId = `menu-${id}`;
$: comboId = `combo-${id}`; $: comboId = `combo-${id}`;
$: highlightedId = items[highlightedIndex] ? items[highlightedIndex].id : undefined; $: highlightedId = items[highlightedIndex]
? items[highlightedIndex].id
: undefined;
$: filteredItems = items.filter(item => shouldFilterItem(item, value)); $: filteredItems = items.filter(item => shouldFilterItem(item, value));
$: selectedItem = items[selectedIndex]; $: selectedItem = items[selectedIndex];
$: inputValue = selectedItem ? selectedItem.text : undefined; $: inputValue = selectedItem ? selectedItem.text : undefined;
@ -70,22 +69,26 @@
<svelte:body <svelte:body
on:click={({ target }) => { on:click={({ target }) => {
if (open && inputRef && !inputRef.contains(target)) { if (open && ref && !ref.contains(target)) {
open = false; open = false;
} }
}} /> }} />
<div class={cx('--list-box__wrapper', className)} {style}> <div class:bx--list-box__wrapper={true} {...$$restProps}>
{#if titleText} {#if titleText}
<label class={cx('--label', disabled && '--label--disabled')} for={id}>{titleText}</label> <label for={id} class:bx--label={true} class:bx--label--disabled={disabled}>
{titleText}
</label>
{/if} {/if}
{#if helperText} {#if helperText}
<div class={cx('--form__helper-text', disabled && '--form__helper-text--disabled')}> <div
class:bx--form__helper-text={true}
class:bx--form__helper-text--disabled={disabled}>
{helperText} {helperText}
</div> </div>
{/if} {/if}
<ListBox <ListBox
class={cx('--combo-box')} class="bx--combo-box"
id={comboId} id={comboId}
aria-label={ariaLabel} aria-label={ariaLabel}
{disabled} {disabled}
@ -105,7 +108,7 @@
{disabled} {disabled}
{translateWithId}> {translateWithId}>
<input <input
bind:this={inputRef} bind:this={ref}
tabindex="0" tabindex="0"
autocomplete="off" autocomplete="off"
aria-autocomplete="list" aria-autocomplete="list"
@ -115,7 +118,8 @@
aria-disabled={disabled} aria-disabled={disabled}
aria-controls={open ? menuId : undefined} aria-controls={open ? menuId : undefined}
aria-owns={open ? menuId : undefined} aria-owns={open ? menuId : undefined}
class={cx('--text-input', inputValue === '' && '--text-input--empty')} class:bx--text-input={true}
class:bx--text-input--empty={inputValue === ''}
on:input={({ target }) => { on:input={({ target }) => {
inputValue = target.value; inputValue = target.value;
}} }}
@ -139,7 +143,7 @@
on:blur on:blur
on:blur={({ relatedTarget }) => { on:blur={({ relatedTarget }) => {
if (relatedTarget && relatedTarget.getAttribute('role') !== 'button') { if (relatedTarget && relatedTarget.getAttribute('role') !== 'button') {
inputRef.focus(); ref.focus();
} }
}} }}
{disabled} {disabled}
@ -147,7 +151,7 @@
{id} {id}
value={inputValue} /> value={inputValue} />
{#if invalid} {#if invalid}
<WarningFilled16 class={cx('--list-box__invalid-icon')} /> <WarningFilled16 class="bx--list-box__invalid-icon" />
{/if} {/if}
{#if inputValue} {#if inputValue}
<ListBoxSelection <ListBoxSelection
@ -175,7 +179,9 @@
highlighted={highlightedIndex === i || selectedIndex === i} highlighted={highlightedIndex === i || selectedIndex === i}
on:click={() => { on:click={() => {
selectedId = item.id; selectedId = item.id;
selectedIndex = items.map(({ id }) => id).indexOf(filteredItems[i].id); selectedIndex = items
.map(({ id }) => id)
.indexOf(filteredItems[i].id);
open = false; open = false;
}} }}
on:mouseenter={() => { on:mouseenter={() => {

View file

@ -1,3 +1 @@
import ComboBox from './ComboBox.svelte'; export { default as ComboBox } from "./ComboBox.svelte";
export default ComboBox;

View file

@ -2,14 +2,14 @@
export let story = undefined; export let story = undefined;
const { modalBody } = $$props; const { modalBody } = $$props;
import Layout from '../../internal/ui/Layout.svelte'; import Layout from "../../internal/ui/Layout.svelte";
import Button from '../Button'; import { Button } from "../Button";
import ComposedModal from './ComposedModal.svelte'; import ComposedModal from "./ComposedModal.svelte";
import ModalHeader from './ModalHeader.svelte'; import ModalHeader from "./ModalHeader.svelte";
import ModalBody from './ModalBody.svelte'; import ModalBody from "./ModalBody.svelte";
import ModalFooter from './ModalFooter.svelte'; import ModalFooter from "./ModalFooter.svelte";
let open = false; $: open = false;
</script> </script>
<Layout> <Layout>
@ -19,13 +19,17 @@
<ModalBody <ModalBody
{...$$props.modalBody} {...$$props.modalBody}
aria-label={modalBody.hasScrollingContent ? 'Modal content' : undefined}> aria-label={modalBody.hasScrollingContent ? 'Modal content' : undefined}>
<p>Please see ModalWrapper for more examples and demo of the functionality.</p> <p>
Please see ModalWrapper for more examples and demo of the
functionality.
</p>
{#if modalBody.hasScrollingContent} {#if modalBody.hasScrollingContent}
<p> <p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna, accumsan augue. Phasellus consequat augue vitae tellus tincidunt
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam posuere. Curabitur justo urna, consectetur vel elit iaculis,
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '} ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie
tellus. Quisque consectetur non risus eu rutrum.{' '}
</p> </p>
{/if} {/if}
</ModalBody> </ModalBody>
@ -41,28 +45,39 @@
<ModalBody <ModalBody
{...$$props.modalBody} {...$$props.modalBody}
aria-label={modalBody.hasScrollingContent ? 'Modal content' : undefined}> aria-label={modalBody.hasScrollingContent ? 'Modal content' : undefined}>
<p>Please see ModalWrapper for more examples and demo of the functionality.</p> <p>
Please see ModalWrapper for more examples and demo of the
functionality.
</p>
{#if modalBody.hasScrollingContent} {#if modalBody.hasScrollingContent}
<p> <p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna, accumsan augue. Phasellus consequat augue vitae tellus tincidunt
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam posuere. Curabitur justo urna, consectetur vel elit iaculis,
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '} ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie
tellus. Quisque consectetur non risus eu rutrum.{' '}
</p> </p>
{/if} {/if}
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button kind="secondary">Cancel</Button> <Button kind="secondary">Cancel</Button>
<Button kind={$$props.composedModal.danger ? 'danger' : 'primary'}>Primary</Button> <Button kind={$$props.composedModal.danger ? 'danger' : 'primary'}>
Primary
</Button>
</ModalFooter> </ModalFooter>
</ComposedModal> </ComposedModal>
{/if} {/if}
{#if story === 'title'} {#if story === 'title'}
<ComposedModal {...$$props.composedModal} open on:close={() => {}} on:submit={() => {}}> <ComposedModal
{...$$props.composedModal}
open
on:close={() => {}}
on:submit={() => {}}>
<ModalHeader <ModalHeader
{...$$props.modalHeader} {...$$props.modalHeader}
title="Passive modal title as the message. Should be direct and 3 lines or less." /> title="Passive modal title as the message. Should be direct and 3 lines
or less." />
<ModalBody {...$$props.modalBody} /> <ModalBody {...$$props.modalBody} />
<ModalFooter {...$$props.modalFooter} /> <ModalFooter {...$$props.modalFooter} />
</ComposedModal> </ComposedModal>
@ -77,55 +92,68 @@
Launch composed modal Launch composed modal
</Button> </Button>
</div> </div>
<ComposedModal {...$$props.composedModal} {open} on:close={() => (open = false)}> <ComposedModal
{...$$props.composedModal}
{open}
on:close={() => (open = false)}>
<ModalHeader {...$$props.modalHeader} /> <ModalHeader {...$$props.modalHeader} />
<ModalBody <ModalBody
{...$$props.modalBody} {...$$props.modalBody}
aria-label={modalBody.hasScrollingContent ? 'Modal content' : undefined}> aria-label={modalBody.hasScrollingContent ? 'Modal content' : undefined}>
<p>Please see ModalWrapper for more examples and demo of the functionality.</p> <p>
Please see ModalWrapper for more examples and demo of the
functionality.
</p>
{#if modalBody.hasScrollingContent} {#if modalBody.hasScrollingContent}
<p> <p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna, accumsan augue. Phasellus consequat augue vitae tellus tincidunt
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam posuere. Curabitur justo urna, consectetur vel elit iaculis,
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '} ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie
tellus. Quisque consectetur non risus eu rutrum.{' '}
</p> </p>
<p> <p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna, accumsan augue. Phasellus consequat augue vitae tellus tincidunt
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam posuere. Curabitur justo urna, consectetur vel elit iaculis,
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '} ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie
tellus. Quisque consectetur non risus eu rutrum.{' '}
</p> </p>
<p> <p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna, accumsan augue. Phasellus consequat augue vitae tellus tincidunt
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam posuere. Curabitur justo urna, consectetur vel elit iaculis,
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '} ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie
tellus. Quisque consectetur non risus eu rutrum.{' '}
</p> </p>
<h3>Lorem ipsum</h3> <h3>Lorem ipsum</h3>
<p> <p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna, accumsan augue. Phasellus consequat augue vitae tellus tincidunt
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam posuere. Curabitur justo urna, consectetur vel elit iaculis,
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '} ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie
tellus. Quisque consectetur non risus eu rutrum.{' '}
</p> </p>
<p> <p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna, accumsan augue. Phasellus consequat augue vitae tellus tincidunt
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam posuere. Curabitur justo urna, consectetur vel elit iaculis,
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '} ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie
tellus. Quisque consectetur non risus eu rutrum.{' '}
</p> </p>
<p> <p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna, accumsan augue. Phasellus consequat augue vitae tellus tincidunt
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam posuere. Curabitur justo urna, consectetur vel elit iaculis,
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '} ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie
tellus. Quisque consectetur non risus eu rutrum.{' '}
</p> </p>
<p> <p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna, accumsan augue. Phasellus consequat augue vitae tellus tincidunt
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam posuere. Curabitur justo urna, consectetur vel elit iaculis,
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '} ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie
tellus. Quisque consectetur non risus eu rutrum.{' '}
</p> </p>
{/if} {/if}
</ModalBody> </ModalBody>

View file

@ -1,135 +1,177 @@
import { withKnobs, select, boolean, text } from '@storybook/addon-knobs'; import { withKnobs, select, boolean, text } from "@storybook/addon-knobs";
import Component from './ComposedModal.Story.svelte'; import Component from "./ComposedModal.Story.svelte";
export default { title: 'ComposedModal', decorators: [withKnobs] }; export default { title: "ComposedModal", decorators: [withKnobs] };
const sizes = { const sizes = {
Default: '', Default: "",
'Extra small (xs)': 'xs', "Extra small (xs)": "xs",
'Small (sm)': 'sm', "Small (sm)": "sm",
'Large (lg)': 'lg' "Large (lg)": "lg",
}; };
export const Default = () => ({ export const Default = () => ({
Component, Component,
props: { props: {
composedModal: { composedModal: {
open: boolean('Open (open in <ComposedModal>)', true), open: boolean("Open (open in <ComposedModal>)", true),
danger: boolean('Danger mode (danger)', false), danger: boolean("Danger mode (danger)", false),
selectorPrimaryFocus: text( selectorPrimaryFocus: text(
'Primary focus element selector (selectorPrimaryFocus)', "Primary focus element selector (selectorPrimaryFocus)",
'[data-modal-primary-focus]' "[data-modal-primary-focus]"
), ),
size: select('Size (size)', sizes, 'sm') size: select("Size (size)", sizes, "sm"),
}, },
modalHeader: { modalHeader: {
label: text('Optional Label (label in <ModalHeader>)', 'Optional Label'), label: text("Optional Label (label in <ModalHeader>)", "Optional Label"),
title: text('Optional title (title in <ModalHeader>)', 'Example'), title: text("Optional title (title in <ModalHeader>)", "Example"),
iconDescription: text('Close icon description (iconDescription in <ModalHeader>)', 'Close') iconDescription: text(
"Close icon description (iconDescription in <ModalHeader>)",
"Close"
),
}, },
modalBody: { modalBody: {
hasScrollingContent: boolean('Modal contains scrollable content (hasScrollingContent)', true), hasScrollingContent: boolean(
'aria-label': text('ARIA label for content', 'Example modal content') "Modal contains scrollable content (hasScrollingContent)",
true
),
"aria-label": text("ARIA label for content", "Example modal content"),
}, },
modalFooter: { modalFooter: {
primaryButtonText: text('Primary button text (primaryButtonText in <ModalFooter>)', 'Save'), primaryButtonText: text(
"Primary button text (primaryButtonText in <ModalFooter>)",
"Save"
),
primaryButtonDisabled: boolean( primaryButtonDisabled: boolean(
'Primary button disabled (primaryButtonDisabled in <ModalFooter>)', "Primary button disabled (primaryButtonDisabled in <ModalFooter>)",
false false
), ),
secondaryButtonText: text('Secondary button text (secondaryButtonText in <ModalFooter>)', '') secondaryButtonText: text(
} "Secondary button text (secondaryButtonText in <ModalFooter>)",
} ""
),
},
},
}); });
export const ChildNodes = () => ({ export const ChildNodes = () => ({
Component, Component,
props: { props: {
story: 'child nodes', story: "child nodes",
composedModal: { composedModal: {
open: boolean('Open (open in <ComposedModal>)', true), open: boolean("Open (open in <ComposedModal>)", true),
danger: boolean('Danger mode (danger)', false), danger: boolean("Danger mode (danger)", false),
selectorPrimaryFocus: text( selectorPrimaryFocus: text(
'Primary focus element selector (selectorPrimaryFocus)', "Primary focus element selector (selectorPrimaryFocus)",
'[data-modal-primary-focus]' "[data-modal-primary-focus]"
), ),
size: select('Size (size)', sizes, 'sm') size: select("Size (size)", sizes, "sm"),
}, },
modalHeader: { modalHeader: {
label: text('Optional Label (label in <ModalHeader>)', 'Optional Label'), label: text("Optional Label (label in <ModalHeader>)", "Optional Label"),
title: text('Optional title (title in <ModalHeader>)', 'Example'), title: text("Optional title (title in <ModalHeader>)", "Example"),
iconDescription: text('Close icon description (iconDescription in <ModalHeader>)', 'Close') iconDescription: text(
"Close icon description (iconDescription in <ModalHeader>)",
"Close"
),
}, },
modalBody: { modalBody: {
hasScrollingContent: boolean('Modal contains scrollable content (hasScrollingContent)', true), hasScrollingContent: boolean(
'aria-label': text('ARIA label for content', 'Example modal content') "Modal contains scrollable content (hasScrollingContent)",
true
),
"aria-label": text("ARIA label for content", "Example modal content"),
},
modalFooter: {},
}, },
modalFooter: {}
}
}); });
export const TitleOnly = () => ({ export const TitleOnly = () => ({
Component, Component,
props: { props: {
story: 'title', story: "title",
composedModal: { composedModal: {
open: boolean('Open (open in <ComposedModal>)', true), open: boolean("Open (open in <ComposedModal>)", true),
danger: boolean('Danger mode (danger)', false), danger: boolean("Danger mode (danger)", false),
selectorPrimaryFocus: text( selectorPrimaryFocus: text(
'Primary focus element selector (selectorPrimaryFocus)', "Primary focus element selector (selectorPrimaryFocus)",
'[data-modal-primary-focus]' "[data-modal-primary-focus]"
), ),
size: select('Size (size)', sizes, 'sm') size: select("Size (size)", sizes, "sm"),
}, },
modalHeader: { modalHeader: {
label: text('Optional Label (label in <ModalHeader>)', 'Optional Label'), label: text("Optional Label (label in <ModalHeader>)", "Optional Label"),
title: text('Optional title (title in <ModalHeader>)', 'Example'), title: text("Optional title (title in <ModalHeader>)", "Example"),
iconDescription: text('Close icon description (iconDescription in <ModalHeader>)', 'Close') iconDescription: text(
"Close icon description (iconDescription in <ModalHeader>)",
"Close"
),
}, },
modalBody: { modalBody: {
hasScrollingContent: boolean('Modal contains scrollable content (hasScrollingContent)', true), hasScrollingContent: boolean(
'aria-label': text('ARIA label for content', 'Example modal content') "Modal contains scrollable content (hasScrollingContent)",
true
),
"aria-label": text("ARIA label for content", "Example modal content"),
}, },
modalFooter: { modalFooter: {
primaryButtonText: text('Primary button text (primaryButtonText in <ModalFooter>)', 'Save'), primaryButtonText: text(
"Primary button text (primaryButtonText in <ModalFooter>)",
"Save"
),
primaryButtonDisabled: boolean( primaryButtonDisabled: boolean(
'Primary button disabled (primaryButtonDisabled in <ModalFooter>)', "Primary button disabled (primaryButtonDisabled in <ModalFooter>)",
false false
), ),
secondaryButtonText: text('Secondary button text (secondaryButtonText in <ModalFooter>)', '') secondaryButtonText: text(
} "Secondary button text (secondaryButtonText in <ModalFooter>)",
} ""
),
},
},
}); });
export const Trigger = () => ({ export const Trigger = () => ({
Component, Component,
props: { props: {
story: 'trigger', story: "trigger",
composedModal: { composedModal: {
open: boolean('Open (open in <ComposedModal>)', true), open: boolean("Open (open in <ComposedModal>)", true),
danger: boolean('Danger mode (danger)', false), danger: boolean("Danger mode (danger)", false),
selectorPrimaryFocus: text( selectorPrimaryFocus: text(
'Primary focus element selector (selectorPrimaryFocus)', "Primary focus element selector (selectorPrimaryFocus)",
'[data-modal-primary-focus]' "[data-modal-primary-focus]"
), ),
size: select('Size (size)', sizes, 'sm') size: select("Size (size)", sizes, "sm"),
}, },
modalHeader: { modalHeader: {
label: text('Optional Label (label in <ModalHeader>)', 'Optional Label'), label: text("Optional Label (label in <ModalHeader>)", "Optional Label"),
title: text('Optional title (title in <ModalHeader>)', 'Example'), title: text("Optional title (title in <ModalHeader>)", "Example"),
iconDescription: text('Close icon description (iconDescription in <ModalHeader>)', 'Close') iconDescription: text(
"Close icon description (iconDescription in <ModalHeader>)",
"Close"
),
}, },
modalBody: { modalBody: {
hasScrollingContent: boolean('Modal contains scrollable content (hasScrollingContent)', true), hasScrollingContent: boolean(
'aria-label': text('ARIA label for content', 'Example modal content') "Modal contains scrollable content (hasScrollingContent)",
true
),
"aria-label": text("ARIA label for content", "Example modal content"),
}, },
modalFooter: { modalFooter: {
primaryButtonText: text('Primary button text (primaryButtonText in <ModalFooter>)', 'Save'), primaryButtonText: text(
"Primary button text (primaryButtonText in <ModalFooter>)",
"Save"
),
primaryButtonDisabled: boolean( primaryButtonDisabled: boolean(
'Primary button disabled (primaryButtonDisabled in <ModalFooter>)', "Primary button disabled (primaryButtonDisabled in <ModalFooter>)",
false false
), ),
secondaryButtonText: text('Secondary button text (secondaryButtonText in <ModalFooter>)', '') secondaryButtonText: text(
} "Secondary button text (secondaryButtonText in <ModalFooter>)",
} ""
),
},
},
}); });

View file

@ -1,48 +1,51 @@
<script> <script>
let className = undefined;
export { className as class };
export let containerClass = undefined;
export let danger = false;
export let open = false; export let open = false;
export let selectorPrimaryFocus = '[data-modal-primary-focus]'; export let danger = false;
export let size = undefined; export let size = undefined; // "xs" | "sm" | "lg"
export let style = undefined; export let containerClass = "";
export let selectorPrimaryFocus = "[data-modal-primary-focus]";
export let ref = null;
import { createEventDispatcher, tick, setContext, onMount, afterUpdate } from 'svelte'; import {
import { cx } from '../../lib'; createEventDispatcher,
tick,
setContext,
onMount,
afterUpdate
} from "svelte";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
let buttonRef = undefined; let buttonRef = null;
let outerModal = undefined; let innerModal = null;
let innerModal = undefined;
let opened = false; setContext("ComposedModal", {
setContext('ComposedModal', {
closeModal: () => { closeModal: () => {
open = false; open = false;
}, },
submit: () => { submit: () => {
dispatch('submit'); dispatch("submit");
}, },
declareRef: ref => { declareRef: ref => {
buttonRef = ref; buttonRef = ref;
} }
}); });
// TODO: reuse in Modal
function focus(element) { function focus(element) {
const node = (element || innerModal).querySelector(selectorPrimaryFocus) || buttonRef; const node =
(element || innerModal).querySelector(selectorPrimaryFocus) || buttonRef;
node.focus(); node.focus();
} }
$: opened = false;
$: didOpen = open;
onMount(async () => { onMount(async () => {
await tick(); await tick();
focus(); focus();
return () => { return () => {
document.body.classList.remove(cx('--body--with-modal-open')); document.body.classList.remove("bx--body--with-modal-open");
}; };
}); });
@ -50,24 +53,25 @@
if (opened) { if (opened) {
if (!open) { if (!open) {
opened = false; opened = false;
dispatch('close'); dispatch("close");
document.body.classList.add(cx('--body--with-modal-open')); document.body.classList.add("bx--body--with-modal-open");
} }
} else if (open) { } else if (open) {
opened = true; opened = true;
dispatch('open'); dispatch("open");
document.body.classList.remove(cx('--body--with-modal-open')); document.body.classList.remove("bx--body--with-modal-open");
} }
}); });
$: didOpen = open;
</script> </script>
<div <div
bind:this={outerModal} bind:this={ref}
role="presentation" role="presentation"
tabindex="-1" tabindex="-1"
class={cx('--modal', open && 'is-visible', danger && '--modal--danger', className)} class:bx--modal={true}
class:is-visible={open}
class:bx--modal--danger={danger}
{...$$restProps}
on:click on:click
on:click={({ target }) => { on:click={({ target }) => {
if (!innerModal.contains(target)) { if (!innerModal.contains(target)) {
@ -83,11 +87,12 @@
focus(currentTarget); focus(currentTarget);
didOpen = false; didOpen = false;
} }
}} }}>
{style}>
<div <div
bind:this={innerModal} bind:this={innerModal}
class={cx('--modal-container', size && `--modal-container--${size}`, containerClass)}> class:bx--modal-container={true}
class="{size && `bx--modal-container--${size}`}
{containerClass}">
<slot /> <slot />
</div> </div>
</div> </div>

View file

@ -1,20 +1,16 @@
<script> <script>
let className = undefined;
export { className as class };
export let hasForm = false; export let hasForm = false;
export let hasScrollingContent = false; export let hasScrollingContent = false;
export let style = undefined;
import { cx } from '../../lib';
</script> </script>
<div <div
tabindex={hasScrollingContent ? '0' : undefined} tabindex={hasScrollingContent ? '0' : undefined}
role={hasScrollingContent ? 'region' : undefined} role={hasScrollingContent ? 'region' : undefined}
class={cx('--modal-content', hasForm && '--modal-content--with-form', className)} class:bx--modal-content={true}
{style}> class:bx--modal-content--with-form={hasForm}
{...$$restProps}>
<slot /> <slot />
</div> </div>
{#if hasScrollingContent} {#if hasScrollingContent}
<div class={cx('--modal-content--overflow-indicator')} /> <div class:bx--modal-content--overflow-indicator={true} />
{/if} {/if}

View file

@ -1,22 +1,18 @@
<script> <script>
let className = undefined;
export { className as class };
export let primaryClass = undefined; export let primaryClass = undefined;
export let primaryButtonText = ''; export let primaryButtonText = "";
export let primaryButtonDisabled = false; export let primaryButtonDisabled = false;
export let secondaryClass = undefined; export let secondaryClass = undefined;
export let secondaryButtonText = ''; export let secondaryButtonText = "";
export let danger = false; export let danger = false;
export let style = undefined;
import { getContext } from 'svelte'; import { getContext } from "svelte";
import { cx } from '../../lib'; import { Button } from "../Button";
import Button from '../Button';
const { closeModal, submit } = getContext('ComposedModal'); const { closeModal, submit } = getContext("ComposedModal");
</script> </script>
<div class={cx('--modal-footer', className)} {style}> <div class:bx--modal-footer={true} {...$$restProps}>
{#if secondaryButtonText} {#if secondaryButtonText}
<Button kind="secondary" class={secondaryClass} on:click={closeModal}> <Button kind="secondary" class={secondaryClass} on:click={closeModal}>
{secondaryButtonText} {secondaryButtonText}
@ -24,9 +20,9 @@
{/if} {/if}
{#if primaryButtonText} {#if primaryButtonText}
<Button <Button
class={primaryClass}
kind={danger ? 'danger' : 'primary'} kind={danger ? 'danger' : 'primary'}
disabled={primaryButtonDisabled} disabled={primaryButtonDisabled}
class={primaryClass}
on:click={submit}> on:click={submit}>
{primaryButtonText} {primaryButtonText}
</Button> </Button>

View file

@ -1,37 +1,44 @@
<script> <script>
let className = undefined; export let title = "";
export { className as class }; export let label = "";
export let labelClass = undefined; export let labelClass = "";
export let titleClass = undefined; export let titleClass = "";
export let closeClass = undefined; export let closeClass = "";
export let closeIconClass = undefined; export let closeIconClass = "";
export let label = undefined; export let iconDescription = "Close";
export let title = '';
export let iconDescription = 'Close';
export let style = undefined;
import { getContext } from 'svelte'; import { getContext } from "svelte";
import Close20 from 'carbon-icons-svelte/lib/Close20'; import Close20 from "carbon-icons-svelte/lib/Close20";
import { cx } from '../../lib';
const { closeModal } = getContext('ComposedModal'); const { closeModal } = getContext("ComposedModal");
</script> </script>
<div class={cx('--modal-header', className)} {style}> <div class:bx--modal-header={true} {...$$restProps}>
{#if label} {#if label}
<p class={cx('--modal-header__label', '--type-delta', labelClass)}>{label}</p> <p
class:bx--modal-header__label={true}
class:bx--type-delta={true}
class:labelClass>
{label}
</p>
{/if} {/if}
{#if title} {#if title}
<p class={cx('--modal-header__heading', '--type-beta', titleClass)}>{title}</p> <p
class:bx--modal-header__heading={true}
class:bx--type-beta={true}
class:titleClass>
{title}
</p>
{/if} {/if}
<slot /> <slot />
<button <button
type="button" type="button"
title={iconDescription} title={iconDescription}
aria-label={iconDescription} aria-label={iconDescription}
class={cx('--modal-close', closeClass)} class:bx--modal-close={true}
class:closeClass
on:click on:click
on:click={closeModal}> on:click={closeModal}>
<Close20 class={cx('--modal-close__icon', closeIconClass)} /> <Close20 class="bx--modal-close__icon {closeIconClass}" />
</button> </button>
</div> </div>

View file

@ -1,6 +1,4 @@
import ComposedModal from './ComposedModal.svelte'; export { default as ComposedModal } from "./ComposedModal.svelte";
export { default as ModalHeader } from "./ModalHeader.svelte";
export default ComposedModal; export { default as ModalBody } from "./ModalBody.svelte";
export { default as ModalHeader } from './ModalHeader.svelte'; export { default as ModalFooter } from "./ModalFooter.svelte";
export { default as ModalBody } from './ModalBody.svelte';
export { default as ModalFooter } from './ModalFooter.svelte';

View file

@ -1,11 +1,13 @@
<script> <script>
export let story = undefined; export let story = undefined;
import Layout from '../../internal/ui/Layout.svelte'; import Layout from "../../internal/ui/Layout.svelte";
import ContentSwitcher from './ContentSwitcher.svelte'; import ContentSwitcher from "./ContentSwitcher.svelte";
import Switch from './Switch.svelte'; import Switch from "./Switch.svelte";
import Add16 from "carbon-icons-svelte/lib/Add16";
let selectedIndex = 0; $: selectedIndex = 0;
$: console.log("bind selectedIndex", selectedIndex);
</script> </script>
<Layout> <Layout>
@ -26,7 +28,19 @@
}}> }}>
<Switch {...$$props} text="First section" /> <Switch {...$$props} text="First section" />
<Switch {...$$props} text="Second section" /> <Switch {...$$props} text="Second section" />
<Switch {...$$props} text="Third section" /> <Switch {...$$props}>
<div style="display: flex; align-items:center;">
<Add16 style="margin-right: .25rem;" />
Third section
</div>
</Switch>
</ContentSwitcher> </ContentSwitcher>
<div
style="margin-top: 1.5rem"
on:click={() => {
selectedIndex = 1;
}}>
Programmatically set to second index
</div>
{/if} {/if}
</Layout> </Layout>

View file

@ -1,19 +1,17 @@
import { withKnobs, boolean } from '@storybook/addon-knobs'; import { withKnobs, boolean } from "@storybook/addon-knobs";
import Component from './ContentSwitcher.Story.svelte'; import Component from "./ContentSwitcher.Story.svelte";
export default { title: 'ContentSwitcher', decorators: [withKnobs] }; export default { title: "ContentSwitcher", decorators: [withKnobs] };
export const Default = () => ({ export const Default = () => ({
Component, Component,
props: { props: { disabled: boolean("Disabled (disabled)", false) },
disabled: boolean('Disabled (disabled)', false)
}
}); });
export const Selected = () => ({ export const Selected = () => ({
Component, Component,
props: { props: {
story: 'selected', story: "selected",
disabled: boolean('Disabled (disabled)', false) disabled: boolean("Disabled (disabled)", false),
} },
}); });

View file

@ -1,30 +1,30 @@
<script> <script>
export let className = undefined;
export { className as class };
export let selectedIndex = 0; export let selectedIndex = 0;
export let style = undefined;
import { createEventDispatcher, setContext } from 'svelte'; import { afterUpdate, createEventDispatcher, setContext } from "svelte";
import { writable } from 'svelte/store'; import { writable } from "svelte/store";
import { cx } from '../../lib';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const currentId = writable(null);
let currentId = writable(null); $: currentIndex = -1;
let currentIndex = selectedIndex; $: switches = [];
let switches = []; $: if (switches[currentIndex]) {
dispatch("change", currentIndex);
currentId.set(switches[currentIndex].id);
}
setContext('ContentSwitcher', { setContext("ContentSwitcher", {
currentId, currentId,
add: ({ id, text, selected }) => { add: ({ id, text, selected }) => {
switches = [...switches, { id, text, selected }];
if (selected) { if (selected) {
currentIndex = switches.length; selectedIndex = switches.length;
} }
switches = [...switches, { id, text, selected }];
}, },
update: id => { update: id => {
currentIndex = switches.map(({ id }) => id).indexOf(id); selectedIndex = switches.map(({ id }) => id).indexOf(id);
}, },
change: direction => { change: direction => {
let index = currentIndex + direction; let index = currentIndex + direction;
@ -35,24 +35,24 @@
index = 0; index = 0;
} }
currentIndex = index; selectedIndex = index;
} }
}); });
$: if (switches[currentIndex]) { afterUpdate(() => {
dispatch('change', currentIndex); if (selectedIndex !== currentIndex) {
selectedIndex = currentIndex; currentIndex = selectedIndex;
currentId.set(switches[currentIndex].id);
} }
});
</script> </script>
<div <div
role="tablist" role="tablist"
class:bx--content-switcher={true}
{...$$restProps}
on:click on:click
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave>
class={cx('--content-switcher', className)}
{style}>
<slot /> <slot />
</div> </div>

View file

@ -1,39 +1,44 @@
<script> <script>
let className = undefined; export let text = "Provide text";
export { className as class };
export let selected = false; export let selected = false;
export let text = 'Provide text';
export let disabled = false; export let disabled = false;
export let style = undefined; export let id = "ccs-" + Math.random().toString(36);
export let ref = null;
import { afterUpdate, getContext } from 'svelte'; import { afterUpdate, getContext, onDestroy } from "svelte";
import { cx } from '../../lib';
const id = Math.random(); const ctx = getContext("ContentSwitcher");
const { currentId, add, update, change } = getContext('ContentSwitcher');
let buttonRef = undefined; ctx.add({ id, text, selected });
add({ id, text, selected }); const unsubscribe = ctx.currentId.subscribe($ => {
selected = $ === id;
});
afterUpdate(() => { afterUpdate(() => {
if (selected) { if (selected) {
buttonRef.focus(); ref.focus();
} }
}); });
$: selected = $currentId === id; onDestroy(() => {
unsubscribe();
});
</script> </script>
<button <button
bind:this={buttonRef} bind:this={ref}
role="tab" role="tab"
tabindex={selected ? '0' : '-1'} tabindex={selected ? '0' : '-1'}
aria-selected={selected} aria-selected={selected}
class={cx('--content-switcher-btn', selected && '--content-switcher--selected', className)} {disabled}
{id}
class:bx--content-switcher-btn={true}
class:bx--content-switcher--selected={selected}
{...$$restProps}
on:click on:click
on:click|preventDefault={() => { on:click|preventDefault={() => {
update(id); ctx.update(id);
}} }}
on:mouseover on:mouseover
on:mouseenter on:mouseenter
@ -41,12 +46,13 @@
on:keydown on:keydown
on:keydown={({ key }) => { on:keydown={({ key }) => {
if (key === 'ArrowRight') { if (key === 'ArrowRight') {
change(1); ctx.change(1);
} else if (key === 'ArrowLeft') { } else if (key === 'ArrowLeft') {
change(-1); ctx.change(-1);
} }
}} }}>
{disabled}
{style}> <span class:bx--content-switcher__label={true}>
<span class={cx('--content-switcher__label')}>{text}</span> <slot>{text}</slot>
</span>
</button> </button>

View file

@ -1,4 +1,2 @@
import ContentSwitcher from './ContentSwitcher.svelte'; export { default as ContentSwitcher } from "./ContentSwitcher.svelte";
export { default as Switch } from "./Switch.svelte";
export default ContentSwitcher;
export { default as Switch } from './Switch.svelte';

View file

@ -1,38 +1,51 @@
<script> <script>
let className = undefined; export let feedback = "Copied!";
export { className as class };
export let feedback = 'Copied!';
export let feedbackTimeout = 2000; export let feedbackTimeout = 2000;
export let style = undefined; export let ref = null;
import { onDestroy } from 'svelte'; import { onDestroy } from "svelte";
import { cx } from '../../lib';
let timeoutId = undefined; $: animation = undefined;
$: timeoutId = undefined;
$: showFeedback = timeoutId !== undefined;
onDestroy(() => { onDestroy(() => {
window.clearTimeout(timeoutId); window.clearTimeout(timeoutId);
timeoutId = undefined; timeoutId = undefined;
}); });
$: showFeedback = timeoutId !== undefined;
</script> </script>
<button <button
bind:this={ref}
type="button" type="button"
class={className} aria-live="polite"
class:bx--copy={true}
class:bx--copy-btn--animating={animation}
aria-label={animation ? feedback : undefined}
{...$$restProps}
class="{$$restProps.class}
{animation && `bx--copy-btn--${animation}`}"
on:click on:click
on:click={() => { on:click={() => {
timeoutId = window.setTimeout(() => { if (animation === 'fade-in') return;
showFeedback = undefined; animation = 'fade-in';
timeoutId = setTimeout(() => {
animation = 'fade-out';
}, feedbackTimeout); }, feedbackTimeout);
}} }}
on:mouseover on:animationend
on:mouseenter on:animationend={({ animationName }) => {
on:mouseleave if (animationName === 'hide-feedback') {
{style}> animation = undefined;
<slot /> }
<div }}>
class={cx('--btn--copy__feedback', showFeedback && '--btn--copy__feedback--displayed')} <slot>
data-feedback={feedback} /> {#if animation}{feedback || $$restProps['aria-label']}{/if}
</slot>
<span
aria-hidden="true"
class:bx--assistive-text={true}
class:bx--copy-btn__feedback={true}>
{feedback}
</span>
</button> </button>

View file

@ -1,3 +1 @@
import Copy from './Copy.svelte'; export { default as Copy } from "./Copy.svelte";
export default Copy;

View file

@ -1,8 +1,15 @@
<script> <script>
import Layout from '../../internal/ui/Layout.svelte'; import Layout from "../../internal/ui/Layout.svelte";
import CopyButton from './CopyButton.svelte'; import CopyButton from "./CopyButton.svelte";
</script> </script>
<Layout> <Layout>
<CopyButton {...$$props} /> <CopyButton
{...$$props}
on:click={() => {
console.log('click');
}}
on:animationend={e => {
console.log('animation end', e.animationName);
}} />
</Layout> </Layout>

View file

@ -1,13 +1,19 @@
import { withKnobs, text, number } from '@storybook/addon-knobs'; import { withKnobs, text, number } from "@storybook/addon-knobs";
import Component from './CopyButton.Story.svelte'; import Component from "./CopyButton.Story.svelte";
export default { title: 'CopyButton', decorators: [withKnobs] }; export default { title: "CopyButton", decorators: [withKnobs] };
export const Default = () => ({ export const Default = () => ({
Component, Component,
props: { props: {
feedback: text('The text shown upon clicking (feedback)', 'Copied!'), feedback: text("The text shown upon clicking (feedback)", "Copied!"),
feedbackTimeout: number('How long the text is shown upon clicking (feedbackTimeout)', 2000), feedbackTimeout: number(
iconDescription: text('Feedback icon description (iconDescription)', 'Copy to clipboard') "How long the text is shown upon clicking (feedbackTimeout)",
} 2000
),
iconDescription: text(
"Feedback icon description (iconDescription)",
"Copy to clipboard"
),
},
}); });

View file

@ -1,52 +1,16 @@
<script> <script>
let className = undefined; export let iconDescription = "Copy to clipboard";
export { className as class };
export let feedback = 'Copied!';
export let feedbackTimeout = 2000;
export let iconDescription = 'Copy to clipboard';
export let style = undefined;
import { afterUpdate, onDestroy } from 'svelte'; import { Copy } from "../Copy";
import Copy16 from 'carbon-icons-svelte/lib/Copy16'; import Copy16 from "carbon-icons-svelte/lib/Copy16";
import { cx } from '../../lib';
let animation = undefined;
let timeoutId = undefined;
afterUpdate(() => {
if (animation === 'fade-in') {
timeoutId = window.setTimeout(() => {
animation = 'fade-out';
}, feedbackTimeout);
}
});
onDestroy(() => {
window.clearTimeout(timeoutId);
timeoutId = undefined;
});
</script> </script>
<button <Copy
type="button" class="bx--copy-btn"
tabindex="0"
aria-label={iconDescription} aria-label={iconDescription}
title={iconDescription} title={iconDescription}
class={cx('--copy-btn', animation && '--copy-btn--animating', animation && `--copy-btn--${animation}`, animation === 'fade-in' && '--btn--copy__feedback--displayed', className)} {...$$restProps}
on:click on:click
on:click={() => { on:animationend>
animation = 'fade-in'; <Copy16 class="bx--snippet__icon" />
}} </Copy>
on:mouseover
on:mouseenter
on:mouseleave
on:animationend
on:animationend={({ animationName }) => {
if (animationName === 'hide-feedback') {
animation = undefined;
}
}}
{style}>
<span class={cx('--assistive-text', '--copy-btn__feedback')}>{feedback}</span>
<Copy16 class={cx('--snippet__icon')} />
</button>

View file

@ -1,3 +1 @@
import CopyButton from './CopyButton.svelte'; export { default as CopyButton } from "./CopyButton.svelte";
export default CopyButton;

View file

@ -1,81 +1,81 @@
<script> <script>
export let story = undefined; export let story = undefined;
import Layout from '../../internal/ui/Layout.svelte'; import Layout from "../../internal/ui/Layout.svelte";
import DataTable from './DataTable.svelte'; import DataTable from "./DataTable.svelte";
import Table from './Table.svelte'; import Table from "./Table.svelte";
import TableBody from './TableBody.svelte'; import TableBody from "./TableBody.svelte";
import TableCell from './TableCell.svelte'; import TableCell from "./TableCell.svelte";
import TableContainer from './TableContainer.svelte'; import TableContainer from "./TableContainer.svelte";
import TableHead from './TableHead.svelte'; import TableHead from "./TableHead.svelte";
import TableHeader from './TableHeader.svelte'; import TableHeader from "./TableHeader.svelte";
import TableRow from './TableRow.svelte'; import TableRow from "./TableRow.svelte";
let rows = [ const rows = [
{ {
id: 'a', id: "a",
name: 'Load Balancer 3', name: "Load Balancer 3",
protocol: 'HTTP', protocol: "HTTP",
port: 3000, port: 3000,
rule: 'Round robin', rule: "Round robin",
attached_groups: 'Kevins VM Groups', attached_groups: "Kevins VM Groups",
status: 'Disabled' status: "Disabled"
}, },
{ {
id: 'b', id: "b",
name: 'Load Balancer 1', name: "Load Balancer 1",
protocol: 'HTTP', protocol: "HTTP",
port: 443, port: 443,
rule: 'Round robin', rule: "Round robin",
attached_groups: 'Maureens VM Groups', attached_groups: "Maureens VM Groups",
status: 'Starting' status: "Starting"
}, },
{ {
id: 'c', id: "c",
name: 'Load Balancer 2', name: "Load Balancer 2",
protocol: 'HTTP', protocol: "HTTP",
port: 80, port: 80,
rule: 'DNS delegation', rule: "DNS delegation",
attached_groups: 'Andrews VM Groups', attached_groups: "Andrews VM Groups",
status: 'Active' status: "Active"
}, },
{ {
id: 'd', id: "d",
name: 'Load Balancer 6', name: "Load Balancer 6",
protocol: 'HTTP', protocol: "HTTP",
port: 3000, port: 3000,
rule: 'Round robin', rule: "Round robin",
attached_groups: 'Marcs VM Groups', attached_groups: "Marcs VM Groups",
status: 'Disabled' status: "Disabled"
}, },
{ {
id: 'e', id: "e",
name: 'Load Balancer 4', name: "Load Balancer 4",
protocol: 'HTTP', protocol: "HTTP",
port: 443, port: 443,
rule: 'Round robin', rule: "Round robin",
attached_groups: 'Mels VM Groups', attached_groups: "Mels VM Groups",
status: 'Starting' status: "Starting"
}, },
{ {
id: 'f', id: "f",
name: 'Load Balancer 5', name: "Load Balancer 5",
protocol: 'HTTP', protocol: "HTTP",
port: 80, port: 80,
rule: 'DNS delegation', rule: "DNS delegation",
attached_groups: 'Ronjas VM Groups', attached_groups: "Ronjas VM Groups",
status: 'Active' status: "Active"
} }
]; ];
let headers = [ const headers = [
{ key: 'name', value: 'Name' }, { key: "name", value: "Name" },
{ key: 'protocol', value: 'Protocol' }, { key: "protocol", value: "Protocol" },
{ key: 'port', value: 'Port' }, { key: "port", value: "Port" },
{ key: 'rule', value: 'Rule' }, { key: "rule", value: "Rule" },
{ key: 'attached_groups', value: 'Attached Groups' }, { key: "attached_groups", value: "Attached Groups" },
{ key: 'status', value: 'Status' } { key: "status", value: "Status" }
]; ];
let sortable = true; $: sortable = true;
</script> </script>
<Layout> <Layout>
@ -89,7 +89,9 @@
<TableHead> <TableHead>
<TableRow> <TableRow>
{#each props.headers as header, i (header.key)} {#each props.headers as header, i (header.key)}
<TableHeader {...props.getHeaderProps({ header })}>{header.header}</TableHeader> <TableHeader {...props.getHeaderProps({ header })}>
{header.header}
</TableHeader>
{/each} {/each}
</TableRow> </TableRow>
</TableHead> </TableHead>

View file

@ -1,35 +1,35 @@
import { withKnobs, boolean, select, text } from '@storybook/addon-knobs'; import { withKnobs, boolean, select, text } from "@storybook/addon-knobs";
import Component from './DataTable.Story.svelte'; import Component from "./DataTable.Story.svelte";
export default { title: 'DataTable', decorators: [withKnobs] }; export default { title: "DataTable", decorators: [withKnobs] };
export const Default = () => ({ export const Default = () => ({
Component, Component,
props: { props: {
title: text('Optional DataTable title (title)', ''), title: text("Optional DataTable title (title)", ""),
description: text('Optional DataTable description (description)', ''), description: text("Optional DataTable description (description)", ""),
zebra: boolean('Zebra row styles (zebra)', false), zebra: boolean("Zebra row styles (zebra)", false),
size: select( size: select(
'Row height (size)', "Row height (size)",
{ compact: 'compact', short: 'short', tall: 'tall', none: null }, { compact: "compact", short: "short", tall: "tall", none: null },
null null
), ),
stickyHeader: boolean('Sticky header (experimental)', false) stickyHeader: boolean("Sticky header (experimental)", false),
} },
}); });
export const Sortable = () => ({ export const Sortable = () => ({
Component, Component,
props: { props: {
story: 'sortable', story: "sortable",
title: text('Optional DataTable title (title)', ''), title: text("Optional DataTable title (title)", ""),
description: text('Optional DataTable description (description)', ''), description: text("Optional DataTable description (description)", ""),
zebra: boolean('Zebra row styles (zebra)', false), zebra: boolean("Zebra row styles (zebra)", false),
size: select( size: select(
'Row height (size)', "Row height (size)",
{ compact: 'compact', short: 'short', tall: 'tall', none: null }, { compact: "compact", short: "short", tall: "tall", none: null },
null null
), ),
stickyHeader: boolean('Sticky header (experimental)', false) stickyHeader: boolean("Sticky header (experimental)", false),
} },
}); });

View file

@ -1,39 +1,39 @@
<script> <script>
let className = undefined; export let size = undefined; // "compact" | "short" | "tall"
export { className as class }; export let title = "";
export let title = ''; export let description = "";
export let description = '';
export let zebra = false; export let zebra = false;
export let sortable = false;
export let stickyHeader = false;
export let rows = []; export let rows = [];
export let headers = []; export let headers = [];
export let stickyHeader = false;
export let size = undefined;
export let sortable = false;
export let style = undefined;
import { createEventDispatcher, setContext } from 'svelte'; import { createEventDispatcher, setContext } from "svelte";
import { writable, derived } from 'svelte/store'; import { writable, derived } from "svelte/store";
import Table from './Table.svelte'; import Table from "./Table.svelte";
import TableBody from './TableBody.svelte'; import TableBody from "./TableBody.svelte";
import TableCell from './TableCell.svelte'; import TableCell from "./TableCell.svelte";
import TableContainer from './TableContainer.svelte'; import TableContainer from "./TableContainer.svelte";
import TableHead from './TableHead.svelte'; import TableHead from "./TableHead.svelte";
import TableHeader from './TableHeader.svelte'; import TableHeader from "./TableHeader.svelte";
import TableRow from './TableRow.svelte'; import TableRow from "./TableRow.svelte";
const sortDirectionMap = {
none: "ascending",
ascending: "descending",
descending: "none"
};
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const sortDirectionMap = { none: 'ascending', ascending: 'descending', descending: 'none' }; const tableSortable = writable(sortable);
const sortHeader = writable({ id: null, key: null, sortDirection: "none" });
let tableSortable = writable(sortable); const headerItems = writable([]);
let sortHeader = writable({ id: null, key: null, sortDirection: 'none' }); const thKeys = derived(headerItems, () =>
let headerItems = writable([]);
let thKeys = derived(headerItems, () =>
headers headers
.map(({ key }, i) => ({ key, id: $headerItems[i] })) .map(({ key }, i) => ({ key, id: $headerItems[i] }))
.reduce((a, c) => ({ ...a, [c.key]: c.id }), {}) .reduce((a, c) => ({ ...a, [c.key]: c.id }), {})
); );
setContext('DataTable', { setContext("DataTable", {
sortHeader, sortHeader,
tableSortable, tableSortable,
add: id => { add: id => {
@ -43,24 +43,29 @@
$: tableSortable.set(sortable); $: tableSortable.set(sortable);
$: headerKeys = headers.map(({ key }) => key); $: headerKeys = headers.map(({ key }) => key);
$: rows = rows.map(row => ({ ...row, cells: headerKeys.map(key => ({ key, value: row[key] })) })); $: rows = rows.map(row => ({
...row,
cells: headerKeys.map(key => ({ key, value: row[key] }))
}));
$: sortedRows = rows; $: sortedRows = rows;
$: ascending = $sortHeader.sortDirection === 'ascending'; $: ascending = $sortHeader.sortDirection === "ascending";
$: sortKey = $sortHeader.key; $: sortKey = $sortHeader.key;
$: sorting = sortable && sortKey != null; $: sorting = sortable && sortKey != null;
$: if (sorting) { $: if (sorting) {
if ($sortHeader.sortDirection === 'none') { if ($sortHeader.sortDirection === "none") {
sortedRows = rows; sortedRows = rows;
} else { } else {
sortedRows = [...rows].sort((a, b) => { sortedRows = [...rows].sort((a, b) => {
const itemA = ascending ? a[sortKey] : b[sortKey]; const itemA = ascending ? a[sortKey] : b[sortKey];
const itemB = ascending ? b[sortKey] : a[sortKey]; const itemB = ascending ? b[sortKey] : a[sortKey];
if (typeof itemA === 'number' && typeof itemB === 'number') { if (typeof itemA === "number" && typeof itemB === "number") {
return itemA - itemB; return itemA - itemB;
} }
return itemA.toString().localeCompare(itemB.toString(), 'en', { numeric: true }); return itemA
.toString()
.localeCompare(itemB.toString(), "en", { numeric: true });
}); });
} }
} }
@ -71,7 +76,7 @@
</script> </script>
<slot {props}> <slot {props}>
<TableContainer class={className} {title} {description} {style}> <TableContainer {title} {description} {...$$restProps}>
<Table {zebra} {size} {stickyHeader} {sortable}> <Table {zebra} {size} {stickyHeader} {sortable}>
<TableHead> <TableHead>
<TableRow> <TableRow>
@ -82,7 +87,7 @@
let active = header.key === $sortHeader.key; let active = header.key === $sortHeader.key;
let currentSortDirection = active ? $sortHeader.sortDirection : 'none'; let currentSortDirection = active ? $sortHeader.sortDirection : 'none';
let sortDirection = sortDirectionMap[currentSortDirection]; let sortDirection = sortDirectionMap[currentSortDirection];
dispatch('click:header', {header, sortDirection}); dispatch('click:header', { header, sortDirection });
sortHeader.set({ sortHeader.set({
id: sortDirection === 'none' ? null : $thKeys[header.key], id: sortDirection === 'none' ? null : $thKeys[header.key],
key: header.key, key: header.key,

View file

@ -1,28 +1,39 @@
<script> <script>
let className = undefined; export let size = undefined; // "compact" | "short" | "tall"
export { className as class };
export let zebra = false; export let zebra = false;
export let size = undefined;
export let useStaticWidth = false; export let useStaticWidth = false;
export let shouldShowBorder = false; export let shouldShowBorder = false;
export let sortable = false; export let sortable = false;
export let stickyHeader = false; export let stickyHeader = false;
export let style = undefined;
import { cx } from '../../lib';
</script> </script>
{#if stickyHeader} {#if stickyHeader}
<section class={cx('--data-table_inner-container', className)} {style}> <section class:bx--data-table_inner-container={true} {...$$restProps}>
<table <table
class={cx('--data-table', size === 'compact' && '--data-table--compact', size === 'short' && '--data-table--short', size === 'tall' && '--data-table--tall', sortable && '--data-table--sort', zebra && '--data-table--zebra', useStaticWidth && '--data-table--static', !shouldShowBorder && '--data-table--no-border', stickyHeader && '--data-table--sticky-header')}> class:bx--data-table={true}
class:bx--data-table--compact={size === 'compact'}
class:bx--data-table--short={size === 'short'}
class:bx--data-table--tall={size === 'tall'}
class:bx--data-table--sort={sortable}
class:bx--data-table--zebra={zebra}
class:bx--data-table--static={useStaticWidth}
class:bx--data-table--no-border={!shouldShowBorder}
class:bx--data-table--sticky-header={stickyHeader}>
<slot /> <slot />
</table> </table>
</section> </section>
{:else} {:else}
<table <table
class={cx('--data-table', size === 'compact' && '--data-table--compact', size === 'short' && '--data-table--short', size === 'tall' && '--data-table--tall', sortable && '--data-table--sort', zebra && '--data-table--zebra', useStaticWidth && '--data-table--static', !shouldShowBorder && '--data-table--no-border', stickyHeader && '--data-table--sticky-header', className)} class:bx--data-table={true}
{style}> class:bx--data-table--compact={size === 'compact'}
class:bx--data-table--short={size === 'short'}
class:bx--data-table--tall={size === 'tall'}
class:bx--data-table--sort={sortable}
class:bx--data-table--zebra={zebra}
class:bx--data-table--static={useStaticWidth}
class:bx--data-table--no-border={!shouldShowBorder}
class:bx--data-table--sticky-header={stickyHeader}
{...$$restProps}>
<slot /> <slot />
</table> </table>
{/if} {/if}

View file

@ -1,9 +1,3 @@
<script> <tbody aria-live="polite" {...$$restProps}>
let className = undefined;
export { className as class };
export let style = undefined;
</script>
<tbody aria-live={$$props['aria-live'] || 'polite'} class={className} {style}>
<slot /> <slot />
</tbody> </tbody>

View file

@ -1,9 +1,3 @@
<script> <td {...$$restProps} on:click on:mouseover on:mouseenter on:mouseleave>
let className = undefined;
export { className as class };
export let style = undefined;
</script>
<td on:click on:mouseover on:mouseenter on:mouseleave class={className} {style}>
<slot /> <slot />
</td> </td>

View file

@ -1,21 +1,17 @@
<script> <script>
let className = undefined;
export { className as class };
export let stickyHeader = false; export let stickyHeader = false;
export let title = ''; export let title = "";
export let description = ''; export let description = "";
export let style = undefined;
import { cx } from '../../lib';
</script> </script>
<div <div
class={cx('--data-table-container', stickyHeader && '--data-table--max-width', className)} class:bx--data-table-container={true}
{style}> class:bx--data-table--max-width={stickyHeader}
{...$$restProps}>
{#if title} {#if title}
<div class={cx('--data-table-header')}> <div class:bx--data-table-header={true}>
<h4 class={cx('--data-table-header__title')}>{title}</h4> <h4 class:bx--data-table-header__title={true}>{title}</h4>
<p class={cx('--data-table-header__description')}>{description}</p> <p class:bx--data-table-header__description={true}>{description}</p>
</div> </div>
{/if} {/if}
<slot /> <slot />

View file

@ -1,9 +1,3 @@
<script> <thead {...$$restProps} on:click on:mouseover on:mouseenter on:mouseleave>
let className = undefined;
export { className as class };
export let style = undefined;
</script>
<thead on:click on:mouseover on:mouseenter on:mouseleave class={className} {style}>
<slot /> <slot />
</thead> </thead>

View file

@ -1,17 +1,13 @@
<script> <script>
let className = undefined; export let scope = "col";
export { className as class }; export let translateWithId = () => "";
export let scope = 'col'; export let id = "ccs-" + Math.random().toString(36);
export let translateWithId = () => '';
export let style = undefined;
import { getContext } from 'svelte'; import { getContext } from "svelte";
import ArrowUp20 from 'carbon-icons-svelte/lib/ArrowUp20'; import ArrowUp20 from "carbon-icons-svelte/lib/ArrowUp20";
import ArrowsVertical20 from 'carbon-icons-svelte/lib/ArrowsVertical20'; import ArrowsVertical20 from "carbon-icons-svelte/lib/ArrowsVertical20";
import { cx } from '../../lib';
const id = Math.random(); const { sortHeader, tableSortable, add } = getContext("DataTable");
const { sortHeader, tableSortable, add } = getContext('DataTable');
add(id); add(id);
@ -22,25 +18,35 @@
{#if $tableSortable} {#if $tableSortable}
<th <th
aria-sort={active ? $sortHeader.sortDirection : 'none'}
{scope}
{...$$restProps}
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave>
class={className}
aria-sort={active ? $sortHeader.sortDirection : 'none'}
{scope}>
<button <button
class={cx('--table-sort', active && '--table-sort--active', active && $sortHeader.sortDirection === 'descending' && '--table-sort--ascending')} class:bx--table-sort={true}
class:bx--table-sort--active={active}
class:bx--table-sort--ascending={active && $sortHeader.sortDirection === 'descending'}
on:click> on:click>
<span class={cx('--table-header-label')}> <span class:bx--table-header-label={true}>
<slot /> <slot />
</span> </span>
<ArrowUp20 class={cx('--table-sort__icon')} aria-label={ariaLabel} /> <ArrowUp20 aria-label={ariaLabel} class="bx--table-sort__icon" />
<ArrowsVertical20 class={cx('--table-sort__icon-unsorted')} aria-label={ariaLabel} /> <ArrowsVertical20
aria-label={ariaLabel}
class="bx--table-sort__icon-unsorted" />
</button> </button>
</th> </th>
{:else} {:else}
<th on:click on:mouseover on:mouseenter on:mouseleave class={className} {style} {scope}> <th
<span class={cx('--table-header-label')}> {scope}
{...$$restProps}
on:click
on:mouseover
on:mouseenter
on:mouseleave>
<span class:bx--table-header-label={true}>
<slot /> <slot />
</span> </span>
</th> </th>

View file

@ -1,20 +1,15 @@
<script> <script>
let className = undefined;
export { className as class };
export let isSelected = false; export let isSelected = false;
export let style = undefined;
import { cx } from '../../lib';
// TODO: include ariaLabel, onExpand, isExpanded, isSelected // TODO: include ariaLabel, onExpand, isExpanded, isSelected
</script> </script>
<tr <tr
class:bx--data-table--selected={isSelected}
{...$$restProps}
on:click on:click
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave>
class={cx(isSelected && '--data-table--selected', className)}
{style}>
<slot /> <slot />
</tr> </tr>

View file

@ -1,10 +1,8 @@
import DataTable from './DataTable.svelte'; export { default as DataTable } from "./DataTable.svelte";
export { default as Table } from "./Table.svelte";
export default DataTable; export { default as TableBody } from "./TableBody.svelte";
export { default as Table } from './Table.svelte'; export { default as TableCell } from "./TableCell.svelte";
export { default as TableBody } from './TableBody.svelte'; export { default as TableContainer } from "./TableContainer.svelte";
export { default as TableCell } from './TableCell.svelte'; export { default as TableHead } from "./TableHead.svelte";
export { default as TableContainer } from './TableContainer.svelte'; export { default as TableHeader } from "./TableHeader.svelte";
export { default as TableHead } from './TableHead.svelte'; export { default as TableRow } from "./TableRow.svelte";
export { default as TableHeader } from './TableHeader.svelte';
export { default as TableRow } from './TableRow.svelte';

View file

@ -1,17 +1,17 @@
import { withKnobs, array, boolean } from '@storybook/addon-knobs'; import { withKnobs, array, boolean } from "@storybook/addon-knobs";
import Component from './DataTableSkeleton.Story.svelte'; import Component from "./DataTableSkeleton.Story.svelte";
export default { title: 'DataTableSkeleton', decorators: [withKnobs] }; export default { title: "DataTableSkeleton", decorators: [withKnobs] };
export const Default = () => ({ export const Default = () => ({
Component, Component,
props: { props: {
headers: array( headers: array(
'Optional table headers (headers)', "Optional table headers (headers)",
['Name', 'Protocol', 'Port', 'Rule', 'Attached Groups'], ["Name", "Protocol", "Port", "Rule", "Attached Groups"],
',' ","
), ),
zebra: boolean('Use zebra stripe (zebra)', false), zebra: boolean("Use zebra stripe (zebra)", false),
compact: boolean('Compact variant (compact)', false) compact: boolean("Compact variant (compact)", false),
} },
}); });

View file

@ -1,6 +1,6 @@
<script> <script>
import Layout from '../../internal/ui/Layout.svelte'; import Layout from "../../internal/ui/Layout.svelte";
import DataTableSkeleton from './DataTableSkeleton.svelte'; import DataTableSkeleton from "./DataTableSkeleton.svelte";
</script> </script>
<Layout> <Layout>

View file

@ -1,25 +1,26 @@
<script> <script>
let className = undefined;
export { className as class };
export let columns = 5; export let columns = 5;
export let compact = false;
export let headers = [];
export let rows = 5; export let rows = 5;
export let style = undefined; export let compact = false;
export let zebra = false; export let zebra = false;
export let headers = [];
import { cx, fillArray } from '../../lib'; $: cols = Array.from(
{ length: headers.length > 0 ? headers.length : columns },
$: cols = fillArray(headers.length > 0 ? headers.length : columns); (_, i) => i
);
</script> </script>
<table <table
class:bx--skeleton={true}
class:bx--data-table={true}
class:bx--data-table--zebra={zebra}
class:bx--data-table--compact={compact}
{...$$restProps}
on:click on:click
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave>
class={cx('--skeleton', '--data-table', zebra && '--data-table--zebra', compact && '--data-table--compact', className)}
{style}>
<thead> <thead>
<tr> <tr>
{#each cols as col, i (col)} {#each cols as col, i (col)}
@ -35,7 +36,7 @@
</td> </td>
{/each} {/each}
</tr> </tr>
{#each fillArray(rows - 1) as row, i (row)} {#each Array.from({ length: rows - 1 }, (_, i) => i) as row, i (row)}
<tr> <tr>
{#each cols as col, j (col)} {#each cols as col, j (col)}
<td /> <td />

View file

@ -1,3 +1 @@
import DataTableSkeleton from './DataTableSkeleton.svelte'; export { default as DataTableSkeleton } from "./DataTableSkeleton.svelte";
export default DataTableSkeleton;

View file

@ -1,20 +1,25 @@
<script> <script>
let className = undefined;
export { className as class };
export let id = Math.random();
export let range = false; export let range = false;
export let style = undefined; export let id = "ccs-" + Math.random().toString(36);
import { cx, fillArray } from '../../lib';
</script> </script>
<div on:click on:mouseover on:mouseenter on:mouseleave class={cx('--form-item')} {style}> <div
class:bx--form-item={true}
{...$$restProps}
on:click
on:mouseover
on:mouseenter
on:mouseleave>
<div <div
class={cx('--date-picker', '--skeleton', range && '--date-picker--range', !range && '--date-picker--short', !range && '--date-picker--simple', className)}> class:bx--date-picker={true}
{#each fillArray(range ? 2 : 1) as input, i (input)} class:bx--skeleton={true}
<div class={cx('--date-picker-container')}> class:bx--date-picker--range={true}
<label class={cx('--label')} for={id} /> class:bx--date-picker--short={!range}
<div class={cx('--date-picker__input', '--skeleton')} /> class:bx--date-picker--simple={!range}>
{#each Array.from({ length: range ? 2 : 1 }, (_, i) => i) as input, i (input)}
<div class:bx--date-picker-container={true}>
<label for={id} class:bx--label={true} />
<div class:bx--date-picker__input={true} class:bx--skeleton={true} />
</div> </div>
{/each} {/each}
</div> </div>

View file

@ -1,13 +1,13 @@
<script> <script>
export let story = undefined; export let story = undefined;
import Layout from '../../internal/ui/Layout.svelte'; import Layout from "../../internal/ui/Layout.svelte";
import DatePicker from './DatePicker.svelte'; import DatePicker from "./DatePicker.svelte";
import DatePickerInput from './DatePickerInput.svelte'; import DatePickerInput from "./DatePickerInput.svelte";
import DatePickerSkeleton from './DatePicker.Skeleton.svelte'; import DatePickerSkeleton from "./DatePicker.Skeleton.svelte";
let datePickerType = 'simple'; $: datePickerType = "simple";
let value = ''; $: value = "";
</script> </script>
<Layout> <Layout>

View file

@ -1,100 +1,139 @@
import { withKnobs, select, text, boolean } from '@storybook/addon-knobs'; import { withKnobs, select, text, boolean } from "@storybook/addon-knobs";
import Component from './DatePicker.Story.svelte'; import Component from "./DatePicker.Story.svelte";
export default { title: 'DatePicker', decorators: [withKnobs] }; export default { title: "DatePicker", decorators: [withKnobs] };
const patterns = { const patterns = {
'Short (d{1,2}/d{4})': 'd{1,2}/d{4}', "Short (d{1,2}/d{4})": "d{1,2}/d{4}",
'Regular (d{1,2}/d{1,2}/d{4})': 'd{1,2}/d{1,2}/d{4}' "Regular (d{1,2}/d{1,2}/d{4})": "d{1,2}/d{1,2}/d{4}",
}; };
export const Default = () => ({ export const Default = () => ({
Component, Component,
props: { props: {
datePicker: { datePicker: {
id: 'date-picker', id: "date-picker",
light: boolean('Light variant (light in <DatePicker>)', false), light: boolean("Light variant (light in <DatePicker>)", false),
short: boolean('Use shorter width (short in <DatePicker>)', false) short: boolean("Use shorter width (short in <DatePicker>)", false),
}, },
datePickerInput: { datePickerInput: {
id: 'date-picker-input-id', id: "date-picker-input-id",
name: 'date-picker-input-name', name: "date-picker-input-name",
labelText: text('Label text (labelText in <DatePickerInput>)', 'Date Picker label'), labelText: text(
hideLabel: boolean('Hide label (hideLabel)', false), "Label text (labelText in <DatePickerInput>)",
pattern: select('The date format (pattern in <DatePickerInput>)', patterns, 'd{1,2}/d{4}'), "Date Picker label"
placeholder: text('Placeholder text (placeholder in <DatePickerInput>)', 'mm/dd/yyyy'), ),
disabled: boolean('Disabled (disabled in <DatePickerInput>)', false), hideLabel: boolean("Hide label (hideLabel)", false),
invalid: boolean('Show form validation UI (invalid in <DatePickerInput>)', false), pattern: select(
"The date format (pattern in <DatePickerInput>)",
patterns,
"d{1,2}/d{4}"
),
placeholder: text(
"Placeholder text (placeholder in <DatePickerInput>)",
"mm/dd/yyyy"
),
disabled: boolean("Disabled (disabled in <DatePickerInput>)", false),
invalid: boolean(
"Show form validation UI (invalid in <DatePickerInput>)",
false
),
invalidText: text( invalidText: text(
'Form validation UI content (invalidText in <DatePickerInput>)', "Form validation UI content (invalidText in <DatePickerInput>)",
'A valid value is required' "A valid value is required"
), ),
iconDescription: text( iconDescription: text(
'Icon description (iconDescription in <DatePickerInput>)', "Icon description (iconDescription in <DatePickerInput>)",
'Icon description' "Icon description"
) ),
} },
} },
}); });
Default.story = { name: 'Default (simple)' }; Default.story = { name: "Default (simple)" };
export const Single = () => ({ export const Single = () => ({
Component, Component,
props: { props: {
story: 'single', story: "single",
datePicker: { datePicker: {
id: 'date-picker', id: "date-picker",
light: boolean('Light variant (light in <DatePicker>)', false), light: boolean("Light variant (light in <DatePicker>)", false),
dateFormat: text('The date format (dateFormat in <DatePicker>)', 'm/d/Y') dateFormat: text("The date format (dateFormat in <DatePicker>)", "m/d/Y"),
}, },
datePickerInput: { datePickerInput: {
id: 'date-picker-input-id', id: "date-picker-input-id",
labelText: text('Label text (labelText in <DatePickerInput>)', 'Date Picker label'), labelText: text(
hideLabel: boolean('Hide label (hideLabel)', false), "Label text (labelText in <DatePickerInput>)",
pattern: select('The date format (pattern in <DatePickerInput>)', patterns, 'd{1,2}/d{4}'), "Date Picker label"
placeholder: text('Placeholder text (placeholder in <DatePickerInput>)', 'mm/dd/yyyy'), ),
disabled: boolean('Disabled (disabled in <DatePickerInput>)', false), hideLabel: boolean("Hide label (hideLabel)", false),
invalid: boolean('Show form validation UI (invalid in <DatePickerInput>)', false), pattern: select(
"The date format (pattern in <DatePickerInput>)",
patterns,
"d{1,2}/d{4}"
),
placeholder: text(
"Placeholder text (placeholder in <DatePickerInput>)",
"mm/dd/yyyy"
),
disabled: boolean("Disabled (disabled in <DatePickerInput>)", false),
invalid: boolean(
"Show form validation UI (invalid in <DatePickerInput>)",
false
),
invalidText: text( invalidText: text(
'Form validation UI content (invalidText in <DatePickerInput>)', "Form validation UI content (invalidText in <DatePickerInput>)",
'A valid value is required' "A valid value is required"
), ),
iconDescription: text( iconDescription: text(
'Icon description (iconDescription in <DatePickerInput>)', "Icon description (iconDescription in <DatePickerInput>)",
'Icon description' "Icon description"
) ),
} },
} },
}); });
export const Range = () => ({ export const Range = () => ({
Component, Component,
props: { props: {
story: 'range', story: "range",
datePicker: { datePicker: {
id: 'date-picker', id: "date-picker",
light: boolean('Light variant (light in <DatePicker>)', false), light: boolean("Light variant (light in <DatePicker>)", false),
dateFormat: text('The date format (dateFormat in <DatePicker>)', 'm/d/Y') dateFormat: text("The date format (dateFormat in <DatePicker>)", "m/d/Y"),
}, },
datePickerInput: { datePickerInput: {
id: 'date-picker-input-id', id: "date-picker-input-id",
labelText: text('Label text (labelText in <DatePickerInput>)', 'Date Picker label'), labelText: text(
hideLabel: boolean('Hide label (hideLabel)', false), "Label text (labelText in <DatePickerInput>)",
pattern: select('The date format (pattern in <DatePickerInput>)', patterns, 'd{1,2}/d{4}'), "Date Picker label"
placeholder: text('Placeholder text (placeholder in <DatePickerInput>)', 'mm/dd/yyyy'), ),
disabled: boolean('Disabled (disabled in <DatePickerInput>)', false), hideLabel: boolean("Hide label (hideLabel)", false),
invalid: boolean('Show form validation UI (invalid in <DatePickerInput>)', false), pattern: select(
"The date format (pattern in <DatePickerInput>)",
patterns,
"d{1,2}/d{4}"
),
placeholder: text(
"Placeholder text (placeholder in <DatePickerInput>)",
"mm/dd/yyyy"
),
disabled: boolean("Disabled (disabled in <DatePickerInput>)", false),
invalid: boolean(
"Show form validation UI (invalid in <DatePickerInput>)",
false
),
invalidText: text( invalidText: text(
'Form validation UI content (invalidText in <DatePickerInput>)', "Form validation UI content (invalidText in <DatePickerInput>)",
'A valid value is required' "A valid value is required"
), ),
iconDescription: text( iconDescription: text(
'Icon description (iconDescription in <DatePickerInput>)', "Icon description (iconDescription in <DatePickerInput>)",
'Icon description' "Icon description"
) ),
} },
} },
}); });
export const Skeleton = () => ({ Component, props: { story: 'skeleton' } }); export const Skeleton = () => ({ Component, props: { story: "skeleton" } });

View file

@ -1,39 +1,42 @@
<script> <script>
let className = undefined;
export { className as class };
export let appendTo = document.body; export let appendTo = document.body;
export let dateFormat = 'm/d/Y'; export let dateFormat = "m/d/Y";
export let datePickerType = 'simple'; export let datePickerType = "simple";
export let id = Math.random(); export let id = "ccs-" + Math.random().toString(36);
export let light = false; export let light = false;
export let locale = 'en'; export let locale = "en";
export let maxDate = null; export let maxDate = null;
export let minDate = null; export let minDate = null;
export let short = false; export let short = false;
export let style = undefined; export let value = "";
export let value = '';
import { createEventDispatcher, setContext, afterUpdate, onDestroy } from 'svelte'; import {
import { writable, derived } from 'svelte/store'; createEventDispatcher,
import { createCalendar } from './createCalendar'; setContext,
import { cx } from '../../lib'; afterUpdate,
onDestroy
} from "svelte";
import { writable, derived } from "svelte/store";
import { createCalendar } from "./createCalendar";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const inputs = writable([]);
let inputs = writable([]); const inputIds = derived(inputs, _ => _.map(({ id }) => id));
let inputIds = derived(inputs, _ => _.map(({ id }) => id)); const labelTextEmpty = derived(
let labelTextEmpty = derived(inputs, _ => _.filter(({ labelText }) => !!labelText).length === 0); inputs,
let inputValue = writable(value); _ => _.filter(({ labelText }) => !!labelText).length === 0
let mode = writable(datePickerType); );
let range = derived(mode, _ => _ === 'range'); const inputValue = writable(value);
let hasCalendar = derived(mode, _ => _ === 'single' || _ === 'range'); const mode = writable(datePickerType);
const range = derived(mode, _ => _ === "range");
const hasCalendar = derived(mode, _ => _ === "single" || _ === "range");
let calendar = undefined; let calendar = undefined;
let datePickerRef = undefined; let datePickerRef = undefined;
let inputRef = undefined; let inputRef = undefined;
let inputRefTo = undefined; let inputRefTo = undefined;
setContext('DatePicker', { setContext("DatePicker", {
range, range,
inputValue, inputValue,
hasCalendar, hasCalendar,
@ -48,12 +51,12 @@
} }
}, },
updateValue: ({ type, value }) => { updateValue: ({ type, value }) => {
if ((!calendar && type === 'input') || type === 'change') { if ((!calendar && type === "input") || type === "change") {
inputValue.set(value); inputValue.set(value);
} }
if (!calendar && type === 'change') { if (!calendar && type === "change") {
dispatch('change', value); dispatch("change", value);
} }
}, },
blurInput: relatedTarget => { blurInput: relatedTarget => {
@ -68,7 +71,7 @@
( (
calendar.selectedDateElem || calendar.selectedDateElem ||
calendar.todayDateElem || calendar.todayDateElem ||
calendar.calendarContainer.querySelector('.flatpickr-day[tabindex]') || calendar.calendarContainer.querySelector(".flatpickr-day[tabindex]") ||
calendar.calendarContainer calendar.calendarContainer
).focus(); ).focus();
} }
@ -133,11 +136,21 @@
} }
}} /> }} />
<div on:click on:mouseover on:mouseenter on:mouseleave class={cx('--form-item', className)} {style}> <div
class:bx--form-item={true}
{...$$restProps}
on:click
on:mouseover
on:mouseenter
on:mouseleave>
<div <div
bind:this={datePickerRef} bind:this={datePickerRef}
class={cx('--date-picker', short && '--date-picker--short', light && '--date-picker--light', datePickerType && `--date-picker--${datePickerType}`, datePickerType === 'range' && $labelTextEmpty && '--date-picker--nolabel')} {id}
{id}> class:bx--date-picker={true}
class:bx--date-picker--short={short}
class:bx--date-picker--light={light}
class="{datePickerType && `--date-picker--${datePickerType}`}
{datePickerType === 'range' && $labelTextEmpty && '--date-picker--nolabel'}">
<slot /> <slot />
</div> </div>
</div> </div>

View file

@ -1,22 +1,19 @@
<script> <script>
let className = undefined; export let type = "text";
export { className as class }; export let placeholder = "";
export let pattern = "\\d{1,2}\\/\\d{1,2}\\/\\d{4}";
export let disabled = false; export let disabled = false;
export let iconDescription = "";
export let id = "ccs-" + Math.random().toString(36);
export let labelText = "";
export let hideLabel = false; export let hideLabel = false;
export let iconDescription = '';
export let id = Math.random();
export let invalid = false; export let invalid = false;
export let invalidText = ''; export let invalidText = "";
export let labelText = '';
export let pattern = '\\d{1,2}\\/\\d{1,2}\\/\\d{4}';
export let placeholder = '';
export let style = undefined;
export let type = 'text';
export let name = undefined; export let name = undefined;
export let ref = null;
import { getContext, onMount } from 'svelte'; import { getContext, onMount } from "svelte";
import Calendar16 from 'carbon-icons-svelte/lib/Calendar16'; import Calendar16 from "carbon-icons-svelte/lib/Calendar16";
import { cx } from '../../lib';
const { const {
range, range,
@ -28,32 +25,33 @@
openCalendar, openCalendar,
focusCalendar, focusCalendar,
inputValue inputValue
} = getContext('DatePicker'); } = getContext("DatePicker");
let inputRef = undefined;
add({ id, labelText }); add({ id, labelText });
onMount(() => { onMount(() => {
declareRef({ id, ref: inputRef }); declareRef({ id, ref });
}); });
</script> </script>
<div <div
class={cx('--date-picker-container', !labelText && '--date-picker--nolabel', className)} class:bx--date-picker-container={true}
{style}> class:bx--date-picker--nolabel={!labelText}
{...$$restProps}>
{#if labelText} {#if labelText}
<label <label
class={cx('--label', hideLabel && '--visually-hidden', disabled && '--label--disabled')} for={id}
for={id}> class:bx--label={true}
class:bx--visually-hidden={hideLabel}
class:bx--label--disabled={disabled}>
{labelText} {labelText}
</label> </label>
{/if} {/if}
<div class={cx('--date-picker-input__wrapper')}> <div class:bx--date-picker-input__wrapper={true}>
<input <input
bind:this={inputRef} bind:this={ref}
data-invalid={invalid || undefined} data-invalid={invalid || undefined}
class={cx('--date-picker__input')} class:bx--date-picker__input={true}
on:input on:input
on:input={({ target }) => { on:input={({ target }) => {
updateValue({ type: 'input', value: target.value }); updateValue({ type: 'input', value: target.value });
@ -81,13 +79,13 @@
{#if $hasCalendar} {#if $hasCalendar}
<Calendar16 <Calendar16
role="img" role="img"
class={cx('--date-picker__icon')} class="bx--date-picker__icon"
aria-label={iconDescription} aria-label={iconDescription}
title={iconDescription} title={iconDescription}
on:click={openCalendar} /> on:click={openCalendar} />
{/if} {/if}
</div> </div>
{#if invalid} {#if invalid}
<div class={cx('--form-requirement')}>{invalidText}</div> <div class:bx--form-requirement={true}>{invalidText}</div>
{/if} {/if}
</div> </div>

View file

@ -1,40 +1,47 @@
import flatpickr from 'flatpickr'; import flatpickr from "flatpickr";
import l10n from 'flatpickr/dist/l10n/index.js'; import l10n from "flatpickr/dist/l10n/index.js";
import rangePlugin from 'flatpickr/dist/plugins/rangePlugin'; import rangePlugin from "flatpickr/dist/plugins/rangePlugin";
import { cx } from '../../lib';
function updateClasses(instance) { function updateClasses(instance) {
const { calendarContainer, days, daysContainer, weekdayContainer, selectedDates } = instance; const {
calendarContainer,
days,
daysContainer,
weekdayContainer,
selectedDates,
} = instance;
calendarContainer.classList.add(cx('--date-picker__calendar')); calendarContainer.classList.add("bx--date-picker__calendar");
calendarContainer.querySelector('.flatpickr-month').classList.add(cx('--date-picker__month')); calendarContainer
.querySelector(".flatpickr-month")
.classList.add("bx--date-picker__month");
weekdayContainer.classList.add(cx('--date-picker__weekdays')); weekdayContainer.classList.add("bx--date-picker__weekdays");
weekdayContainer.querySelectorAll('.flatpickr-weekday').forEach(node => { weekdayContainer.querySelectorAll(".flatpickr-weekday").forEach((node) => {
node.classList.add(cx('--date-picker__weekday')); node.classList.add("bx--date-picker__weekday");
}); });
daysContainer.classList.add(cx('--date-picker__days')); daysContainer.classList.add("bx--date-picker__days");
days.querySelectorAll('.flatpickr-day').forEach(node => { days.querySelectorAll(".flatpickr-day").forEach((node) => {
node.classList.add(cx('--date-picker__day')); node.classList.add("bx--date-picker__day");
if (node.classList.contains('today') && selectedDates.length > 0) { if (node.classList.contains("today") && selectedDates.length > 0) {
node.classList.add('no-border'); node.classList.add("no-border");
} else if (node.classList.contains('today') && selectedDates.length === 0) { } else if (node.classList.contains("today") && selectedDates.length === 0) {
node.classList.remove('no-border'); node.classList.remove("no-border");
} }
}); });
} }
function updateMonthNode(instance) { function updateMonthNode(instance) {
const monthText = instance.l10n.months.longhand[instance.currentMonth]; const monthText = instance.l10n.months.longhand[instance.currentMonth];
const staticMonthNode = instance.monthNav.querySelector('.cur-month'); const staticMonthNode = instance.monthNav.querySelector(".cur-month");
if (staticMonthNode) { if (staticMonthNode) {
staticMonthNode.textContent = monthText; staticMonthNode.textContent = monthText;
} else { } else {
const monthSelectNode = instance.monthsDropdownContainer; const monthSelectNode = instance.monthsDropdownContainer;
const span = document.createElement('span'); const span = document.createElement("span");
span.setAttribute('class', 'cur-month'); span.setAttribute("class", "cur-month");
span.textContent = monthText; span.textContent = monthText;
monthSelectNode.parentNode.replaceChild(span, monthSelectNode); monthSelectNode.parentNode.replaceChild(span, monthSelectNode);
} }
@ -43,10 +50,11 @@ function updateMonthNode(instance) {
function createCalendar({ options, base, input, dispatch }) { function createCalendar({ options, base, input, dispatch }) {
let locale = options.locale; let locale = options.locale;
if (options.locale === 'en' && l10n && l10n.en) { if (options.locale === "en" && l10n && l10n.en) {
l10n.en.weekdays.shorthand.forEach((_, index) => { l10n.en.weekdays.shorthand.forEach((_, index) => {
const shorthand = _.slice(0, 2); const shorthand = _.slice(0, 2);
l10n.en.weekdays.shorthand[index] = shorthand === 'Th' ? 'Th' : shorthand.charAt(0); l10n.en.weekdays.shorthand[index] =
shorthand === "Th" ? "Th" : shorthand.charAt(0);
}); });
locale = l10n.en; locale = l10n.en;
@ -58,27 +66,27 @@ function createCalendar({ options, base, input, dispatch }) {
disableMobile: true, disableMobile: true,
clickOpens: true, clickOpens: true,
locale, locale,
plugins: [options.mode === 'range' && new rangePlugin({ position: 'left', input })].filter( plugins: [
Boolean options.mode === "range" && new rangePlugin({ position: "left", input }),
), ].filter(Boolean),
nextArrow: nextArrow:
'<svg width="16px" height="16px" viewBox="0 0 16 16"><polygon points="11,8 6,13 5.3,12.3 9.6,8 5.3,3.7 6,3 "/><rect width="16" height="16" style="fill: none" /></svg>', '<svg width="16px" height="16px" viewBox="0 0 16 16"><polygon points="11,8 6,13 5.3,12.3 9.6,8 5.3,3.7 6,3 "/><rect width="16" height="16" style="fill: none" /></svg>',
prevArrow: prevArrow:
'<svg width="16px" height="16px" viewBox="0 0 16 16"><polygon points="5,8 10,3 10.7,3.7 6.4,8 10.7,12.3 10,13 "/><rect width="16" height="16" style="fill: none" /></svg>', '<svg width="16px" height="16px" viewBox="0 0 16 16"><polygon points="5,8 10,3 10.7,3.7 6.4,8 10.7,12.3 10,13 "/><rect width="16" height="16" style="fill: none" /></svg>',
onChange: () => { onChange: () => {
dispatch('change'); dispatch("change");
}, },
onClose: () => { onClose: () => {
dispatch('close'); dispatch("close");
}, },
onMonthChange: (s, d, instance) => { onMonthChange: (s, d, instance) => {
updateMonthNode(instance); updateMonthNode(instance);
}, },
onOpen: (s, d, instance) => { onOpen: (s, d, instance) => {
dispatch('open'); dispatch("open");
updateClasses(instance); updateClasses(instance);
updateMonthNode(instance); updateMonthNode(instance);
} },
}); });
} }

View file

@ -1,4 +1,2 @@
import DatePicker from './DatePicker.svelte'; export { default as DatePicker } from "./DatePicker.svelte";
export { default as DatePickerInput } from "./DatePickerInput.svelte";
export default DatePicker;
export { default as DatePickerInput } from './DatePickerInput.svelte';

View file

@ -1,20 +1,19 @@
<script> <script>
let className = undefined;
export { className as class };
export let inline = false; export let inline = false;
export let style = undefined;
import { cx } from '../../lib';
</script> </script>
<div <div
class:bx--skeleton={true}
class:bx--dropdown-v2={true}
class:bx--list-box={true}
class:bx--form-item={true}
class:bx--list-box--inline={inline}
{...$$restProps}
on:click on:click
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave>
class={cx('--skeleton', '--dropdown-v2', '--list-box', '--form-item', inline && '--list-box--inline', className)} <div role="button" class:bx--list-box__field={true}>
{style}> <span class:bx--list-box__label={true} />
<div role="button" class={cx('--list-box__field')}>
<span class={cx('--list-box__label')} />
</div> </div>
</div> </div>

View file

@ -1,19 +1,18 @@
<script> <script>
export let story = undefined; export let story = undefined;
import Layout from '../../internal/ui/Layout.svelte'; import Layout from "../../internal/ui/Layout.svelte";
import Button from '../Button'; import { Button } from "../Button";
import Dropdown from './Dropdown.svelte'; import Dropdown from "./Dropdown.svelte";
import DropdownSkeleton from './Dropdown.Skeleton.svelte'; import DropdownSkeleton from "./Dropdown.Skeleton.svelte";
let items = [ $: items = [
{ id: 'option-0', text: 'Option 1' }, { id: "option-0", text: "Option 1" },
{ id: 'option-1', text: 'Option 2' }, { id: "option-1", text: "Option 2" },
{ id: 'option-2', text: 'Option 3' }, { id: "option-2", text: "Option 3" },
{ id: 'option-3', text: 'Option 4' } { id: "option-3", text: "Option 4" }
]; ];
$: selectedIndex = -1;
let selectedIndex = -1;
</script> </script>
<Layout> <Layout>

View file

@ -1,35 +1,41 @@
import { withKnobs, select, text, boolean } from '@storybook/addon-knobs'; import { withKnobs, select, text, boolean } from "@storybook/addon-knobs";
import Component from './Dropdown.Story.svelte'; import Component from "./Dropdown.Story.svelte";
export default { title: 'Dropdown', decorators: [withKnobs] }; export default { title: "Dropdown", decorators: [withKnobs] };
const types = { const types = {
'Default (default)': 'default', "Default (default)": "default",
'Inline (inline)': 'inline' "Inline (inline)": "inline",
}; };
const sizes = { const sizes = {
'Extra large size (xl)': 'xl', "Extra large size (xl)": "xl",
'Regular size (lg)': '', "Regular size (lg)": "",
'Small size (sm)': 'sm' "Small size (sm)": "sm",
}; };
export const Default = () => ({ export const Default = () => ({
Component, Component,
props: { props: {
id: text('Dropdown id', 'carbon-dropdown-id'), id: text("Dropdown id", "carbon-dropdown-id"),
name: text('Dropdown name', 'carbon-dropdown-name'), name: text("Dropdown name", "carbon-dropdown-name"),
type: select('Dropdown type (type)', types, 'default'), type: select("Dropdown type (type)", types, "default"),
size: select('Field size (size)', sizes, '') || undefined, size: select("Field size (size)", sizes, "") || undefined,
label: text('Label (label)', 'Dropdown menu options'), label: text("Label (label)", "Dropdown menu options"),
'aria-label': text('Aria Label (aria-label)', 'Dropdown'), "aria-label": text("Aria Label (aria-label)", "Dropdown"),
disabled: boolean('Disabled (disabled)', false), disabled: boolean("Disabled (disabled)", false),
light: boolean('Light variant (light)', false), light: boolean("Light variant (light)", false),
titleText: text('Title (titleText)', 'This is not a dropdown title.'), titleText: text("Title (titleText)", "This is not a dropdown title."),
helperText: text('Helper text (helperText)', 'This is not some helper text.'), helperText: text(
invalid: boolean('Show form validation UI (invalid)', false), "Helper text (helperText)",
invalidText: text('Form validation UI content (invalidText)', 'A valid value is required') "This is not some helper text."
} ),
invalid: boolean("Show form validation UI (invalid)", false),
invalidText: text(
"Form validation UI content (invalidText)",
"A valid value is required"
),
},
}); });
export const Skeleton = () => ({ Component, props: { story: 'skeleton' } }); export const Skeleton = () => ({ Component, props: { story: "skeleton" } });

View file

@ -1,40 +1,35 @@
<script> <script>
let className = undefined; export let selectedIndex = -1;
export { className as class }; export let open = false;
export let disabled = false;
export let helperText = '';
export let id = Math.random();
export let name = undefined;
export let inline = false; export let inline = false;
export let light = false;
export let disabled = false;
export let invalid = false; export let invalid = false;
export let invalidText = '';
export let items = []; export let items = [];
export let itemToString = item => item.text || item.id; export let itemToString = item => item.text || item.id;
export let type = "default"; // "default" | "inline"
export let size = undefined; // "sm" | "lg" | "xl"
export let id = "ccs-" + Math.random().toString(36);
export let name = undefined;
export let invalidText = "";
export let helperText = "";
export let label = undefined; export let label = undefined;
export let light = false; export let titleText = "";
export let open = false;
export let selectedIndex = -1;
export let size = undefined;
export let style = undefined;
export let titleText = '';
export let translateWithId = undefined; export let translateWithId = undefined;
export let type = 'default'; export let ref = null;
import { setContext } from 'svelte'; import { setContext } from "svelte";
import WarningFilled16 from 'carbon-icons-svelte/lib/WarningFilled16'; import WarningFilled16 from "carbon-icons-svelte/lib/WarningFilled16";
import { cx } from '../../lib'; import {
import ListBox, { ListBoxField, ListBoxMenu, ListBoxMenuIcon, ListBoxMenuItem } from '../ListBox'; ListBox,
ListBoxMenu,
ListBoxMenuIcon,
ListBoxMenuItem
} from "../ListBox";
let selectedId = undefined; let selectedId = undefined;
let fieldRef = undefined;
let highlightedIndex = -1; let highlightedIndex = -1;
setContext('Dropdown', {
declareRef: ({ ref }) => {
fieldRef = ref;
}
});
function change(direction) { function change(direction) {
let index = highlightedIndex + direction; let index = highlightedIndex + direction;
@ -47,7 +42,7 @@
highlightedIndex = index; highlightedIndex = index;
} }
$: inline = type === 'inline'; $: inline = type === "inline";
$: selectedItem = items[selectedIndex]; $: selectedItem = items[selectedIndex];
$: if (!open) { $: if (!open) {
highlightedIndex = -1; highlightedIndex = -1;
@ -56,19 +51,27 @@
<svelte:body <svelte:body
on:click={({ target }) => { on:click={({ target }) => {
if (open && fieldRef && !fieldRef.contains(target)) { if (open && ref && !ref.contains(target)) {
open = false; open = false;
} }
}} /> }} />
<div <div
class={cx('--dropdown__wrapper', '--list-box__wrapper', inline && '--dropdown__wrapper--inline', inline && '--list-box__wrapper--inline', inline && invalid && '--dropdown__wrapper--inline--invalid', inline && invalid && '--list-box__wrapper--inline--invalid', className)} class:bx--dropdown__wrapper={true}
{style}> class:bx--list-box__wrapper={true}
class:bx--dropdown__wrapper--inline={inline}
class:bx--list-box__wrapper--inline={inline}
class:bx--dropdown__wrapper--inline--invalid={inline && invalid}
{...$$restProps}>
{#if titleText} {#if titleText}
<label for={id} class={cx('--label', disabled && '--label--disabled')}>{titleText}</label> <label for={id} class:bx--label={true} class:bx--label--disabled={disabled}>
{titleText}
</label>
{/if} {/if}
{#if !inline && helperText} {#if !inline && helperText}
<div class={cx('--form__helper-text', disabled && '--form__helper-text--disabled')}> <div
class:bx--form__helper-text={true}
class:bx--form__helper-text--disabled={disabled}>
{helperText} {helperText}
</div> </div>
{/if} {/if}
@ -78,9 +81,13 @@
{id} {id}
{name} {name}
aria-label={$$props['aria-label']} aria-label={$$props['aria-label']}
class={cx('--dropdown', invalid && '--dropdown--invalid', open && '--dropdown--open', inline && '--dropdown--inline', disabled && '--dropdown--disabled', light && '--dropdown--light')} class="bx--dropdown {invalid && 'bx--dropdown--invalid'}
{open && 'bx--dropdown--open'}
{inline && 'bx--dropdown--inline'}
{disabled && 'bx--dropdown--disabled'}
{light && 'bx--dropdown--light'}"
on:click={({ target }) => { on:click={({ target }) => {
open = fieldRef.contains(target) ? !open : false; open = ref.contains(target) ? !open : false;
}} }}
{disabled} {disabled}
{open} {open}
@ -88,9 +95,11 @@
{invalidText} {invalidText}
{light}> {light}>
{#if invalid} {#if invalid}
<WarningFilled16 class={cx('--list-box__invalid-icon')} /> <WarningFilled16 class="bx--list-box__invalid-icon" />
{/if} {/if}
<ListBoxField <button
bind:this={ref}
class:bx--list-box__field={true}
tabindex="0" tabindex="0"
role="button" role="button"
aria-expanded={open} aria-expanded={open}
@ -103,7 +112,7 @@
} }
} else if (key === 'Tab') { } else if (key === 'Tab') {
open = false; open = false;
fieldRef.blur(); ref.blur();
} else if (key === 'ArrowDown') { } else if (key === 'ArrowDown') {
change(1); change(1);
} else if (key === 'ArrowUp') { } else if (key === 'ArrowUp') {
@ -112,17 +121,17 @@
}} }}
on:blur={({ relatedTarget }) => { on:blur={({ relatedTarget }) => {
if (relatedTarget) { if (relatedTarget) {
fieldRef.focus(); ref.focus();
} }
}} }}
{disabled} {disabled}
{translateWithId} {translateWithId}
{id}> {id}>
<span class={cx('--list-box__label')}> <span class="bx--list-box__label">
{#if selectedItem}{itemToString(selectedItem)}{:else}{label}{/if} {#if selectedItem}{itemToString(selectedItem)}{:else}{label}{/if}
</span> </span>
<ListBoxMenuIcon {open} {translateWithId} /> <ListBoxMenuIcon {open} {translateWithId} />
</ListBoxField> </button>
{#if open} {#if open}
<ListBoxMenu aria-labelledby={id} {id}> <ListBoxMenu aria-labelledby={id} {id}>
{#each items as item, i (item.id || i)} {#each items as item, i (item.id || i)}

View file

@ -1,4 +1,2 @@
import Dropdown from './Dropdown.svelte'; export { default as Dropdown } from "./Dropdown.svelte";
export { default as DropdownSkeleton } from "./Dropdown.Skeleton.svelte";
export default Dropdown;
export { default as DropdownSkeleton } from './Dropdown.Skeleton.svelte';

View file

@ -1,15 +1,16 @@
<script> <script>
let className = undefined; import { ButtonSkeleton } from "../Button";
export { className as class }; import { SkeletonText } from "../SkeletonText";
export let style = undefined;
import { cx } from '../../lib';
import { ButtonSkeleton } from '../Button';
import SkeletonText from '../SkeletonText';
</script> </script>
<div on:click on:mouseover on:mouseenter on:mouseleave class={cx('--form-item', className)} {style}> <div
class:bx--form-item={true}
{...$$restProps}
on:click
on:mouseover
on:mouseenter
on:mouseleave>
<SkeletonText heading width="100px" /> <SkeletonText heading width="100px" />
<SkeletonText width="225px" class={cx('--label-description')} /> <SkeletonText width="225px" class="bx--label-description" />
<ButtonSkeleton /> <ButtonSkeleton />
</div> </div>

View file

@ -1,18 +1,16 @@
<script> <script>
export let story = undefined; export let story = undefined;
import Layout from '../../internal/ui/Layout.svelte'; import Layout from "../../internal/ui/Layout.svelte";
import { cx } from '../../lib'; import { Button } from "../Button";
import Button from '../Button'; import FileUploader from "./FileUploader.svelte";
import FileUploader from './FileUploader.svelte'; import FileUploaderButton from "./FileUploaderButton.svelte";
import FileUploaderButton from './FileUploaderButton.svelte'; import FileUploaderDropContainer from "./FileUploaderDropContainer.svelte";
import FileUploaderDropContainer from './FileUploaderDropContainer.svelte'; import FileUploaderItem from "./FileUploaderItem.svelte";
import FileUploaderItem from './FileUploaderItem.svelte'; import FileUploaderSkeleton from "./FileUploader.Skeleton.svelte";
import FileUploaderSkeleton from './FileUploader.Skeleton.svelte';
let fileUploader = undefined;
let files = [];
$: fileUploader = undefined;
$: files = [];
$: disabled = files.length === 0; $: disabled = files.length === 0;
</script> </script>
@ -36,7 +34,7 @@
console.log('click'); console.log('click');
}} /> }} />
{:else if story === 'uploader'} {:else if story === 'uploader'}
<div class={cx('--file__container')}> <div class="bx--file__container">
<FileUploader <FileUploader
bind:this={fileUploader} bind:this={fileUploader}
{...$$props} {...$$props}

View file

@ -1,98 +1,116 @@
import { withKnobs, text, select, boolean, array } from '@storybook/addon-knobs'; import {
import Component from './FileUploader.Story.svelte'; withKnobs,
text,
select,
boolean,
array,
} from "@storybook/addon-knobs";
import Component from "./FileUploader.Story.svelte";
export default { title: 'FileUploader', decorators: [withKnobs] }; export default { title: "FileUploader", decorators: [withKnobs] };
const buttonKinds = { const buttonKinds = {
'Primary (primary)': 'primary', "Primary (primary)": "primary",
'Secondary (secondary)': 'secondary', "Secondary (secondary)": "secondary",
'Danger (danger)': 'danger', "Danger (danger)": "danger",
'Ghost (ghost)': 'ghost', "Ghost (ghost)": "ghost",
'Tertiary (tertiary)': 'tertiary' "Tertiary (tertiary)": "tertiary",
}; };
const filenameStatuses = { const filenameStatuses = {
'Edit (edit)': 'edit', "Edit (edit)": "edit",
'Complete (complete)': 'complete', "Complete (complete)": "complete",
'Uploading (uploading)': 'uploading' "Uploading (uploading)": "uploading",
}; };
export const FileUploaderButton = () => ({ export const FileUploaderButton = () => ({
Component, Component,
props: { props: {
story: 'button', story: "button",
kind: select('Button kind (kind)', buttonKinds, 'primary'), kind: select("Button kind (kind)", buttonKinds, "primary"),
labelText: text('Label text (labelText)', 'Add files'), labelText: text("Label text (labelText)", "Add files"),
name: text('Form item name: (name)', ''), name: text("Form item name: (name)", ""),
multiple: boolean('Supports multiple files (multiple)', true), multiple: boolean("Supports multiple files (multiple)", true),
disabled: boolean('Disabled (disabled)', false), disabled: boolean("Disabled (disabled)", false),
disableLabelChanges: boolean( disableLabelChanges: boolean(
'Prevent the label from being replaced with file selected file (disableLabelChanges)', "Prevent the label from being replaced with file selected file (disableLabelChanges)",
false false
), ),
role: text('ARIA role of the button (role)', 'button'), role: text("ARIA role of the button (role)", "button"),
tabindex: text('Tab index (tabindex)', '0') tabindex: text("Tab index (tabindex)", "0"),
} },
}); });
FileUploaderButton.story = { name: 'FileUploaderButton' }; FileUploaderButton.story = { name: "FileUploaderButton" };
export const FileUploader = () => ({ export const FileUploader = () => ({
Component, Component,
props: { props: {
story: 'uploader', story: "uploader",
labelTitle: text('The label title (labelTitle)', 'Upload'), labelTitle: text("The label title (labelTitle)", "Upload"),
labelDescription: text( labelDescription: text(
'The label description (labelDescription)', "The label description (labelDescription)",
'only .jpg files at 500mb or less' "only .jpg files at 500mb or less"
), ),
buttonLabel: text('The button label (buttonLabel)', 'Add files'), buttonLabel: text("The button label (buttonLabel)", "Add files"),
status: select('Status for file name (status)', filenameStatuses, 'edit'), status: select("Status for file name (status)", filenameStatuses, "edit"),
accept: array('Accepted file extensions (accept)', ['.jpg', '.png'], ','), accept: array("Accepted file extensions (accept)", [".jpg", ".png"], ","),
name: text('Form item name: (name)', ''), name: text("Form item name: (name)", ""),
multiple: boolean('Supports multiple files (multiple)', true), multiple: boolean("Supports multiple files (multiple)", true),
iconDescription: text('Close button icon description (iconDescription)', 'Clear file') iconDescription: text(
} "Close button icon description (iconDescription)",
"Clear file"
),
},
}); });
FileUploader.story = { name: 'FileUploader' }; FileUploader.story = { name: "FileUploader" };
export const FileUploaderItem = () => ({ export const FileUploaderItem = () => ({
Component, Component,
props: { props: {
story: 'item', story: "item",
name: text('Filename (name)', 'README.md'), name: text("Filename (name)", "README.md"),
status: select('Status for file name (status)', filenameStatuses, 'edit'), status: select("Status for file name (status)", filenameStatuses, "edit"),
iconDescription: text('Close button icon description (iconDescription)', 'Clear file'), iconDescription: text(
invalid: boolean('Invalid (invalid)', false), "Close button icon description (iconDescription)",
errorSubject: text('Error subject (errorSubject)', 'File size exceeds limit'), "Clear file"
),
invalid: boolean("Invalid (invalid)", false),
errorSubject: text(
"Error subject (errorSubject)",
"File size exceeds limit"
),
errorBody: text( errorBody: text(
'Error body (errorBody)', "Error body (errorBody)",
'500kb max file size. Select a new file and try again.' "500kb max file size. Select a new file and try again."
) ),
} },
}); });
FileUploaderItem.story = { name: 'FileUploaderItem' }; FileUploaderItem.story = { name: "FileUploaderItem" };
export const FileUploaderDropContainer = () => ({ export const FileUploaderDropContainer = () => ({
Component, Component,
props: { props: {
story: 'drop container', story: "drop container",
labelText: text('Label text (labelText)', 'Drag and drop files here or click to upload'), labelText: text(
name: text('Form item name (name)', ''), "Label text (labelText)",
multiple: boolean('Supports multiple files (multiple)', true), "Drag and drop files here or click to upload"
accept: array(
'Accepted MIME types or file extensions (accept)',
['image/jpeg', 'image/png'],
','
), ),
disabled: boolean('Disabled (disabled)', false), name: text("Form item name (name)", ""),
role: text('ARIA role of the button (role)', ''), multiple: boolean("Supports multiple files (multiple)", true),
tabindex: text('Tab index (tabindex)', '0') accept: array(
} "Accepted MIME types or file extensions (accept)",
["image/jpeg", "image/png"],
","
),
disabled: boolean("Disabled (disabled)", false),
role: text("ARIA role of the button (role)", ""),
tabindex: text("Tab index (tabindex)", "0"),
},
}); });
FileUploaderDropContainer.story = { name: 'FileUploaderDropContainer' }; FileUploaderDropContainer.story = { name: "FileUploaderDropContainer" };
export const Skeleton = () => ({ Component, props: { story: 'skeleton' } }); export const Skeleton = () => ({ Component, props: { story: "skeleton" } });

View file

@ -1,62 +1,63 @@
<script> <script>
let className = undefined; export let status = "uploading"; // "uploading" | "edit" | "complete"
export { className as class };
export const clearFiles = () => (files = []);
export let accept = []; export let accept = [];
export let buttonLabel = '';
export let files = []; export let files = [];
export let iconDescription = 'Provide icon description'; export const clearFiles = () => (files = []);
export let kind = 'primary'; export let buttonLabel = "";
export let labelDescription = ''; export let iconDescription = "Provide icon description";
export let labelTitle = ''; export let kind = "primary";
export let labelDescription = "";
export let labelTitle = "";
export let multiple = false; export let multiple = false;
export let name = ''; export let name = "";
export let status = 'uploading';
export let style = undefined;
import { createEventDispatcher, afterUpdate } from 'svelte'; import { createEventDispatcher, afterUpdate } from "svelte";
import { cx } from '../../lib'; import Filename from "./Filename.svelte";
import Filename from './Filename.svelte'; import FileUploaderButton from "./FileUploaderButton.svelte";
import FileUploaderButton from './FileUploaderButton.svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
let prevFiles = []; $: prevFiles = [];
afterUpdate(() => { afterUpdate(() => {
if (files.length > prevFiles.length) { if (files.length > prevFiles.length) {
dispatch('add', files); dispatch("add", files);
} else { } else {
dispatch( dispatch("remove", prevFiles.filter(_ => !files.includes(_)));
'remove',
prevFiles.filter(_ => !files.includes(_))
);
} }
prevFiles = [...files]; prevFiles = [...files];
}); });
</script> </script>
<div on:click on:mouseover on:mouseenter on:mouseleave class={cx('--form-item', className)} {style}> <div
<strong class={cx('--file--label')}>{labelTitle}</strong> class:bx--form-item={true}
<p class={cx('--label-description')}>{labelDescription}</p> {...$$restProps}
on:click
on:mouseover
on:mouseenter
on:mouseleave>
<strong class:bx--file--label={true}>{labelTitle}</strong>
<p class:bx--label-description={true}>{labelDescription}</p>
<FileUploaderButton <FileUploaderButton
disableLabelChanges disableLabelChanges
labelText={buttonLabel} labelText={buttonLabel}
on:change
on:change={({ target }) => {
files = [...target.files].map(({ name }) => name);
}}
{accept} {accept}
{name} {name}
{multiple} {multiple}
{kind} /> {kind}
<div class={cx('--file-container')}> on:change
on:change={({ target }) => {
files = [...target.files].map(({ name }) => name);
}} />
<div class:bx--file-container={true}>
{#each files as name, i (name)} {#each files as name, i (name)}
<span class={cx('--file__selected-file')}> <span class:bx--file__selected-file={true}>
<p class={cx('--file-filename')}>{name}</p> <p class:bx--file-filename={true}>{name}</p>
<span class={cx('--file__state-container')}> <span class:bx--file__state-container={true}>
<Filename <Filename
{iconDescription}
{status}
on:keydown on:keydown
on:keydown={({ key }) => { on:keydown={({ key }) => {
if (key === ' ' || key === 'Enter') { if (key === ' ' || key === 'Enter') {
@ -66,9 +67,7 @@
on:click on:click
on:click={() => { on:click={() => {
files = files.filter((_, index) => index !== i); files = files.filter((_, index) => index !== i);
}} }} />
{iconDescription}
{status} />
</span> </span>
</span> </span>
{/each} {/each}

View file

@ -1,42 +1,44 @@
<script> <script>
let className = undefined;
export { className as class };
export let accept = []; export let accept = [];
export let multiple = false;
export let disabled = false; export let disabled = false;
export let disableLabelChanges = false; export let disableLabelChanges = false;
export let id = Math.random(); export let kind = "primary"; // Button.kind
export let kind = 'primary'; export let labelText = "Add file";
export let labelText = 'Add file'; export let role = "button";
export let multiple = false; export let tabindex = "0";
export let name = ''; export let id = "ccs-" + Math.random().toString(36);
export let role = 'button'; export let name = "";
export let style = undefined; export let ref = null;
export let tabindex = '0';
import { cx } from '../../lib';
let inputRef = undefined;
</script> </script>
<label <label
tabindex={disabled ? '-1' : tabindex}
aria-disabled={disabled} aria-disabled={disabled}
class={cx('--btn', '--btn--sm', kind && `--btn--${kind}`, disabled && '--btn--disabled', className)}
for={id} for={id}
tabindex={disabled ? '-1' : tabindex}
class:bx--btn={true}
class:bx--btn--sm={true}
class:bx--btn--disabled={disabled}
class={kind && `bx--btn--${kind}`}
on:keydown on:keydown
on:keydown={({ key }) => { on:keydown={({ key }) => {
if (key === ' ' || key === 'Enter') { if (key === ' ' || key === 'Enter') {
inputRef.click(); ref.click();
} }
}} }}>
{style}>
<span {role}>{labelText}</span> <span {role}>{labelText}</span>
</label> </label>
<input <input
bind:this={inputRef} bind:this={ref}
type="file" type="file"
tabindex="-1" tabindex="-1"
class={cx('--visually-hidden')} {accept}
{disabled}
{id}
{multiple}
{name}
class:bx--visually-hidden={true}
{...$$restProps}
on:change|stopPropagation on:change|stopPropagation
on:change|stopPropagation={({ target }) => { on:change|stopPropagation={({ target }) => {
const files = target.files; const files = target.files;
@ -48,9 +50,4 @@
on:click on:click
on:click={({ target }) => { on:click={({ target }) => {
target.value = null; target.value = null;
}} }} />
{id}
{disabled}
{multiple}
{accept}
{name} />

View file

@ -1,28 +1,25 @@
<script> <script>
let className = undefined;
export { className as class };
export let accept = []; export let accept = [];
export let disabled = false; export let disabled = false;
export let id = Math.random();
export let labelText = 'Add file';
export let multiple = false; export let multiple = false;
export let name = '';
export let role = 'button';
export let style = undefined;
export let tabindex = '0';
export let validateFiles = files => files; export let validateFiles = files => files;
export let labelText = "Add file";
export let role = "button";
export let tabindex = "0";
export let id = "ccs-" + Math.random().toString(36);
export let name = "";
export let ref = null;
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from "svelte";
import { cx } from '../../lib';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
let over = false; $: over = false;
let inputRef = undefined;
</script> </script>
<div <div
class={cx('--file')} class:bx--file={true}
{...$$restProps}
on:dragover on:dragover
on:dragover|preventDefault|stopPropagation={({ dataTransfer }) => { on:dragover|preventDefault|stopPropagation={({ dataTransfer }) => {
if (!disabled) { if (!disabled) {
@ -43,27 +40,33 @@
over = false; over = false;
dispatch('add', validateFiles(dataTransfer.files)); dispatch('add', validateFiles(dataTransfer.files));
} }
}} }}>
{style}>
<label <label
class={cx('--file-browse-btn', disabled && '--file-browse-btn--disabled')}
for={id} for={id}
{tabindex}
class:bx--file-browse-btn={true}
class:bx--file-browse-btn--disabled={disabled}
on:keydown on:keydown
on:keydown={({ key }) => { on:keydown={({ key }) => {
if (key === ' ' || key === 'Enter') { if (key === ' ' || key === 'Enter') {
inputRef.click(); ref.click();
} }
}} }}>
{tabindex}>
<div <div
class={cx('--file__drop-container', over && '--file__drop-container--drag-over', className)} {role}
{role}> class:bx--file__drop-container={true}
class:bx--file__drop-container--drag-over={over}>
{labelText} {labelText}
<input <input
bind:this={inputRef} bind:this={ref}
type="file" type="file"
tabindex="-1" tabindex="-1"
class={cx('--file-input')} {id}
{disabled}
{accept}
{name}
{multiple}
class:bx--file-input={true}
on:change on:change
on:change={({ target }) => { on:change={({ target }) => {
dispatch('add', validateFiles(target.files)); dispatch('add', validateFiles(target.files));
@ -71,12 +74,7 @@
on:click on:click
on:click={({ target }) => { on:click={({ target }) => {
target.value = null; target.value = null;
}} }} />
{id}
{disabled}
{accept}
{name}
{multiple} />
</div> </div>
</label> </label>
</div> </div>

View file

@ -1,30 +1,27 @@
<script> <script>
let className = undefined; export let errorBody = "";
export { className as class }; export let errorSubject = "";
export let errorBody = ''; export let iconDescription = "";
export let errorSubject = ''; export let id = "ccs-" + Math.random().toString(36);
export let iconDescription = '';
export let id = Math.random();
export let invalid = false; export let invalid = false;
export let name = ''; export let name = "";
export let status = 'uploading'; export let status = "uploading";
export let style = undefined;
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from "svelte";
import { cx } from '../../lib'; import Filename from "./Filename.svelte";
import Filename from './Filename.svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
</script> </script>
<span <span
class:bx--file__selected-file={true}
class:bx--file__selected-file--invalid={invalid}
{...$$restProps}
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave>
class={cx('--file__selected-file', invalid && '--file__selected-file--invalid', className)} <p class:bx--file-filename={true}>{name}</p>
{style}> <span class:bx--file__state-container={true}>
<p class={cx('--file-filename')}>{name}</p>
<span class={cx('--file__state-container')}>
<Filename <Filename
on:keydown={({ key }) => { on:keydown={({ key }) => {
if (key === ' ' || key === 'Enter') { if (key === ' ' || key === 'Enter') {
@ -39,10 +36,10 @@
{invalid} /> {invalid} />
</span> </span>
{#if invalid && errorSubject} {#if invalid && errorSubject}
<div class={cx('--form-requirement')}> <div class:bx--form-requirement={true}>
<div class={cx('--form-requirement__title')}>{errorSubject}</div> <div class:bx--form-requirement__title={true}>{errorSubject}</div>
{#if errorBody} {#if errorBody}
<p class={cx('--form-requirement__supplement')}>{errorBody}</p> <p class:bx--form-requirement__supplement={true}>{errorBody}</p>
{/if} {/if}
</div> </div>
{/if} {/if}

View file

@ -1,42 +1,39 @@
<script> <script>
let className = undefined; export let iconDescription = "";
export { className as class };
export let iconDescription = '';
export let invalid = false; export let invalid = false;
export let status = 'uploading'; export let status = "uploading"; // "uploading" | "edit" | "complete"
export let style = undefined;
export let tabindex = '0';
import Close16 from 'carbon-icons-svelte/lib/Close16'; import Close16 from "carbon-icons-svelte/lib/Close16";
import CheckmarkFilled16 from 'carbon-icons-svelte/lib/CheckmarkFilled16'; import CheckmarkFilled16 from "carbon-icons-svelte/lib/CheckmarkFilled16";
import WarningFilled16 from 'carbon-icons-svelte/lib/WarningFilled16'; import WarningFilled16 from "carbon-icons-svelte/lib/WarningFilled16";
import { cx } from '../../lib'; import { Loading } from "../Loading";
import Loading from '../Loading';
</script> </script>
{#if status === 'uploading'} {#if status === 'uploading'}
<Loading small description={iconDescription} withOverlay={false} class={className} {style} /> <Loading
description={iconDescription}
{...$$restProps}
small
withOverlay={false} />
{/if} {/if}
{#if status === 'edit'} {#if status === 'edit'}
{#if invalid} {#if invalid}
<WarningFilled16 class={cx('--file-invalid')} /> <WarningFilled16 class="bx--file-invalid" />
{/if} {/if}
<Close16 <Close16
class={cx('--file-close', className)}
aria-label={iconDescription} aria-label={iconDescription}
title={iconDescription} title={iconDescription}
class="bx--file-close"
{...$$restProps}
on:click on:click
on:keydown on:keydown />
{tabindex}
{style} />
{/if} {/if}
{#if status === 'complete'} {#if status === 'complete'}
<CheckmarkFilled16 <CheckmarkFilled16
class={cx('--file-complete', className)}
aria-label={iconDescription} aria-label={iconDescription}
title={iconDescription} title={iconDescription}
{tabindex} class="bx--file-complete"
{style} /> {...$$restProps} />
{/if} {/if}

View file

@ -1,7 +1,5 @@
import FileUploader from './FileUploader.svelte'; export { default as FileUploader } from "./FileUploader.svelte";
export { default as FileUploaderButton } from "./FileUploaderButton.svelte";
export default FileUploader; export { default as FileUploaderItem } from "./FileUploaderItem.svelte";
export { default as FileUploaderButton } from './FileUploaderButton.svelte'; export { default as FileUploaderDropContainer } from "./FileUploaderDropContainer.svelte";
export { default as FileUploaderItem } from './FileUploaderItem.svelte'; export { default as Filename } from "./Filename.svelte";
export { default as FileUploaderDropContainer } from './FileUploaderDropContainer.svelte';
export { default as Filename } from './Filename.svelte';

View file

@ -1,18 +1,18 @@
<script> <script>
import Layout from '../../internal/ui/Layout.svelte'; import Layout from "../../internal/ui/Layout.svelte";
import Checkbox from '../Checkbox'; import { Checkbox } from "../Checkbox";
import FormGroup from '../FormGroup'; import { FormGroup } from "../FormGroup";
import FileUploader from '../FileUploader'; import { FileUploader } from "../FileUploader";
import NumberInput from '../NumberInput'; import { NumberInput } from "../NumberInput";
import RadioButton from '../RadioButton'; import { RadioButton } from "../RadioButton";
import RadioButtonGroup from '../RadioButtonGroup'; import { RadioButtonGroup } from "../RadioButtonGroup";
import Button from '../Button'; import { Button } from "../Button";
import Search from '../Search'; import { Search } from "../Search";
import Select, { SelectItem } from '../Select'; import { Select, SelectItem } from "../Select";
import TextArea from '../TextArea'; import { TextArea } from "../TextArea";
import TextInput from '../TextInput'; import { TextInput } from "../TextInput";
import Toggle from '../Toggle'; import { Toggle } from "../Toggle";
import Form from './Form.svelte'; import Form from "./Form.svelte";
</script> </script>
<Layout> <Layout>
@ -25,42 +25,74 @@
<Checkbox id="checkbox-1" labelText="Checkbox Label" /> <Checkbox id="checkbox-1" labelText="Checkbox Label" />
<Checkbox id="checkbox-2" labelText="Checkbox Label" disabled /> <Checkbox id="checkbox-2" labelText="Checkbox Label" disabled />
</FormGroup> </FormGroup>
<NumberInput id="number-input-1" label="Number Input" min={0} max={100} value={50} step={10} /> <NumberInput
id="number-input-1"
label="Number Input"
min={0}
max={100}
value={50}
step={10} />
<FormGroup legendText="Toggle heading"> <FormGroup legendText="Toggle heading">
<Toggle id="toggle-1" /> <Toggle id="toggle-1" />
<Toggle id="toggle-2" disabled /> <Toggle id="toggle-2" disabled />
</FormGroup> </FormGroup>
<FormGroup legendText="File Uploader"> <FormGroup legendText="File Uploader">
<FileUploader id="file-1" buttonLabel="Add files" labelDescription="Choose Files..." /> <FileUploader
id="file-1"
buttonLabel="Add files"
labelDescription="Choose Files..." />
</FormGroup> </FormGroup>
<FormGroup legendText="Radio Button heading"> <FormGroup legendText="Radio Button heading">
<RadioButtonGroup name="radio-button-group" defaultSelected="default-selected"> <RadioButtonGroup
<RadioButton id="radio-1" value="standard" labelText="Standard Radio Button" /> name="radio-button-group"
defaultSelected="default-selected">
<RadioButton
id="radio-1"
value="standard"
labelText="Standard Radio Button" />
<RadioButton <RadioButton
id="radio-2" id="radio-2"
value="default-selected" value="default-selected"
labelText="Default Selected Radio Button" /> labelText="Default Selected Radio Button" />
<RadioButton id="radio-3" value="blue" labelText="Standard Radio Button" /> <RadioButton
<RadioButton id="radio-4" value="disabled" labelText="Disabled Radio Button" disabled /> id="radio-3"
value="blue"
labelText="Standard Radio Button" />
<RadioButton
id="radio-4"
value="disabled"
labelText="Disabled Radio Button"
disabled />
</RadioButtonGroup> </RadioButtonGroup>
</FormGroup> </FormGroup>
<FormGroup legendText="Search"> <FormGroup legendText="Search">
<Search id="search-1" labelText="Search" placeholder="Search" /> <Search id="search-1" labelText="Search" placeholder="Search" />
</FormGroup> </FormGroup>
<Select id="select-1" defaultValue="placeholder-item"> <Select id="select-1" defaultValue="placeholder-item">
<SelectItem disabled hidden value="placeholder-item" text="Choose an option" /> <SelectItem
disabled
hidden
value="placeholder-item"
text="Choose an option" />
<SelectItem value="option-1" text="Option 1" /> <SelectItem value="option-1" text="Option 1" />
<SelectItem value="option-2" text="Option 2" /> <SelectItem value="option-2" text="Option 2" />
<SelectItem value="option-3" text="Option 3" /> <SelectItem value="option-3" text="Option 3" />
</Select> </Select>
<TextInput id="text-input-1" labelText="Text Input label" placeholder="Placeholder text" /> <TextInput
<TextInput id="text-input-2" type="password" labelText="Password" required /> id="text-input-1"
labelText="Text Input label"
placeholder="Placeholder text" />
<TextInput
id="text-input-2"
type="password"
labelText="Password"
required />
<TextInput <TextInput
id="text-input-3" id="text-input-3"
type="password" type="password"
labelText="Password" labelText="Password"
invalidText="Your password must be at least 6 characters as well as contain at least one invalidText="Your password must be at least 6 characters as well as
uppercase, one lowercase, and one number." contain at least one uppercase, one lowercase, and one number."
required required
invalid /> invalid />
<TextArea <TextArea

View file

@ -1,14 +1,14 @@
import { withKnobs, text, boolean } from '@storybook/addon-knobs'; import { withKnobs, text, boolean } from "@storybook/addon-knobs";
import Component from './Form.Story.svelte'; import Component from "./Form.Story.svelte";
export default { title: 'Form', decorators: [withKnobs] }; export default { title: "Form", decorators: [withKnobs] };
export const Default = () => ({ export const Default = () => ({
Component, Component,
props: { props: {
legendText: text('Text in <legend> (legendText)', 'Checkbox heading'), legendText: text("Text in <legend> (legendText)", "Checkbox heading"),
message: boolean('Show form requirement (message)', false), message: boolean("Show form requirement (message)", false),
messageText: text('Form requirement text (messageText)', ''), messageText: text("Form requirement text (messageText)", ""),
invalid: boolean('Mark as invalid (invalid)', false) invalid: boolean("Mark as invalid (invalid)", false),
} },
}); });

View file

@ -1,18 +1,10 @@
<script>
let className = undefined;
export { className as class };
export let style = undefined;
import { cx } from '../../lib';
</script>
<form <form
class:bx--form={true}
{...$$restProps}
on:click on:click
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave
on:submit|preventDefault on:submit|preventDefault>
class={cx('--form', className)}
{style}>
<slot /> <slot />
</form> </form>

View file

@ -1,3 +1 @@
import Form from './Form.svelte'; export { default as Form } from "./Form.svelte";
export default Form;

View file

@ -1,26 +1,21 @@
<script> <script>
let className = undefined; export let legendText = "";
export { className as class };
export let invalid = false; export let invalid = false;
export let legendText = '';
export let message = false; export let message = false;
export let messageText = ''; export let messageText = "";
export let style = undefined;
import { cx } from '../../lib';
</script> </script>
<fieldset <fieldset
data-invalid={invalid || undefined} data-invalid={invalid || undefined}
class={cx('--fieldset', className)} class:bx--fieldset={true}
{...$$restProps}
on:click on:click
on:mouseover on:mouseover
on:mouseenter on:mouseenter
on:mouseleave on:mouseleave>
{style}> <legend class:bx--label={true}>{legendText}</legend>
<legend class={cx('--label', className)}>{legendText}</legend>
<slot /> <slot />
{#if message} {#if message}
<div class={cx('--form__requirements')}>{messageText}</div> <div class:bx--form__requirement={true}>{messageText}</div>
{/if} {/if}
</fieldset> </fieldset>

View file

@ -1,3 +1 @@
import FormGroup from './FormGroup.svelte'; export { default as FormGroup } from "./FormGroup.svelte";
export default FormGroup;

View file

@ -1,7 +1,7 @@
<script> <script>
import Layout from '../../internal/ui/Layout.svelte'; import Layout from "../../internal/ui/Layout.svelte";
import NumberInput from '../NumberInput'; import { NumberInput } from "../NumberInput";
import FormItem from './FormItem.svelte'; import FormItem from "./FormItem.svelte";
</script> </script>
<Layout> <Layout>

View file

@ -1,6 +1,6 @@
import { withKnobs } from '@storybook/addon-knobs'; import { withKnobs } from "@storybook/addon-knobs";
import Component from './FormItem.Story.svelte'; import Component from "./FormItem.Story.svelte";
export default { title: 'FormItem', decorators: [withKnobs] }; export default { title: "FormItem", decorators: [withKnobs] };
export const Default = () => ({ Component }); export const Default = () => ({ Component });

View file

@ -1,11 +1,9 @@
<script> <div
let className = undefined; class:bx--form-item={true}
export { className as class }; {...$$restProps}
export let style = undefined; on:click
on:mouseover
import { cx } from '../../lib'; on:mouseenter
</script> on:mouseleave>
<div on:click on:mouseover on:mouseenter on:mouseleave class={cx('--form-item', className)} {style}>
<slot /> <slot />
</div> </div>

View file

@ -1,3 +1 @@
import FormItem from './FormItem.svelte'; export { default as FormItem } from "./FormItem.svelte";
export default FormItem;

View file

@ -1,9 +1,9 @@
<script> <script>
export let story = undefined; export let story = undefined;
import Layout from '../../internal/ui/Layout.svelte'; import Layout from "../../internal/ui/Layout.svelte";
import Tooltip from '../Tooltip'; import { Tooltip } from "../Tooltip";
import FormLabel from './FormLabel.svelte'; import FormLabel from "./FormLabel.svelte";
</script> </script>
<Layout> <Layout>

Some files were not shown because too many files have changed in this diff Show more