mirror of
https://github.com/carbon-design-system/carbon-components-svelte.git
synced 2025-09-14 18:01:06 +00:00
feat(components): add Tile
Supports #34 TODO: - remove exported props - compose TileGroup, SelectableTile, RadioTile components
This commit is contained in:
parent
f048e9ea4a
commit
46cb9aa44b
12 changed files with 472 additions and 2 deletions
42
src/components/Tile/ClickableTile.svelte
Normal file
42
src/components/Tile/ClickableTile.svelte
Normal file
|
@ -0,0 +1,42 @@
|
|||
<script>
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
export let href = undefined;
|
||||
export let rel = undefined;
|
||||
export let light = false;
|
||||
export let clicked = false;
|
||||
export let props = {};
|
||||
|
||||
import { createEventDispatcher, tick } from 'svelte';
|
||||
import { cx } from '../../lib';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
async function handleClick(event) {
|
||||
clicked = !clicked;
|
||||
await tick();
|
||||
dispatch('click', event);
|
||||
}
|
||||
|
||||
async function handleKeyDown(event) {
|
||||
if (event.key === ' ' || event.key === 'Enter') {
|
||||
clicked = !clicked;
|
||||
await tick();
|
||||
}
|
||||
|
||||
dispatch('keydown', event);
|
||||
}
|
||||
|
||||
$: _class = cx(
|
||||
'--link',
|
||||
'--tile',
|
||||
'--tile--clickable',
|
||||
clicked && '--tile--is-clicked',
|
||||
light && '--tile--light',
|
||||
className
|
||||
);
|
||||
</script>
|
||||
|
||||
<a {...props} class={_class} on:click={handleClick} on:keydown={handleKeyDown} {href} {rel}>
|
||||
<slot />
|
||||
</a>
|
88
src/components/Tile/ExpandableTile.svelte
Normal file
88
src/components/Tile/ExpandableTile.svelte
Normal file
|
@ -0,0 +1,88 @@
|
|||
<script>
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
export let expanded = false;
|
||||
export let id = Math.random();
|
||||
export let tileCollapsedIconText = 'Interact to expand Tile';
|
||||
export let tileExpandedIconText = 'Interact to collapse Tile';
|
||||
export let tileMaxHeight = 0;
|
||||
export let tilePadding = 0;
|
||||
export let tabIndex = 0;
|
||||
export let light = false;
|
||||
export let props = {};
|
||||
|
||||
import { createEventDispatcher, tick, onMount } from 'svelte';
|
||||
import ChevronDown16 from 'carbon-icons-svelte/lib/ChevronDown16';
|
||||
import { cx } from '../../lib';
|
||||
|
||||
let tile = undefined;
|
||||
let tileContent = undefined;
|
||||
let aboveTheFold = undefined;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
onMount(() => {
|
||||
const tileStyle = window.getComputedStyle(tile, null);
|
||||
tileMaxHeight = aboveTheFold.getBoundingClientRect().height;
|
||||
tilePadding =
|
||||
parseInt(tileStyle.getPropertyValue('padding-top'), 10) +
|
||||
parseInt(tileStyle.getPropertyValue('padding-bottom'), 10);
|
||||
});
|
||||
|
||||
function setMaxHeight() {
|
||||
tileMaxHeight = expanded
|
||||
? tileContent.getBoundingClientRect().height
|
||||
: aboveTheFold.getBoundingClientRect().height;
|
||||
}
|
||||
|
||||
async function handleClick(event) {
|
||||
expanded = !expanded;
|
||||
await tick();
|
||||
setMaxHeight();
|
||||
dispatch('click', event);
|
||||
}
|
||||
|
||||
async function handleKeyPress(event) {
|
||||
if (event.key === ' ' || event.key === 'Enter') {
|
||||
expanded = !expanded;
|
||||
await tick();
|
||||
setMaxHeight();
|
||||
}
|
||||
|
||||
dispatch('keypress', event);
|
||||
}
|
||||
|
||||
$: _class = cx(
|
||||
'--tile',
|
||||
'--tile--expandable',
|
||||
expanded && '--tile--is-expanded',
|
||||
light && '--tile--light',
|
||||
className
|
||||
);
|
||||
$: tileStyle = expanded ? undefined : `max-height: ${tileMaxHeight + tilePadding}px`;
|
||||
</script>
|
||||
|
||||
<div
|
||||
{...props}
|
||||
bind:this={tile}
|
||||
style={tileStyle}
|
||||
class={_class}
|
||||
on:click={handleClick}
|
||||
on:keypress={handleKeyPress}
|
||||
tabindex={tabIndex}
|
||||
{id}>
|
||||
<div bind:this={tileContent}>
|
||||
<div bind:this={aboveTheFold} class={cx('--tile-content')}>
|
||||
<slot name="above" />
|
||||
</div>
|
||||
<button
|
||||
aria-expanded={expanded}
|
||||
aria-label={expanded ? tileExpandedIconText : tileCollapsedIconText}
|
||||
class={cx('--tile__chevron')}>
|
||||
<ChevronDown16 />
|
||||
</button>
|
||||
<div class={cx('--tile-content')}>
|
||||
<slot name="below" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
59
src/components/Tile/RadioTile.svelte
Normal file
59
src/components/Tile/RadioTile.svelte
Normal file
|
@ -0,0 +1,59 @@
|
|||
<script>
|
||||
// TODO: compose as "children" components
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
export let checked = false;
|
||||
export let id = Math.random();
|
||||
export let name = '';
|
||||
export let iconDescription = 'Tile checkmark';
|
||||
export let value = '';
|
||||
export let tabIndex = 0;
|
||||
export let light = false;
|
||||
export let props = {};
|
||||
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import CheckmarkFilled16 from 'carbon-icons-svelte/lib/CheckmarkFilled16';
|
||||
import { cx } from '../../lib';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function handleChange(event) {
|
||||
dispatch('change', event);
|
||||
}
|
||||
|
||||
function handleKeyDown(event) {
|
||||
if (event.key === ' ' || event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
handleChange(event);
|
||||
}
|
||||
|
||||
dispatch('keydown', event);
|
||||
}
|
||||
|
||||
$: _class = cx(
|
||||
'--tile',
|
||||
'--tile--selectable',
|
||||
checked && '--tile--is-selected',
|
||||
light && '--tile--light',
|
||||
className
|
||||
);
|
||||
</script>
|
||||
|
||||
<input
|
||||
{...props}
|
||||
type="radio"
|
||||
class={cx('--tile-input')}
|
||||
on:change
|
||||
on:change={handleChange}
|
||||
{id}
|
||||
{name}
|
||||
{value}
|
||||
{checked} />
|
||||
<label for={id} class={_class} tabindex={tabIndex} on:keydown={handleKeyDown}>
|
||||
<span class={cx('--tile__checkmark')}>
|
||||
<CheckmarkFilled16 aria-label={iconDescription} title={iconDescription} />
|
||||
</span>
|
||||
<span class={cx('--tile-content')}>
|
||||
<slot />
|
||||
</span>
|
||||
</label>
|
78
src/components/Tile/SelectableTile.svelte
Normal file
78
src/components/Tile/SelectableTile.svelte
Normal file
|
@ -0,0 +1,78 @@
|
|||
<script>
|
||||
// TODO: emit current selected tile
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
export let selected = false;
|
||||
export let id = Math.random();
|
||||
export let value = 'value';
|
||||
export let title = 'title';
|
||||
export let name = '';
|
||||
export let iconDescription = 'Tile checkmark';
|
||||
export let tabIndex = 0;
|
||||
export let light = false;
|
||||
export let props = {};
|
||||
|
||||
import { createEventDispatcher, tick } from 'svelte';
|
||||
import CheckmarkFilled16 from 'carbon-icons-svelte/lib/CheckmarkFilled16';
|
||||
import { cx } from '../../lib';
|
||||
|
||||
let input = undefined;
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function handleChange(event) {
|
||||
dispatch('change', event);
|
||||
}
|
||||
|
||||
async function handleClick(event) {
|
||||
if (event.target !== input) {
|
||||
selected = !selected;
|
||||
await tick();
|
||||
}
|
||||
|
||||
dispatch('click', event);
|
||||
}
|
||||
|
||||
async function handleKeyDown(event) {
|
||||
if (event.key === ' ' || event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
selected = !selected;
|
||||
await tick();
|
||||
}
|
||||
|
||||
dispatch('keydown', event);
|
||||
}
|
||||
|
||||
$: _class = cx(
|
||||
'--tile',
|
||||
'--tile--selectable',
|
||||
selected && '--tile--is-selected',
|
||||
light && '--tile--light',
|
||||
className
|
||||
);
|
||||
</script>
|
||||
|
||||
<input
|
||||
bind:this={input}
|
||||
type="checkbox"
|
||||
tabindex={-1}
|
||||
class={cx('--tile-input')}
|
||||
on:change={handleChange}
|
||||
checked={selected}
|
||||
{id}
|
||||
{value}
|
||||
{name}
|
||||
{title} />
|
||||
<label
|
||||
{...props}
|
||||
for={id}
|
||||
class={_class}
|
||||
tabindex={tabIndex}
|
||||
on:click|preventDefault={handleClick}
|
||||
on:keydown={handleKeyDown}>
|
||||
<span class={cx('--tile__checkmark')}>
|
||||
<CheckmarkFilled16 aria-label={iconDescription} title={iconDescription} />
|
||||
</span>
|
||||
<span class={cx('--tile-content')}>
|
||||
<slot />
|
||||
</span>
|
||||
</label>
|
68
src/components/Tile/Tile.Story.svelte
Normal file
68
src/components/Tile/Tile.Story.svelte
Normal file
|
@ -0,0 +1,68 @@
|
|||
<script>
|
||||
export let story = undefined;
|
||||
|
||||
import Layout from '../../internal/ui/Layout.svelte';
|
||||
import Tile from './Tile.svelte';
|
||||
import ClickableTile from './ClickableTile.svelte';
|
||||
import SelectableTile from './SelectableTile.svelte';
|
||||
import ExpandableTile from './ExpandableTile.svelte';
|
||||
import TileAboveTheFoldContent from './TileAboveTheFoldContent.svelte';
|
||||
import TileBelowTheFoldContent from './TileBelowTheFoldContent.svelte';
|
||||
import TileGroup from './TileGroup.svelte';
|
||||
import RadioTile from './RadioTile.svelte';
|
||||
|
||||
const radioTiles = [
|
||||
{ value: 'standard', id: 'tile-1', labelText: 'Selectable Tile' },
|
||||
{ value: 'default-selected', id: 'tile-2', labelText: 'Selectable Tile' },
|
||||
{ value: 'selected', id: 'tile-3', labelText: 'Selectable Tile' }
|
||||
];
|
||||
|
||||
let selected = radioTiles[1].value;
|
||||
</script>
|
||||
|
||||
<Layout>
|
||||
<div>
|
||||
{#if story === 'filter'}
|
||||
<Tile {...$$props} />
|
||||
{:else if story === 'clickable'}
|
||||
<ClickableTile {...$$props}>Clickable Tile</ClickableTile>
|
||||
{:else if story === 'multi-select'}
|
||||
<div role="group" aria-label="selectable tiles">
|
||||
<SelectableTile id="tile-1" name="tiles" {...$$props}>Multi-select Tile</SelectableTile>
|
||||
<SelectableTile id="tile-2" name="tiles" {...$$props}>Multi-select Tile</SelectableTile>
|
||||
<SelectableTile id="tile-3" name="tiles" {...$$props}>Multi-select Tile</SelectableTile>
|
||||
</div>
|
||||
{:else if story === 'selectable'}
|
||||
<TileGroup legend="Selectable Tile Group">
|
||||
{#each radioTiles as { value, id, labelText }, i (id)}
|
||||
<RadioTile
|
||||
{...$$props}
|
||||
checked={selected === value}
|
||||
on:change={() => {
|
||||
selected = value;
|
||||
}}
|
||||
{value}
|
||||
{id}
|
||||
{labelText}>
|
||||
Selectable Tile
|
||||
</RadioTile>
|
||||
{/each}
|
||||
</TileGroup>
|
||||
{:else if story === 'expandable'}
|
||||
<ExpandableTile {...$$props}>
|
||||
<div slot="above">
|
||||
<TileAboveTheFoldContent>
|
||||
<div style="height: 200px">Above the fold content here</div>
|
||||
</TileAboveTheFoldContent>
|
||||
</div>
|
||||
<div slot="below">
|
||||
<TileBelowTheFoldContent>
|
||||
<div style="height: 400px">Below the fold content here</div>
|
||||
</TileBelowTheFoldContent>
|
||||
</div>
|
||||
</ExpandableTile>
|
||||
{:else}
|
||||
<Tile {...$$props}>Default Tile</Tile>
|
||||
{/if}
|
||||
</div>
|
||||
</Layout>
|
64
src/components/Tile/Tile.stories.js
Normal file
64
src/components/Tile/Tile.stories.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
import { withKnobs, select, number, boolean, text } from '@storybook/addon-knobs';
|
||||
import Component from './Tile.Story.svelte';
|
||||
|
||||
export default { title: 'Tile', decorators: [withKnobs] };
|
||||
|
||||
export const Default = () => ({
|
||||
Component,
|
||||
props: {
|
||||
light: boolean('Light variant (light)', false)
|
||||
}
|
||||
});
|
||||
|
||||
export const Clickable = () => ({
|
||||
Component,
|
||||
props: {
|
||||
story: 'clickable',
|
||||
href: text('Href for clickable UI (href)', 'javascript:void(0)'),
|
||||
light: boolean('Light variant (light)', false)
|
||||
}
|
||||
});
|
||||
|
||||
export const MultiSelect = () => ({
|
||||
Component,
|
||||
props: {
|
||||
story: 'multi-select',
|
||||
selected: boolean('Selected (selected)', false),
|
||||
light: boolean('Light variant (light)', false)
|
||||
}
|
||||
});
|
||||
|
||||
const radioValues = {
|
||||
None: '',
|
||||
standard: 'standard',
|
||||
'default-selected': 'default-selected',
|
||||
selected: 'selected'
|
||||
};
|
||||
|
||||
export const Selectable = () => ({
|
||||
Component,
|
||||
props: {
|
||||
story: 'selectable',
|
||||
name: text('Form item name (name in <RadioTile>)', 'tiles'),
|
||||
light: boolean('Light variant (light)', false)
|
||||
}
|
||||
});
|
||||
|
||||
export const Expandable = () => ({
|
||||
Component,
|
||||
props: {
|
||||
story: 'expandable',
|
||||
tabIndex: number('Tab index (tabIndex)', 0),
|
||||
expanded: boolean('Expanded (expanded)', false),
|
||||
tileMaxHeight: number('Max height (tileMaxHeight)', 0),
|
||||
tileCollapsedIconText: text(
|
||||
'Collapsed icon text (tileCollapsedIconText)',
|
||||
'Interact to Expand tile'
|
||||
),
|
||||
tileExpandedIconText: text(
|
||||
'Collapsed icon text (tileExpandedIconText)',
|
||||
'Interact to Collapse tile'
|
||||
),
|
||||
light: boolean('Light variant (light)', false)
|
||||
}
|
||||
});
|
14
src/components/Tile/Tile.svelte
Normal file
14
src/components/Tile/Tile.svelte
Normal file
|
@ -0,0 +1,14 @@
|
|||
<script>
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
export let light = false;
|
||||
export let props = {};
|
||||
|
||||
import { cx } from '../../lib';
|
||||
|
||||
$: _class = cx('--tile', light && '--tile--light', className);
|
||||
</script>
|
||||
|
||||
<div class={_class} {...props}>
|
||||
<slot />
|
||||
</div>
|
7
src/components/Tile/TileAboveTheFoldContent.svelte
Normal file
7
src/components/Tile/TileAboveTheFoldContent.svelte
Normal file
|
@ -0,0 +1,7 @@
|
|||
<script>
|
||||
import { cx } from '../../lib';
|
||||
</script>
|
||||
|
||||
<span class={cx('--tile-content__above-the-fold')}>
|
||||
<slot />
|
||||
</span>
|
7
src/components/Tile/TileBelowTheFoldContent.svelte
Normal file
7
src/components/Tile/TileBelowTheFoldContent.svelte
Normal file
|
@ -0,0 +1,7 @@
|
|||
<script>
|
||||
import { cx } from '../../lib';
|
||||
</script>
|
||||
|
||||
<span class={cx('--tile-content__below-the-fold')}>
|
||||
<slot />
|
||||
</span>
|
19
src/components/Tile/TileGroup.svelte
Normal file
19
src/components/Tile/TileGroup.svelte
Normal file
|
@ -0,0 +1,19 @@
|
|||
<script>
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
export let disabled = false;
|
||||
export let legend = '';
|
||||
|
||||
import { cx } from '../../lib';
|
||||
|
||||
$: _class = cx('--tile-group', className);
|
||||
</script>
|
||||
|
||||
<fieldset class={_class} {disabled}>
|
||||
{#if legend}
|
||||
<legend>{legend}</legend>
|
||||
{/if}
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
</fieldset>
|
9
src/components/Tile/index.js
Normal file
9
src/components/Tile/index.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import Tile from './Tile.svelte';
|
||||
|
||||
export default Tile;
|
||||
export { default as ClickableTile } from './ClickableTile.svelte';
|
||||
export { default as ExpandableTile } from './ExpandableTile.svelte';
|
||||
export { default as SelectableTile } from './SelectableTile.svelte';
|
||||
export { default as TileGroup } from './TileGroup.svelte';
|
||||
export { default as TileAboveTheFoldContent } from './TileAboveTheFoldContent.svelte';
|
||||
export { default as TileBelowTheFoldContent } from './TileBelowTheFoldContent.svelte';
|
19
src/index.js
19
src/index.js
|
@ -18,6 +18,14 @@ import SkeletonText from './components/SkeletonText';
|
|||
import Tag, { TagSkeleton } from './components/Tag';
|
||||
import TextArea, { TextAreaSkeleton } from './components/TextArea';
|
||||
import TextInput, { TextInputSkeleton, PasswordInput } from './components/TextInput';
|
||||
import Tile, {
|
||||
ClickableTile,
|
||||
ExpandableTile,
|
||||
SelectableTile,
|
||||
TileGroup,
|
||||
TileAboveTheFoldContent,
|
||||
TileBelowTheFoldContent
|
||||
} from './components/Tile';
|
||||
import Toggle, { ToggleSkeleton } from './components/Toggle';
|
||||
import ToggleSmall, { ToggleSmallSkeleton } from './components/ToggleSmall';
|
||||
import TooltipDefinition from './components/TooltipDefinition';
|
||||
|
@ -35,20 +43,24 @@ export {
|
|||
ButtonSkeleton,
|
||||
Checkbox,
|
||||
CheckboxSkeleton,
|
||||
ClickableTile,
|
||||
CodeSnippet,
|
||||
CodeSnippetSkeleton,
|
||||
Copy,
|
||||
CopyButton,
|
||||
DataTableSkeleton,
|
||||
ExpandableTile,
|
||||
InlineLoading,
|
||||
Loading,
|
||||
Link,
|
||||
ListItem,
|
||||
Loading,
|
||||
OrderedList,
|
||||
PasswordInput,
|
||||
RadioButton,
|
||||
RadioButtonSkeleton,
|
||||
Search,
|
||||
SearchSkeleton,
|
||||
SelectableTile,
|
||||
SkeletonPlaceholder,
|
||||
SkeletonText,
|
||||
Tag,
|
||||
|
@ -57,7 +69,10 @@ export {
|
|||
TextAreaSkeleton,
|
||||
TextInput,
|
||||
TextInputSkeleton,
|
||||
PasswordInput,
|
||||
Tile,
|
||||
TileAboveTheFoldContent,
|
||||
TileBelowTheFoldContent,
|
||||
TileGroup,
|
||||
Toggle,
|
||||
ToggleSkeleton,
|
||||
ToggleSmall,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue