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,
"dependencies": {
"carbon-icons-svelte": "10.9.3",
"carbon-icons-svelte": "^10.13.0",
"flatpickr": "4.6.3"
},
"devDependencies": {

View file

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

View file

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

View file

@ -1,21 +1,32 @@
import { withKnobs, text, boolean, number } from '@storybook/addon-knobs';
import Component from './Accordion.Story.svelte';
import {
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 = () => ({
Component,
props: {
title: text('The title (title)', 'Section 1 title'),
open: boolean('Open the section (open)', false)
}
align: select(
"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 = () => ({
Component,
props: {
story: 'skeleton',
open: boolean('Show first item opened (open)', true),
count: number('Set number of items (count)', 4)
}
story: "skeleton",
open: boolean("Show first item opened (open)", true),
count: number("Set number of items (count)", 4),
},
});

View file

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

View file

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

View file

@ -1,5 +1,3 @@
import Accordion from './Accordion.svelte';
export default Accordion;
export { default as AccordionItem } from './AccordionItem.svelte';
export { default as AccordionSkeleton } from './Accordion.Skeleton.svelte';
export { default as Accordion } from "./Accordion.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
class:bx--breadcrumb={true}
class:bx--skeleton={true}
{...$$restProps}
on:click
on:mouseover
on:mouseenter
on:mouseleave
class={cx('--breadcrumb', '--skeleton', className)}
{style}>
on:mouseleave>
{#each [0, 1, 2] as item, i (item)}
<div class={cx('--breadcrumb-item')}>
<span class={cx('--link')}>&nbsp;</span>
<div class:bx--breadcrumb-item={true}>
<span class:bx--link={true}>&nbsp;</span>
</div>
{/each}
</div>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,11 +1,9 @@
<script>
export let story = undefined;
import { cx } from '../../lib';
import Layout from '../../internal/ui/Layout.svelte';
import Button from './Button.svelte';
import ButtonSkeleton from './Button.Skeleton.svelte';
import Add16 from 'carbon-icons-svelte/lib/Add16';
import Button from "./Button.svelte";
import ButtonSkeleton from "./Button.Skeleton.svelte";
import Add16 from "carbon-icons-svelte/lib/Add16";
const {
kind,
@ -39,36 +37,34 @@
const setProps = { disabled, small, size, iconDescription };
</script>
<Layout>
<div>
{#if story === 'skeleton'}
<ButtonSkeleton />
&nbsp;
<ButtonSkeleton href="#" />
&nbsp;
<ButtonSkeleton small />
{:else if story === 'inline'}
<Button />
{:else if story === 'icon-only buttons'}
<Button {...iconOnlyProps} />
{:else if story === 'set of buttons'}
<div class={cx('--btn-set')}>
<Button kind="secondary" {...setProps}>Secondary button</Button>
<Button kind="primary" {...setProps}>Primary button</Button>
</div>
{:else}
<Button {...regularProps}>Button</Button>
&nbsp;
<Button {...regularProps} href="#">Link</Button>
&nbsp;
<Button {...regularProps} as let:props>
<p {...props}>Element</p>
</Button>
&nbsp;
<Button {...regularProps} as let:props>
<!-- svelte-ignore a11y-missing-attribute -->
<a {...props}>Custom component</a>
</Button>
{/if}
</div>
</Layout>
<div>
{#if story === 'skeleton'}
<ButtonSkeleton />
&nbsp;
<ButtonSkeleton href="#" />
&nbsp;
<ButtonSkeleton small />
{:else if story === 'inline'}
<Button />
{:else if story === 'icon-only buttons'}
<Button {...iconOnlyProps} />
{:else if story === 'set of buttons'}
<div class="bx--btn-set">
<Button kind="secondary" {...setProps}>Secondary button</Button>
<Button kind="primary" {...setProps}>Primary button</Button>
</div>
{:else}
<Button {...regularProps}>Button</Button>
&nbsp;
<Button {...regularProps} href="#">Link</Button>
&nbsp;
<Button {...regularProps} as let:props>
<p {...props}>Element</p>
</Button>
&nbsp;
<Button {...regularProps} as let:props>
<!-- svelte-ignore a11y-missing-attribute -->
<a {...props}>Custom component</a>
</Button>
{/if}
</div>

View file

@ -1,62 +1,62 @@
import { withKnobs, select, boolean, text } from '@storybook/addon-knobs';
import Component from './Button.Story.svelte';
import { withKnobs, select, boolean, text } from "@storybook/addon-knobs";
import Component from "./Button.Story.svelte";
export default { title: 'Button', decorators: [withKnobs] };
export default { title: "Button", decorators: [withKnobs] };
const kinds = {
'Primary button (primary)': 'primary',
'Secondary button (secondary)': 'secondary',
'Danger button (danger)': 'danger',
'Ghost button (ghost)': 'ghost'
"Primary button (primary)": "primary",
"Secondary button (secondary)": "secondary",
"Danger button (danger)": "danger",
"Ghost button (ghost)": "ghost",
};
const sizes = {
Default: 'default',
Field: 'field',
Small: 'small'
Default: "default",
Field: "field",
Small: "small",
};
export const Default = () => ({
Component,
props: {
kind: select('Button kind (kind)', kinds, 'primary'),
disabled: boolean('Disabled (disabled)', false),
size: select('Button size (size)', sizes, 'default'),
iconDescription: text('Icon description (iconDescription)', 'Button icon'),
small: boolean('Small (small) - Deprecated in favor of `size`', false)
}
kind: select("Button kind (kind)", kinds, "primary"),
disabled: boolean("Disabled (disabled)", false),
size: select("Button size (size)", sizes, "default"),
iconDescription: text("Icon description (iconDescription)", "Button icon"),
small: boolean("Small (small) - Deprecated in favor of `size`", false),
},
});
export const IconOnlyButtons = () => ({
Component,
props: {
story: 'icon-only buttons',
kind: select('Button kind (kind)', kinds, 'primary'),
disabled: boolean('Disabled (disabled)', false),
size: select('Button size (size)', sizes, 'default'),
iconDescription: text('Icon description (iconDescription)', 'Button icon'),
story: "icon-only buttons",
kind: select("Button kind (kind)", kinds, "primary"),
disabled: boolean("Disabled (disabled)", false),
size: select("Button size (size)", sizes, "default"),
iconDescription: text("Icon description (iconDescription)", "Button icon"),
tooltipPosition: select(
'Tooltip position (tooltipPosition)',
['top', 'right', 'bottom', 'left'],
'bottom'
"Tooltip position (tooltipPosition)",
["top", "right", "bottom", "left"],
"bottom"
),
tooltipAlignment: select(
'Tooltip alignment (tooltipAlignment)',
['start', 'center', 'end'],
'center'
)
}
"Tooltip alignment (tooltipAlignment)",
["start", "center", "end"],
"center"
),
},
});
export const SetOfButtons = () => ({
Component,
props: {
story: 'set of buttons',
disabled: boolean('Disabled (disabled)', false),
small: boolean('Small (small)', false),
size: select('Button size (size)', sizes, 'default'),
iconDescription: text('Icon description (iconDescription)', 'Button icon')
}
story: "set of buttons",
disabled: boolean("Disabled (disabled)", false),
small: boolean("Small (small)", false),
size: select("Button size (size)", sizes, "default"),
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>
let className = undefined;
export { className as class };
export let as = undefined;
export let disabled = false;
export let href = undefined;
export let icon = undefined;
export let iconDescription = undefined;
export let hasIconOnly = false;
export let kind = 'primary';
export let size = 'default';
export let style = undefined;
export let tabindex = '0';
export let kind = "primary";
export let size = "default";
export let tabindex = "0";
export let tooltipAlignment = undefined;
export let tooltipPosition = undefined;
export let type = 'button';
export let type = "button";
export let ref = null;
import { getContext } from 'svelte';
import { cx } from '../../lib';
import { getContext } from "svelte";
const ctx = getContext('ComposedModal');
const ctx = getContext("ComposedModal");
let buttonRef = undefined;
$: if (ctx && buttonRef) {
ctx.declareRef(buttonRef);
$: if (ctx && ref) {
ctx.declareRef(ref);
}
$: buttonProps = {
role: 'button',
role: "button",
type: href && !disabled ? undefined : type,
tabindex,
disabled,
href,
style,
class: cx(
'--btn',
size === 'field' && '--btn--field',
size === 'small' && '--btn--sm',
kind && `--btn--${kind}`,
disabled && '--btn--disabled',
hasIconOnly && '--btn--icon-only',
hasIconOnly && '--tooltip__trigger',
hasIconOnly && '--tooltip--a11y',
hasIconOnly && tooltipPosition && `--tooltip--${tooltipPosition}`,
hasIconOnly && tooltipAlignment && `--tooltip--align-${tooltipAlignment}`,
className
)
style: $$restProps.style,
class: [
"bx--btn",
size === "field" && "bx--btn--field",
size === "small" && "bx--btn--sm",
kind && `bx--btn--${kind}`,
disabled && "bx--btn--disabled",
hasIconOnly && "bx--btn--icon-only",
hasIconOnly && "bx--tooltip__trigger",
hasIconOnly && "bx--tooltip--a11y",
hasIconOnly && tooltipPosition && `bx--tooltip--${tooltipPosition}`,
hasIconOnly &&
tooltipAlignment &&
`bx--tooltip--align-${tooltipAlignment}`,
$$restProps.class
]
.filter(Boolean)
.join(" ")
};
</script>
@ -52,30 +51,42 @@
<slot props={buttonProps} />
{:else if href && !disabled}
<!-- 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}
<span class={cx('--assistive-text')}>{iconDescription}</span>
<span class:bx--assistive-text={true}>{iconDescription}</span>
{/if}
<slot />
{#if icon}
<svelte:component
this={icon}
aria-hidden="true"
class={cx('--btn__icon')}
class="bx--btn__icon"
aria-label={iconDescription} />
{/if}
</a>
{: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}
<span class={cx('--assistive-text')}>{iconDescription}</span>
<span class:bx--assistive-text={true}>{iconDescription}</span>
{/if}
<slot />
{#if icon}
<svelte:component
this={icon}
aria-hidden="true"
class={cx('--btn__icon')}
class="bx--btn__icon"
aria-label={iconDescription} />
{/if}
</button>

View file

@ -1,4 +1,2 @@
import Button from './Button.svelte';
export default Button;
export { default as ButtonSkeleton } from './Button.Skeleton.svelte';
export { default as Button } from "./Button.svelte";
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
class:bx--form-item={true}
class:bx--checkbox-wrapper={true}
class:bx--checkbox-label={true}
{...$$restProps}
on:click
on:mouseover
on:mouseenter
on:mouseleave
class={cx('--form-item', '--checkbox-wrapper', '--checkbox-label', className)}
{style}>
<span class={cx('--checkbox-label-text', '--skeleton')} />
on:mouseleave>
<span class:bx--checkbox-label-text={true} class:bx--skeleton={true} />
</div>

View file

@ -1,39 +1,47 @@
<script>
export let story = undefined;
import { cx } from '../../lib';
import Layout from '../../internal/ui/Layout.svelte';
import Checkbox from './Checkbox.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 checkboxProps = { labelText, indeterminate, disabled, hideLabel, wrapperClassName };
const {
labelText,
indeterminate,
disabled,
hideLabel,
wrapperClassName
} = $$props;
const checkboxProps = {
labelText,
indeterminate,
disabled,
hideLabel,
wrapperClassName
};
let checked = true;
</script>
<Layout>
{#if story === 'skeleton'}
<div>
<CheckboxSkeleton />
</div>
{:else if story === 'unchecked'}
<fieldset class={cx('--fieldset')}>
<legend class={cx('--label')}>Checkbox heading</legend>
<Checkbox {...checkboxProps} id="checkbox-label-1" />
<Checkbox {...checkboxProps} id="checkbox-label-2" />
</fieldset>
{:else}
<fieldset class={cx('--fieldset')}>
<legend class={cx('--label')}>Checkbox heading</legend>
<Checkbox
{...checkboxProps}
id="checkbox-label-1"
bind:checked
on:check={({ detail }) => {
console.log('on:check', detail);
}} />
<Checkbox {...checkboxProps} id="checkbox-label-2" checked />
</fieldset>
{/if}
</Layout>
{#if story === 'skeleton'}
<div>
<CheckboxSkeleton />
</div>
{:else if story === 'unchecked'}
<fieldset class="bx--fieldset">
<legend class="bx--label">Checkbox heading</legend>
<Checkbox {...checkboxProps} id="checkbox-label-1" />
<Checkbox {...checkboxProps} id="checkbox-label-2" />
</fieldset>
{:else}
<fieldset class="bx--fieldset">
<legend class="bx--label">Checkbox heading</legend>
<Checkbox
{...checkboxProps}
id="checkbox-label-1"
bind:checked
on:check={({ detail }) => {
console.log('on:check', detail);
}} />
<Checkbox {...checkboxProps} id="checkbox-label-2" checked />
</fieldset>
{/if}

View file

@ -1,27 +1,27 @@
import { withKnobs, boolean, text } from '@storybook/addon-knobs';
import Component from './Checkbox.Story.svelte';
import { withKnobs, boolean, text } from "@storybook/addon-knobs";
import Component from "./Checkbox.Story.svelte";
export default { title: 'Checkbox', decorators: [withKnobs] };
export default { title: "Checkbox", decorators: [withKnobs] };
export const Checked = () => ({
Component,
props: {
labelText: text('Label text (labelText)', 'Checkbox label'),
indeterminate: boolean('Intermediate (indeterminate)', false),
disabled: boolean('Disabled (disabled)', false),
hideLabel: boolean('Hide label (hideLabel)', false)
}
labelText: text("Label text (labelText)", "Checkbox label"),
indeterminate: boolean("Intermediate (indeterminate)", false),
disabled: boolean("Disabled (disabled)", false),
hideLabel: boolean("Hide label (hideLabel)", false),
},
});
export const Unchecked = () => ({
Component,
props: {
story: 'unchecked',
labelText: text('Label text (labelText)', 'Checkbox label'),
indeterminate: boolean('Intermediate (indeterminate)', false),
disabled: boolean('Disabled (disabled)', false),
hideLabel: boolean('Hide label (hideLabel)', false)
}
story: "unchecked",
labelText: text("Label text (labelText)", "Checkbox label"),
indeterminate: boolean("Intermediate (indeterminate)", false),
disabled: boolean("Disabled (disabled)", 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>
let className = undefined;
export { className as class };
export let indeterminate = false;
export let readonly = false;
export let checked = false;
export let disabled = false;
export let labelText = "";
export let hideLabel = false;
export let id = Math.random();
export let indeterminate = false;
export let labelText = '';
export let name = '';
export let readonly = false;
export let style = undefined;
export let title = '';
export let id = "ccs-" + Math.random().toString(36);
export let name = "";
export let title = undefined;
export let ref = null;
import { createEventDispatcher } from 'svelte';
import { cx } from '../../lib';
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
$: {
dispatch('check', checked);
}
$: dispatch("check", checked);
</script>
<div
class:bx--form-item={true}
class:bx--checkbox-wrapper={true}
{...$$restProps}
on:click
on:mouseover
on:mouseenter
on:mouseleave
class={cx('--form-item', '--checkbox-wrapper', className)}
{style}>
on:mouseleave>
<input
bind:this={ref}
type="checkbox"
class={cx('--checkbox')}
{checked}
{disabled}
{id}
{indeterminate}
{name}
{readonly}
class:bx--checkbox={true}
on:change
on:change={() => {
checked = !checked;
}}
{indeterminate}
{disabled}
{checked}
{name}
{id}
{readonly} />
<label class={cx('--checkbox-label')} for={id} title={title || undefined}>
<span class={cx('--checkbox-label-text', hideLabel && '--visually-hidden')}>{labelText}</span>
}} />
<label class:bx--checkbox-label={true} for={id} {title}>
<span
class:bx--checkbox-label-text={true}
class:bx--visually-hidden={hideLabel}>
{labelText}
</span>
</label>
</div>

View file

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

View file

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

View file

@ -1,32 +1,25 @@
<script>
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 CodeSnippetSkeleton from './CodeSnippet.Skeleton.svelte';
import CodeSnippet from "./CodeSnippet.svelte";
import CodeSnippetSkeleton from "./CodeSnippet.Skeleton.svelte";
</script>
<Layout>
<div>
{#if story === 'skeleton'}
<div style="width: 800px">
<CodeSnippetSkeleton type="single" style="margin-bottom: 8px" />
<CodeSnippetSkeleton type="multi" />
</div>
{:else if story === 'inline'}
<CodeSnippet type="inline" {light} {feedback} {copyLabel}>{'node -v'}</CodeSnippet>
{:else if story === 'single line'}
<CodeSnippet
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!'}
</CodeSnippet>
{:else if story === 'multi line'}
<CodeSnippet type="multi" {feedback} {showLessText} {showMoreText}>
{`@mixin grid-container {
<div>
{#if story === 'skeleton'}
<div style="width: 800px">
<CodeSnippetSkeleton type="single" style="margin-bottom: 8px" />
<CodeSnippetSkeleton type="multi" />
</div>
{:else if story === 'inline'}
<CodeSnippet {...$$restProps} type="inline">{'node -v'}</CodeSnippet>
{:else if story === 'single line'}
<CodeSnippet {...$$restProps} type="single">
{'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>
{:else if story === 'multi line'}
<CodeSnippet {...$$restProps} type="multi">
{`@mixin grid-container {
width: 100%;
padding-right: padding(mobile);
padding-left: padding(mobile);
@ -43,11 +36,10 @@ $z-indexes: (
dropdown : 7000,
header : 6000,
footer : 5000,
hidden : - 1,
overflowHidden: - 1,
hidden : -1,
overflowHidden: -1,
floating: 10000
);`}
</CodeSnippet>
{/if}
</div>
</Layout>
</CodeSnippet>
{/if}
</div>

View file

@ -1,39 +1,52 @@
import { withKnobs, boolean, text } from '@storybook/addon-knobs';
import Component from './CodeSnippet.Story.svelte';
import { withKnobs, boolean, text } from "@storybook/addon-knobs";
import Component from "./CodeSnippet.Story.svelte";
export default { title: 'CodeSnippet', decorators: [withKnobs] };
export default { title: "CodeSnippet", decorators: [withKnobs] };
export const Inline = () => ({
Component,
props: {
story: 'inline',
light: boolean('Light variant (light)', false),
feedback: text('Feedback text (feedback)', 'Feedback Enabled 👍'),
copyLabel: text('ARIA label for the snippet/copy button (copyLabel)', 'copyable code snippet')
}
story: "inline",
light: boolean("Light variant (light)", false),
feedback: text("Feedback text (feedback)", "Feedback Enabled 👍"),
copyLabel: text(
"ARIA label for the snippet/copy button (copyLabel)",
"copyable code snippet"
),
},
});
export const SingleLine = () => ({
Component,
props: {
story: 'single line',
feedback: text('Feedback text (feedback)', 'Feedback Enabled 👍'),
story: "single line",
light: boolean("Light variant (light)", false),
feedback: text("Feedback text (feedback)", "Feedback Enabled 👍"),
copyButtonDescription: text(
'Copy icon description (copyButtonDescription)',
'copyable code snippet'
"Copy icon description (copyButtonDescription)",
"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 = () => ({
Component,
props: {
story: 'multi line',
feedback: text('Feedback text (feedback)', 'Feedback Enabled 👍'),
showMoreText: text('Text for "show more" button (showMoreText)', 'Show more'),
showLessText: text('Text for "show less" button (showLessText)', 'Show less')
}
story: "multi line",
feedback: text("Feedback text (feedback)", "Feedback Enabled 👍"),
showMoreText: text(
'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>
let className = undefined;
export { className as class };
export let type = "single"; // "single" | "inline" | "multi"
export let code = undefined;
export let light = false;
export let skeleton = false;
export let copyButtonDescription = undefined;
export let copyLabel = undefined;
export let feedback = undefined;
export let feedbackTimeout = undefined;
export let id = Math.random();
export let light = false;
export let showLessText = 'Show less';
export let showMoreText = 'Show more';
export let skeleton = false;
export let style = undefined;
export let type = 'single';
export let feedback = "Copied!";
export let feedbackTimeout = 2000;
export let showLessText = "Show less";
export let showMoreText = "Show more";
export let id = "ccs-" + Math.random().toString(36);
export let ref = null;
import { afterUpdate } from 'svelte';
import ChevronDown16 from 'carbon-icons-svelte/lib/ChevronDown16';
import { cx } from '../../lib';
import Button from '../Button';
import Copy from '../Copy';
import CopyButton from '../CopyButton';
import CodeSnippetSkeleton from './CodeSnippet.Skeleton.svelte';
import { afterUpdate } from "svelte";
import ChevronDown16 from "carbon-icons-svelte/lib/ChevronDown16";
import { Button } from "../Button";
import { Copy } from "../Copy";
import { CopyButton } from "../CopyButton";
import CodeSnippetSkeleton from "./CodeSnippet.Skeleton.svelte";
let codeRef = undefined;
let expanded = false;
let showMoreLess = false;
$: showMoreLess = false;
$: expanded = false;
$: expandText = expanded ? showLessText : showMoreText;
afterUpdate(() => {
if (type === 'multi' && codeRef) {
showMoreLess = codeRef.getBoundingClientRect().height > 255;
if (type === "multi" && ref) {
showMoreLess = ref.getBoundingClientRect().height > 255;
}
});
$: expandText = expanded ? showLessText : showMoreText;
</script>
{#if skeleton}
<CodeSnippetSkeleton class={className} {type} {style} />
{/if}
{#if !skeleton}
<CodeSnippetSkeleton {type} {...$$restProps} />
{:else}
{#if type === 'inline'}
<Copy
aria-label={$$props['aria-label'] || copyLabel}
aria-label={copyLabel}
aria-describedby={id}
class={cx('--snippet', type && `--snippet--${type}`, type === 'inline' && '--btn--copy', expanded && '--snippet--expand', light && '--snippet--light', className)}
on:click
class="bx--snippet {type && `bx--snippet--${type}`}
{type === 'inline' && 'bx--btn--copy'}
{expanded && 'bx--snippet--expand'}
{light && 'bx--snippet--light'}"
{...$$restProps}
on:clicks
on:mouseover
on:mouseenter
on:mouseleave
{feedback}
{feedbackTimeout}
{style}>
on:mouseleave>
<code {id}>
<slot>{code}</slot>
</code>
</Copy>
{:else}
<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:mouseenter
on:mouseleave
class={cx('--snippet', type && `--snippet--${type}`, type === 'inline' && '--btn--copy', expanded && '--snippet--expand', light && '--snippet--light', className)}
{style}>
on:mouseleave>
<div
role="textbox"
tabindex="0"
class={cx('--snippet-container')}
aria-label={$$props['aria-label'] || copyLabel || 'code-snippet'}>
role={type === 'single' ? 'textbox' : undefined}
tabindex={type === 'single' ? '0' : undefined}
class:bx--snippet-container={true}
aria-label={$$restProps['aria-label'] || copyLabel || 'code-snippet'}>
<code>
<pre bind:this={codeRef}>
<pre bind:this={ref}>
<slot>{code}</slot>
</pre>
</code>
</div>
<CopyButton
iconDescription={copyButtonDescription}
class={cx('--snippet-button')}
on:click
{feedback}
{feedbackTimeout} />
{feedbackTimeout}
iconDescription={copyButtonDescription}
on:click
on:animationend />
{#if showMoreLess}
<Button
kind="ghost"
size="small"
class={cx('--snippet-btn--expand')}
class="bx--snippet-btn--expand"
on:click={() => {
expanded = !expanded;
}}>
<span class={cx('--snippet-btn--text')}>{expandText}</span>
<span class:bx--snippet-btn--text={true}>{expandText}</span>
<ChevronDown16
aria-label={expandText}
class={cx('--icon-chevron--down', '--snippet__icon')} />
class="bx--icon-chevron--down bx--snippet__icon"
aria-label={expandText} />
</Button>
{/if}
</div>

View file

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

View file

@ -1,23 +1,25 @@
<script>
import Layout from '../../internal/ui/Layout.svelte';
import ToggleSmall from '../ToggleSmall';
import Button from '../Button';
import ComboBox from './ComboBox.svelte';
import { ToggleSmall } from "../ToggleSmall";
import { Button } from "../Button";
import ComboBox from "./ComboBox.svelte";
let items = [
{ id: 'option-0', text: 'Option 1' },
{ id: 'option-1', text: 'Option 2' },
{ id: 'option-2', text: 'Option 3' },
{ id: 'option-3', text: 'Option 4' },
{ id: "option-0", text: "Option 1" },
{ id: "option-1", text: "Option 2" },
{ id: "option-2", text: "Option 3" },
{ id: "option-3", text: "Option 4" },
{
id: 'option-4',
text: 'An example option that is really long to show what should be done to handle long text'
id: "option-4",
text:
"An example option that is really long to show what should be done to handle long text"
}
];
let toggled = false;
let value = undefined;
let selectedIndex = -1;
$: toggled = false;
$: value = undefined;
$: selectedIndex = -1;
$: ref = null;
$: ref && ref.focus();
function shouldFilterItem(item, value) {
if (!toggled || !value) {
@ -28,26 +30,34 @@
}
</script>
<Layout>
<p>Currently, this component does not support items as slots.</p>
<p>
<code>items</code>
must be an array of objects; mandatory fields are `id` and `text`.
</p>
<pre style="margin-top: 1rem;">
<code>{'items = Array<{ id: string; text: string; }>'}</code>
</pre>
<div style="margin-top: 2rem;">
<ToggleSmall labelText="Enable filtering" bind:toggled />
<Button
size="small"
on:click={() => {
selectedIndex = 1;
}}>
Set item to 'Option 2'
</Button>
</div>
<div style="width: 300px; margin-top: 2rem;">
<ComboBox {...$$props} id="combobox" bind:value bind:selectedIndex {items} {shouldFilterItem} />
</div>
</Layout>
<p>Currently, this component does not support items as slots.</p>
<p>
<code>items</code>
must be an array of objects; mandatory fields are `id` and `text`.
</p>
<pre style="margin-top: 1rem;">
<code>{'items = Array<{ id: string; text: string; }>'}</code>
</pre>
<div style="margin-top: 2rem;">
<ToggleSmall
labelText="Enable filtering"
bind:toggled
style="margin-top: 1rem;" />
<Button
size="small"
on:click={() => {
selectedIndex = 1;
}}>
Set item to 'Option 2'
</Button>
</div>
<div style="width: 300px; margin-top: 2rem;">
<ComboBox
{...$$props}
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 Component from './ComboBox.Story.svelte';
import { withKnobs, select, boolean, text } from "@storybook/addon-knobs";
import Component from "./ComboBox.Story.svelte";
export default { title: 'ComboBox', decorators: [withKnobs] };
export default { title: "ComboBox", decorators: [withKnobs] };
const sizes = {
'Extra large size (xl)': 'xl',
'Regular size (lg)': '',
'Small size (sm)': 'sm'
"Extra large size (xl)": "xl",
"Regular size (lg)": "",
"Small size (sm)": "sm",
};
export const Default = () => ({
Component,
props: {
size: select('Field size (size)', sizes, ''),
placeholder: text('Placeholder text (placeholder)', 'Filter...'),
titleText: text('Title (titleText)', 'Combobox title'),
helperText: text('Helper text (helperText)', 'Optional helper text here'),
light: boolean('Light (light)', false),
disabled: boolean('Disabled (disabled)', false),
invalid: boolean('Invalid (invalid)', false),
invalidText: text('Invalid text (invalidText)', 'A valid value is required'),
name: 'combo-box-name'
}
size: select("Field size (size)", sizes, ""),
placeholder: text("Placeholder text (placeholder)", "Filter..."),
titleText: text("Title (titleText)", "Combobox title"),
light: boolean("Light (light)", false),
disabled: boolean("Disabled (disabled)", false),
invalid: boolean("Invalid (invalid)", false),
invalidText: text(
"Invalid text (invalidText)",
"A valid value is required"
),
name: "combo-box-name",
},
});

View file

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

View file

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

View file

@ -2,14 +2,14 @@
export let story = undefined;
const { modalBody } = $$props;
import Layout from '../../internal/ui/Layout.svelte';
import Button from '../Button';
import ComposedModal from './ComposedModal.svelte';
import ModalHeader from './ModalHeader.svelte';
import ModalBody from './ModalBody.svelte';
import ModalFooter from './ModalFooter.svelte';
import Layout from "../../internal/ui/Layout.svelte";
import { Button } from "../Button";
import ComposedModal from "./ComposedModal.svelte";
import ModalHeader from "./ModalHeader.svelte";
import ModalBody from "./ModalBody.svelte";
import ModalFooter from "./ModalFooter.svelte";
let open = false;
$: open = false;
</script>
<Layout>
@ -19,13 +19,17 @@
<ModalBody
{...$$props.modalBody}
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}
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue.
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna,
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id
accumsan augue. Phasellus consequat augue vitae tellus tincidunt
posuere. Curabitur justo urna, consectetur vel elit iaculis,
ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie
tellus. Quisque consectetur non risus eu rutrum.{' '}
</p>
{/if}
</ModalBody>
@ -41,28 +45,39 @@
<ModalBody
{...$$props.modalBody}
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}
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue.
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna,
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id
accumsan augue. Phasellus consequat augue vitae tellus tincidunt
posuere. Curabitur justo urna, consectetur vel elit iaculis,
ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie
tellus. Quisque consectetur non risus eu rutrum.{' '}
</p>
{/if}
</ModalBody>
<ModalFooter>
<Button kind="secondary">Cancel</Button>
<Button kind={$$props.composedModal.danger ? 'danger' : 'primary'}>Primary</Button>
<Button kind={$$props.composedModal.danger ? 'danger' : 'primary'}>
Primary
</Button>
</ModalFooter>
</ComposedModal>
{/if}
{#if story === 'title'}
<ComposedModal {...$$props.composedModal} open on:close={() => {}} on:submit={() => {}}>
<ComposedModal
{...$$props.composedModal}
open
on:close={() => {}}
on:submit={() => {}}>
<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} />
<ModalFooter {...$$props.modalFooter} />
</ComposedModal>
@ -77,55 +92,68 @@
Launch composed modal
</Button>
</div>
<ComposedModal {...$$props.composedModal} {open} on:close={() => (open = false)}>
<ComposedModal
{...$$props.composedModal}
{open}
on:close={() => (open = false)}>
<ModalHeader {...$$props.modalHeader} />
<ModalBody
{...$$props.modalBody}
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}
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue.
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna,
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id
accumsan augue. Phasellus consequat augue vitae tellus tincidunt
posuere. Curabitur justo urna, consectetur vel elit iaculis,
ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie
tellus. Quisque consectetur non risus eu rutrum.{' '}
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue.
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna,
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id
accumsan augue. Phasellus consequat augue vitae tellus tincidunt
posuere. Curabitur justo urna, consectetur vel elit iaculis,
ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie
tellus. Quisque consectetur non risus eu rutrum.{' '}
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue.
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna,
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id
accumsan augue. Phasellus consequat augue vitae tellus tincidunt
posuere. Curabitur justo urna, consectetur vel elit iaculis,
ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie
tellus. Quisque consectetur non risus eu rutrum.{' '}
</p>
<h3>Lorem ipsum</h3>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue.
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna,
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id
accumsan augue. Phasellus consequat augue vitae tellus tincidunt
posuere. Curabitur justo urna, consectetur vel elit iaculis,
ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie
tellus. Quisque consectetur non risus eu rutrum.{' '}
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue.
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna,
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id
accumsan augue. Phasellus consequat augue vitae tellus tincidunt
posuere. Curabitur justo urna, consectetur vel elit iaculis,
ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie
tellus. Quisque consectetur non risus eu rutrum.{' '}
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue.
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna,
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id
accumsan augue. Phasellus consequat augue vitae tellus tincidunt
posuere. Curabitur justo urna, consectetur vel elit iaculis,
ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie
tellus. Quisque consectetur non risus eu rutrum.{' '}
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue.
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna,
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id
accumsan augue. Phasellus consequat augue vitae tellus tincidunt
posuere. Curabitur justo urna, consectetur vel elit iaculis,
ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie
tellus. Quisque consectetur non risus eu rutrum.{' '}
</p>
{/if}
</ModalBody>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,11 +1,13 @@
<script>
export let story = undefined;
import Layout from '../../internal/ui/Layout.svelte';
import ContentSwitcher from './ContentSwitcher.svelte';
import Switch from './Switch.svelte';
import Layout from "../../internal/ui/Layout.svelte";
import ContentSwitcher from "./ContentSwitcher.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>
<Layout>
@ -26,7 +28,19 @@
}}>
<Switch {...$$props} text="First 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>
<div
style="margin-top: 1.5rem"
on:click={() => {
selectedIndex = 1;
}}>
Programmatically set to second index
</div>
{/if}
</Layout>

View file

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

View file

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

View file

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

View file

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

View file

@ -1,38 +1,51 @@
<script>
let className = undefined;
export { className as class };
export let feedback = 'Copied!';
export let feedback = "Copied!";
export let feedbackTimeout = 2000;
export let style = undefined;
export let ref = null;
import { onDestroy } from 'svelte';
import { cx } from '../../lib';
import { onDestroy } from "svelte";
let timeoutId = undefined;
$: animation = undefined;
$: timeoutId = undefined;
$: showFeedback = timeoutId !== undefined;
onDestroy(() => {
window.clearTimeout(timeoutId);
timeoutId = undefined;
});
$: showFeedback = timeoutId !== undefined;
</script>
<button
bind:this={ref}
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={() => {
timeoutId = window.setTimeout(() => {
showFeedback = undefined;
if (animation === 'fade-in') return;
animation = 'fade-in';
timeoutId = setTimeout(() => {
animation = 'fade-out';
}, feedbackTimeout);
}}
on:mouseover
on:mouseenter
on:mouseleave
{style}>
<slot />
<div
class={cx('--btn--copy__feedback', showFeedback && '--btn--copy__feedback--displayed')}
data-feedback={feedback} />
on:animationend
on:animationend={({ animationName }) => {
if (animationName === 'hide-feedback') {
animation = undefined;
}
}}>
<slot>
{#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>

View file

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

View file

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

View file

@ -1,13 +1,19 @@
import { withKnobs, text, number } from '@storybook/addon-knobs';
import Component from './CopyButton.Story.svelte';
import { withKnobs, text, number } from "@storybook/addon-knobs";
import Component from "./CopyButton.Story.svelte";
export default { title: 'CopyButton', decorators: [withKnobs] };
export default { title: "CopyButton", decorators: [withKnobs] };
export const Default = () => ({
Component,
props: {
feedback: text('The text shown upon clicking (feedback)', 'Copied!'),
feedbackTimeout: number('How long the text is shown upon clicking (feedbackTimeout)', 2000),
iconDescription: text('Feedback icon description (iconDescription)', 'Copy to clipboard')
}
feedback: text("The text shown upon clicking (feedback)", "Copied!"),
feedbackTimeout: number(
"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>
let className = undefined;
export { className as class };
export let feedback = 'Copied!';
export let feedbackTimeout = 2000;
export let iconDescription = 'Copy to clipboard';
export let style = undefined;
export let iconDescription = "Copy to clipboard";
import { afterUpdate, onDestroy } from 'svelte';
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;
});
import { Copy } from "../Copy";
import Copy16 from "carbon-icons-svelte/lib/Copy16";
</script>
<button
type="button"
tabindex="0"
<Copy
class="bx--copy-btn"
aria-label={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={() => {
animation = 'fade-in';
}}
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>
on:animationend>
<Copy16 class="bx--snippet__icon" />
</Copy>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,10 +1,8 @@
import DataTable from './DataTable.svelte';
export default DataTable;
export { default as Table } from './Table.svelte';
export { default as TableBody } from './TableBody.svelte';
export { default as TableCell } from './TableCell.svelte';
export { default as TableContainer } from './TableContainer.svelte';
export { default as TableHead } from './TableHead.svelte';
export { default as TableHeader } from './TableHeader.svelte';
export { default as TableRow } from './TableRow.svelte';
export { default as DataTable } from "./DataTable.svelte";
export { default as Table } from "./Table.svelte";
export { default as TableBody } from "./TableBody.svelte";
export { default as TableCell } from "./TableCell.svelte";
export { default as TableContainer } from "./TableContainer.svelte";
export { default as TableHead } from "./TableHead.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 Component from './DataTableSkeleton.Story.svelte';
import { withKnobs, array, boolean } from "@storybook/addon-knobs";
import Component from "./DataTableSkeleton.Story.svelte";
export default { title: 'DataTableSkeleton', decorators: [withKnobs] };
export default { title: "DataTableSkeleton", decorators: [withKnobs] };
export const Default = () => ({
Component,
props: {
headers: array(
'Optional table headers (headers)',
['Name', 'Protocol', 'Port', 'Rule', 'Attached Groups'],
','
"Optional table headers (headers)",
["Name", "Protocol", "Port", "Rule", "Attached Groups"],
","
),
zebra: boolean('Use zebra stripe (zebra)', false),
compact: boolean('Compact variant (compact)', false)
}
zebra: boolean("Use zebra stripe (zebra)", false),
compact: boolean("Compact variant (compact)", false),
},
});

View file

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

View file

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

View file

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

View file

@ -1,20 +1,25 @@
<script>
let className = undefined;
export { className as class };
export let id = Math.random();
export let range = false;
export let style = undefined;
import { cx, fillArray } from '../../lib';
export let id = "ccs-" + Math.random().toString(36);
</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
class={cx('--date-picker', '--skeleton', range && '--date-picker--range', !range && '--date-picker--short', !range && '--date-picker--simple', className)}>
{#each fillArray(range ? 2 : 1) as input, i (input)}
<div class={cx('--date-picker-container')}>
<label class={cx('--label')} for={id} />
<div class={cx('--date-picker__input', '--skeleton')} />
class:bx--date-picker={true}
class:bx--skeleton={true}
class:bx--date-picker--range={true}
class:bx--date-picker--short={!range}
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>
{/each}
</div>

View file

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

View file

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

View file

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

View file

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

View file

@ -1,40 +1,47 @@
import flatpickr from 'flatpickr';
import l10n from 'flatpickr/dist/l10n/index.js';
import rangePlugin from 'flatpickr/dist/plugins/rangePlugin';
import { cx } from '../../lib';
import flatpickr from "flatpickr";
import l10n from "flatpickr/dist/l10n/index.js";
import rangePlugin from "flatpickr/dist/plugins/rangePlugin";
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.querySelector('.flatpickr-month').classList.add(cx('--date-picker__month'));
calendarContainer.classList.add("bx--date-picker__calendar");
calendarContainer
.querySelector(".flatpickr-month")
.classList.add("bx--date-picker__month");
weekdayContainer.classList.add(cx('--date-picker__weekdays'));
weekdayContainer.querySelectorAll('.flatpickr-weekday').forEach(node => {
node.classList.add(cx('--date-picker__weekday'));
weekdayContainer.classList.add("bx--date-picker__weekdays");
weekdayContainer.querySelectorAll(".flatpickr-weekday").forEach((node) => {
node.classList.add("bx--date-picker__weekday");
});
daysContainer.classList.add(cx('--date-picker__days'));
days.querySelectorAll('.flatpickr-day').forEach(node => {
node.classList.add(cx('--date-picker__day'));
if (node.classList.contains('today') && selectedDates.length > 0) {
node.classList.add('no-border');
} else if (node.classList.contains('today') && selectedDates.length === 0) {
node.classList.remove('no-border');
daysContainer.classList.add("bx--date-picker__days");
days.querySelectorAll(".flatpickr-day").forEach((node) => {
node.classList.add("bx--date-picker__day");
if (node.classList.contains("today") && selectedDates.length > 0) {
node.classList.add("no-border");
} else if (node.classList.contains("today") && selectedDates.length === 0) {
node.classList.remove("no-border");
}
});
}
function updateMonthNode(instance) {
const monthText = instance.l10n.months.longhand[instance.currentMonth];
const staticMonthNode = instance.monthNav.querySelector('.cur-month');
const staticMonthNode = instance.monthNav.querySelector(".cur-month");
if (staticMonthNode) {
staticMonthNode.textContent = monthText;
} else {
const monthSelectNode = instance.monthsDropdownContainer;
const span = document.createElement('span');
span.setAttribute('class', 'cur-month');
const span = document.createElement("span");
span.setAttribute("class", "cur-month");
span.textContent = monthText;
monthSelectNode.parentNode.replaceChild(span, monthSelectNode);
}
@ -43,10 +50,11 @@ function updateMonthNode(instance) {
function createCalendar({ options, base, input, dispatch }) {
let locale = options.locale;
if (options.locale === 'en' && l10n && l10n.en) {
if (options.locale === "en" && l10n && l10n.en) {
l10n.en.weekdays.shorthand.forEach((_, index) => {
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;
@ -58,27 +66,27 @@ function createCalendar({ options, base, input, dispatch }) {
disableMobile: true,
clickOpens: true,
locale,
plugins: [options.mode === 'range' && new rangePlugin({ position: 'left', input })].filter(
Boolean
),
plugins: [
options.mode === "range" && new rangePlugin({ position: "left", input }),
].filter(Boolean),
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>',
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>',
onChange: () => {
dispatch('change');
dispatch("change");
},
onClose: () => {
dispatch('close');
dispatch("close");
},
onMonthChange: (s, d, instance) => {
updateMonthNode(instance);
},
onOpen: (s, d, instance) => {
dispatch('open');
dispatch("open");
updateClasses(instance);
updateMonthNode(instance);
}
},
});
}

View file

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

View file

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

View file

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

View file

@ -1,35 +1,41 @@
import { withKnobs, select, text, boolean } from '@storybook/addon-knobs';
import Component from './Dropdown.Story.svelte';
import { withKnobs, select, text, boolean } from "@storybook/addon-knobs";
import Component from "./Dropdown.Story.svelte";
export default { title: 'Dropdown', decorators: [withKnobs] };
export default { title: "Dropdown", decorators: [withKnobs] };
const types = {
'Default (default)': 'default',
'Inline (inline)': 'inline'
"Default (default)": "default",
"Inline (inline)": "inline",
};
const sizes = {
'Extra large size (xl)': 'xl',
'Regular size (lg)': '',
'Small size (sm)': 'sm'
"Extra large size (xl)": "xl",
"Regular size (lg)": "",
"Small size (sm)": "sm",
};
export const Default = () => ({
Component,
props: {
id: text('Dropdown id', 'carbon-dropdown-id'),
name: text('Dropdown name', 'carbon-dropdown-name'),
type: select('Dropdown type (type)', types, 'default'),
size: select('Field size (size)', sizes, '') || undefined,
label: text('Label (label)', 'Dropdown menu options'),
'aria-label': text('Aria Label (aria-label)', 'Dropdown'),
disabled: boolean('Disabled (disabled)', false),
light: boolean('Light variant (light)', false),
titleText: text('Title (titleText)', 'This is not a dropdown title.'),
helperText: text('Helper text (helperText)', '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')
}
id: text("Dropdown id", "carbon-dropdown-id"),
name: text("Dropdown name", "carbon-dropdown-name"),
type: select("Dropdown type (type)", types, "default"),
size: select("Field size (size)", sizes, "") || undefined,
label: text("Label (label)", "Dropdown menu options"),
"aria-label": text("Aria Label (aria-label)", "Dropdown"),
disabled: boolean("Disabled (disabled)", false),
light: boolean("Light variant (light)", false),
titleText: text("Title (titleText)", "This is not a dropdown title."),
helperText: text(
"Helper text (helperText)",
"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>
let className = undefined;
export { className as class };
export let disabled = false;
export let helperText = '';
export let id = Math.random();
export let name = undefined;
export let selectedIndex = -1;
export let open = false;
export let inline = false;
export let light = false;
export let disabled = false;
export let invalid = false;
export let invalidText = '';
export let items = [];
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 light = false;
export let open = false;
export let selectedIndex = -1;
export let size = undefined;
export let style = undefined;
export let titleText = '';
export let titleText = "";
export let translateWithId = undefined;
export let type = 'default';
export let ref = null;
import { setContext } from 'svelte';
import WarningFilled16 from 'carbon-icons-svelte/lib/WarningFilled16';
import { cx } from '../../lib';
import ListBox, { ListBoxField, ListBoxMenu, ListBoxMenuIcon, ListBoxMenuItem } from '../ListBox';
import { setContext } from "svelte";
import WarningFilled16 from "carbon-icons-svelte/lib/WarningFilled16";
import {
ListBox,
ListBoxMenu,
ListBoxMenuIcon,
ListBoxMenuItem
} from "../ListBox";
let selectedId = undefined;
let fieldRef = undefined;
let highlightedIndex = -1;
setContext('Dropdown', {
declareRef: ({ ref }) => {
fieldRef = ref;
}
});
function change(direction) {
let index = highlightedIndex + direction;
@ -47,7 +42,7 @@
highlightedIndex = index;
}
$: inline = type === 'inline';
$: inline = type === "inline";
$: selectedItem = items[selectedIndex];
$: if (!open) {
highlightedIndex = -1;
@ -56,19 +51,27 @@
<svelte:body
on:click={({ target }) => {
if (open && fieldRef && !fieldRef.contains(target)) {
if (open && ref && !ref.contains(target)) {
open = false;
}
}} />
<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)}
{style}>
class:bx--dropdown__wrapper={true}
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}
<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 !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}
</div>
{/if}
@ -78,9 +81,13 @@
{id}
{name}
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 }) => {
open = fieldRef.contains(target) ? !open : false;
open = ref.contains(target) ? !open : false;
}}
{disabled}
{open}
@ -88,9 +95,11 @@
{invalidText}
{light}>
{#if invalid}
<WarningFilled16 class={cx('--list-box__invalid-icon')} />
<WarningFilled16 class="bx--list-box__invalid-icon" />
{/if}
<ListBoxField
<button
bind:this={ref}
class:bx--list-box__field={true}
tabindex="0"
role="button"
aria-expanded={open}
@ -103,7 +112,7 @@
}
} else if (key === 'Tab') {
open = false;
fieldRef.blur();
ref.blur();
} else if (key === 'ArrowDown') {
change(1);
} else if (key === 'ArrowUp') {
@ -112,17 +121,17 @@
}}
on:blur={({ relatedTarget }) => {
if (relatedTarget) {
fieldRef.focus();
ref.focus();
}
}}
{disabled}
{translateWithId}
{id}>
<span class={cx('--list-box__label')}>
<span class="bx--list-box__label">
{#if selectedItem}{itemToString(selectedItem)}{:else}{label}{/if}
</span>
<ListBoxMenuIcon {open} {translateWithId} />
</ListBoxField>
</button>
{#if open}
<ListBoxMenu aria-labelledby={id} {id}>
{#each items as item, i (item.id || i)}

View file

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

View file

@ -1,15 +1,16 @@
<script>
let className = undefined;
export { className as class };
export let style = undefined;
import { cx } from '../../lib';
import { ButtonSkeleton } from '../Button';
import SkeletonText from '../SkeletonText';
import { ButtonSkeleton } from "../Button";
import { SkeletonText } from "../SkeletonText";
</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 width="225px" class={cx('--label-description')} />
<SkeletonText width="225px" class="bx--label-description" />
<ButtonSkeleton />
</div>

View file

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

View file

@ -1,98 +1,116 @@
import { withKnobs, text, select, boolean, array } from '@storybook/addon-knobs';
import Component from './FileUploader.Story.svelte';
import {
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 = {
'Primary (primary)': 'primary',
'Secondary (secondary)': 'secondary',
'Danger (danger)': 'danger',
'Ghost (ghost)': 'ghost',
'Tertiary (tertiary)': 'tertiary'
"Primary (primary)": "primary",
"Secondary (secondary)": "secondary",
"Danger (danger)": "danger",
"Ghost (ghost)": "ghost",
"Tertiary (tertiary)": "tertiary",
};
const filenameStatuses = {
'Edit (edit)': 'edit',
'Complete (complete)': 'complete',
'Uploading (uploading)': 'uploading'
"Edit (edit)": "edit",
"Complete (complete)": "complete",
"Uploading (uploading)": "uploading",
};
export const FileUploaderButton = () => ({
Component,
props: {
story: 'button',
kind: select('Button kind (kind)', buttonKinds, 'primary'),
labelText: text('Label text (labelText)', 'Add files'),
name: text('Form item name: (name)', ''),
multiple: boolean('Supports multiple files (multiple)', true),
disabled: boolean('Disabled (disabled)', false),
story: "button",
kind: select("Button kind (kind)", buttonKinds, "primary"),
labelText: text("Label text (labelText)", "Add files"),
name: text("Form item name: (name)", ""),
multiple: boolean("Supports multiple files (multiple)", true),
disabled: boolean("Disabled (disabled)", false),
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
),
role: text('ARIA role of the button (role)', 'button'),
tabindex: text('Tab index (tabindex)', '0')
}
role: text("ARIA role of the button (role)", "button"),
tabindex: text("Tab index (tabindex)", "0"),
},
});
FileUploaderButton.story = { name: 'FileUploaderButton' };
FileUploaderButton.story = { name: "FileUploaderButton" };
export const FileUploader = () => ({
Component,
props: {
story: 'uploader',
labelTitle: text('The label title (labelTitle)', 'Upload'),
story: "uploader",
labelTitle: text("The label title (labelTitle)", "Upload"),
labelDescription: text(
'The label description (labelDescription)',
'only .jpg files at 500mb or less'
"The label description (labelDescription)",
"only .jpg files at 500mb or less"
),
buttonLabel: text('The button label (buttonLabel)', 'Add files'),
status: select('Status for file name (status)', filenameStatuses, 'edit'),
accept: array('Accepted file extensions (accept)', ['.jpg', '.png'], ','),
name: text('Form item name: (name)', ''),
multiple: boolean('Supports multiple files (multiple)', true),
iconDescription: text('Close button icon description (iconDescription)', 'Clear file')
}
buttonLabel: text("The button label (buttonLabel)", "Add files"),
status: select("Status for file name (status)", filenameStatuses, "edit"),
accept: array("Accepted file extensions (accept)", [".jpg", ".png"], ","),
name: text("Form item name: (name)", ""),
multiple: boolean("Supports multiple files (multiple)", true),
iconDescription: text(
"Close button icon description (iconDescription)",
"Clear file"
),
},
});
FileUploader.story = { name: 'FileUploader' };
FileUploader.story = { name: "FileUploader" };
export const FileUploaderItem = () => ({
Component,
props: {
story: 'item',
name: text('Filename (name)', 'README.md'),
status: select('Status for file name (status)', filenameStatuses, 'edit'),
iconDescription: text('Close button icon description (iconDescription)', 'Clear file'),
invalid: boolean('Invalid (invalid)', false),
errorSubject: text('Error subject (errorSubject)', 'File size exceeds limit'),
story: "item",
name: text("Filename (name)", "README.md"),
status: select("Status for file name (status)", filenameStatuses, "edit"),
iconDescription: text(
"Close button icon description (iconDescription)",
"Clear file"
),
invalid: boolean("Invalid (invalid)", false),
errorSubject: text(
"Error subject (errorSubject)",
"File size exceeds limit"
),
errorBody: text(
'Error body (errorBody)',
'500kb max file size. Select a new file and try again.'
)
}
"Error body (errorBody)",
"500kb max file size. Select a new file and try again."
),
},
});
FileUploaderItem.story = { name: 'FileUploaderItem' };
FileUploaderItem.story = { name: "FileUploaderItem" };
export const FileUploaderDropContainer = () => ({
Component,
props: {
story: 'drop container',
labelText: text('Label text (labelText)', 'Drag and drop files here or click to upload'),
name: text('Form item name (name)', ''),
multiple: boolean('Supports multiple files (multiple)', true),
accept: array(
'Accepted MIME types or file extensions (accept)',
['image/jpeg', 'image/png'],
','
story: "drop container",
labelText: text(
"Label text (labelText)",
"Drag and drop files here or click to upload"
),
disabled: boolean('Disabled (disabled)', false),
role: text('ARIA role of the button (role)', ''),
tabindex: text('Tab index (tabindex)', '0')
}
name: text("Form item name (name)", ""),
multiple: boolean("Supports multiple files (multiple)", true),
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>
let className = undefined;
export { className as class };
export const clearFiles = () => (files = []);
export let status = "uploading"; // "uploading" | "edit" | "complete"
export let accept = [];
export let buttonLabel = '';
export let files = [];
export let iconDescription = 'Provide icon description';
export let kind = 'primary';
export let labelDescription = '';
export let labelTitle = '';
export const clearFiles = () => (files = []);
export let buttonLabel = "";
export let iconDescription = "Provide icon description";
export let kind = "primary";
export let labelDescription = "";
export let labelTitle = "";
export let multiple = false;
export let name = '';
export let status = 'uploading';
export let style = undefined;
export let name = "";
import { createEventDispatcher, afterUpdate } from 'svelte';
import { cx } from '../../lib';
import Filename from './Filename.svelte';
import FileUploaderButton from './FileUploaderButton.svelte';
import { createEventDispatcher, afterUpdate } from "svelte";
import Filename from "./Filename.svelte";
import FileUploaderButton from "./FileUploaderButton.svelte";
const dispatch = createEventDispatcher();
let prevFiles = [];
$: prevFiles = [];
afterUpdate(() => {
if (files.length > prevFiles.length) {
dispatch('add', files);
dispatch("add", files);
} else {
dispatch(
'remove',
prevFiles.filter(_ => !files.includes(_))
);
dispatch("remove", prevFiles.filter(_ => !files.includes(_)));
}
prevFiles = [...files];
});
</script>
<div on:click on:mouseover on:mouseenter on:mouseleave class={cx('--form-item', className)} {style}>
<strong class={cx('--file--label')}>{labelTitle}</strong>
<p class={cx('--label-description')}>{labelDescription}</p>
<div
class:bx--form-item={true}
{...$$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
disableLabelChanges
labelText={buttonLabel}
on:change
on:change={({ target }) => {
files = [...target.files].map(({ name }) => name);
}}
{accept}
{name}
{multiple}
{kind} />
<div class={cx('--file-container')}>
{kind}
on:change
on:change={({ target }) => {
files = [...target.files].map(({ name }) => name);
}} />
<div class:bx--file-container={true}>
{#each files as name, i (name)}
<span class={cx('--file__selected-file')}>
<p class={cx('--file-filename')}>{name}</p>
<span class={cx('--file__state-container')}>
<span class:bx--file__selected-file={true}>
<p class:bx--file-filename={true}>{name}</p>
<span class:bx--file__state-container={true}>
<Filename
{iconDescription}
{status}
on:keydown
on:keydown={({ key }) => {
if (key === ' ' || key === 'Enter') {
@ -66,9 +67,7 @@
on:click
on:click={() => {
files = files.filter((_, index) => index !== i);
}}
{iconDescription}
{status} />
}} />
</span>
</span>
{/each}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,18 +1,18 @@
<script>
import Layout from '../../internal/ui/Layout.svelte';
import Checkbox from '../Checkbox';
import FormGroup from '../FormGroup';
import FileUploader from '../FileUploader';
import NumberInput from '../NumberInput';
import RadioButton from '../RadioButton';
import RadioButtonGroup from '../RadioButtonGroup';
import Button from '../Button';
import Search from '../Search';
import Select, { SelectItem } from '../Select';
import TextArea from '../TextArea';
import TextInput from '../TextInput';
import Toggle from '../Toggle';
import Form from './Form.svelte';
import Layout from "../../internal/ui/Layout.svelte";
import { Checkbox } from "../Checkbox";
import { FormGroup } from "../FormGroup";
import { FileUploader } from "../FileUploader";
import { NumberInput } from "../NumberInput";
import { RadioButton } from "../RadioButton";
import { RadioButtonGroup } from "../RadioButtonGroup";
import { Button } from "../Button";
import { Search } from "../Search";
import { Select, SelectItem } from "../Select";
import { TextArea } from "../TextArea";
import { TextInput } from "../TextInput";
import { Toggle } from "../Toggle";
import Form from "./Form.svelte";
</script>
<Layout>
@ -25,42 +25,74 @@
<Checkbox id="checkbox-1" labelText="Checkbox Label" />
<Checkbox id="checkbox-2" labelText="Checkbox Label" disabled />
</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">
<Toggle id="toggle-1" />
<Toggle id="toggle-2" disabled />
</FormGroup>
<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 legendText="Radio Button heading">
<RadioButtonGroup name="radio-button-group" defaultSelected="default-selected">
<RadioButton id="radio-1" value="standard" labelText="Standard Radio Button" />
<RadioButtonGroup
name="radio-button-group"
defaultSelected="default-selected">
<RadioButton
id="radio-1"
value="standard"
labelText="Standard Radio Button" />
<RadioButton
id="radio-2"
value="default-selected"
labelText="Default Selected Radio Button" />
<RadioButton id="radio-3" value="blue" labelText="Standard Radio Button" />
<RadioButton id="radio-4" value="disabled" labelText="Disabled Radio Button" disabled />
<RadioButton
id="radio-3"
value="blue"
labelText="Standard Radio Button" />
<RadioButton
id="radio-4"
value="disabled"
labelText="Disabled Radio Button"
disabled />
</RadioButtonGroup>
</FormGroup>
<FormGroup legendText="Search">
<Search id="search-1" labelText="Search" placeholder="Search" />
</FormGroup>
<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-2" text="Option 2" />
<SelectItem value="option-3" text="Option 3" />
</Select>
<TextInput id="text-input-1" labelText="Text Input label" placeholder="Placeholder text" />
<TextInput id="text-input-2" type="password" labelText="Password" required />
<TextInput
id="text-input-1"
labelText="Text Input label"
placeholder="Placeholder text" />
<TextInput
id="text-input-2"
type="password"
labelText="Password"
required />
<TextInput
id="text-input-3"
type="password"
labelText="Password"
invalidText="Your password must be at least 6 characters as well as contain at least one
uppercase, one lowercase, and one number."
invalidText="Your password must be at least 6 characters as well as
contain at least one uppercase, one lowercase, and one number."
required
invalid />
<TextArea

View file

@ -1,14 +1,14 @@
import { withKnobs, text, boolean } from '@storybook/addon-knobs';
import Component from './Form.Story.svelte';
import { withKnobs, text, boolean } from "@storybook/addon-knobs";
import Component from "./Form.Story.svelte";
export default { title: 'Form', decorators: [withKnobs] };
export default { title: "Form", decorators: [withKnobs] };
export const Default = () => ({
Component,
props: {
legendText: text('Text in <legend> (legendText)', 'Checkbox heading'),
message: boolean('Show form requirement (message)', false),
messageText: text('Form requirement text (messageText)', ''),
invalid: boolean('Mark as invalid (invalid)', false)
}
legendText: text("Text in <legend> (legendText)", "Checkbox heading"),
message: boolean("Show form requirement (message)", false),
messageText: text("Form requirement text (messageText)", ""),
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
class:bx--form={true}
{...$$restProps}
on:click
on:mouseover
on:mouseenter
on:mouseleave
on:submit|preventDefault
class={cx('--form', className)}
{style}>
on:submit|preventDefault>
<slot />
</form>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,11 +1,9 @@
<script>
let className = undefined;
export { className as class };
export let style = undefined;
import { cx } from '../../lib';
</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>
<slot />
</div>

View file

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

View file

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

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