Skip to content

Commit 6ba0fdf

Browse files
committed
feat(selectable-tile): add SelectableTileGroup component
Adds a new `SelectableTileGroup` component that manages multiple `SelectableTile` components using Svelte context API.
1 parent a25f9ea commit 6ba0fdf

File tree

10 files changed

+557
-7
lines changed

10 files changed

+557
-7
lines changed

COMPONENT_INDEX.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Component Index
22

3-
> 166 components exported from carbon-components-svelte@0.93.0.
3+
> 167 components exported from carbon-components-svelte@0.93.0.
44
55
## Components
66

@@ -112,6 +112,7 @@
112112
- [`SelectItemGroup`](#selectitemgroup)
113113
- [`SelectSkeleton`](#selectskeleton)
114114
- [`SelectableTile`](#selectabletile)
115+
- [`SelectableTileGroup`](#selectabletilegroup)
115116
- [`SideNav`](#sidenav)
116117
- [`SideNavDivider`](#sidenavdivider)
117118
- [`SideNavItems`](#sidenavitems)
@@ -3320,6 +3321,30 @@ None.
33203321
| mouseleave | forwarded | -- | -- |
33213322
| keydown | forwarded | -- | -- |
33223323

3324+
## `SelectableTileGroup`
3325+
3326+
### Props
3327+
3328+
| Prop name | Required | Kind | Reactive | Type | Default value | Description |
3329+
| :-------- | :------- | :--------------- | :------- | ------------------------------------ | ---------------------- | ------------------------------------------------ |
3330+
| selected | No | <code>let</code> | Yes | <code>T[]</code> | <code>[]</code> | Specify the selected tile values |
3331+
| disabled | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to disable the tile group |
3332+
| name | No | <code>let</code> | No | <code>string &#124; undefined</code> | <code>undefined</code> | Specify a name attribute for the checkbox inputs |
3333+
| legend | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the legend text |
3334+
3335+
### Slots
3336+
3337+
| Slot name | Default | Props | Fallback |
3338+
| :-------- | :------ | :---------------------------------- | :------- |
3339+
| -- | Yes | <code>Record<string, never> </code> | -- |
3340+
3341+
### Events
3342+
3343+
| Event name | Type | Detail | Description |
3344+
| :--------- | :--------- | :------------- | :---------- |
3345+
| select | dispatched | <code>T</code> | -- |
3346+
| deselect | dispatched | <code>T</code> | -- |
3347+
33233348
## `SideNav`
33243349

33253350
### Props

docs/src/COMPONENT_API.json

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"total": 166,
2+
"total": 167,
33
"components": [
44
{
55
"moduleName": "Accordion",
@@ -13651,6 +13651,120 @@
1365113651
},
1365213652
"contexts": []
1365313653
},
13654+
{
13655+
"moduleName": "SelectableTileGroup",
13656+
"filePath": "src/Tile/SelectableTileGroup.svelte",
13657+
"props": [
13658+
{
13659+
"name": "selected",
13660+
"kind": "let",
13661+
"description": "Specify the selected tile values",
13662+
"type": "T[]",
13663+
"value": "[]",
13664+
"isFunction": false,
13665+
"isFunctionDeclaration": false,
13666+
"isRequired": false,
13667+
"constant": false,
13668+
"reactive": true
13669+
},
13670+
{
13671+
"name": "disabled",
13672+
"kind": "let",
13673+
"description": "Set to `true` to disable the tile group",
13674+
"type": "boolean",
13675+
"value": "false",
13676+
"isFunction": false,
13677+
"isFunctionDeclaration": false,
13678+
"isRequired": false,
13679+
"constant": false,
13680+
"reactive": false
13681+
},
13682+
{
13683+
"name": "name",
13684+
"kind": "let",
13685+
"description": "Specify a name attribute for the checkbox inputs",
13686+
"type": "string | undefined",
13687+
"value": "undefined",
13688+
"isFunction": false,
13689+
"isFunctionDeclaration": false,
13690+
"isRequired": false,
13691+
"constant": false,
13692+
"reactive": false
13693+
},
13694+
{
13695+
"name": "legend",
13696+
"kind": "let",
13697+
"description": "Specify the legend text",
13698+
"type": "string",
13699+
"value": "\"\"",
13700+
"isFunction": false,
13701+
"isFunctionDeclaration": false,
13702+
"isRequired": false,
13703+
"constant": false,
13704+
"reactive": false
13705+
}
13706+
],
13707+
"moduleExports": [],
13708+
"slots": [
13709+
{
13710+
"name": null,
13711+
"default": true,
13712+
"slot_props": "Record<string, never>"
13713+
}
13714+
],
13715+
"events": [
13716+
{
13717+
"type": "dispatched",
13718+
"name": "select",
13719+
"detail": "T"
13720+
},
13721+
{
13722+
"type": "dispatched",
13723+
"name": "deselect",
13724+
"detail": "T"
13725+
}
13726+
],
13727+
"typedefs": [],
13728+
"generics": [
13729+
"T",
13730+
"T extends string = string"
13731+
],
13732+
"rest_props": {
13733+
"type": "Element",
13734+
"name": "fieldset"
13735+
},
13736+
"contexts": [
13737+
{
13738+
"key": "SelectableTileGroup",
13739+
"typeName": "SelectableTileGroupContext",
13740+
"properties": [
13741+
{
13742+
"name": "selectedValues",
13743+
"type": "import(\"svelte/store\").Writable<T[]>",
13744+
"description": "",
13745+
"optional": false
13746+
},
13747+
{
13748+
"name": "groupName",
13749+
"type": "any",
13750+
"optional": false
13751+
},
13752+
{
13753+
"name": "add",
13754+
"type": "(data: { selected: boolean; value: T }) => void",
13755+
"description": "",
13756+
"optional": false
13757+
},
13758+
{
13759+
"name": "update",
13760+
"type": "(data: { value: T; selected: boolean }) => void",
13761+
"description": "",
13762+
"optional": false
13763+
}
13764+
]
13765+
}
13766+
]
13767+
},
1365413768
{
1365513769
"moduleName": "SideNav",
1365613770
"filePath": "src/UIShell/SideNav.svelte",

src/Tile/SelectableTile.svelte

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,26 @@
3737
/** Obtain a reference to the input HTML element */
3838
export let ref = null;
3939
40-
import { createEventDispatcher } from "svelte";
40+
import { createEventDispatcher, getContext } from "svelte";
41+
import { readable } from "svelte/store";
4142
import CheckmarkFilled from "../icons/CheckmarkFilled.svelte";
4243
4344
const dispatch = createEventDispatcher();
4445
45-
$: if (!disabled) dispatch(selected ? "select" : "deselect", id);
46+
const ctx = getContext("SelectableTileGroup");
47+
const hasGroup = ctx !== undefined;
48+
const {
49+
add = () => {},
50+
update = () => {},
51+
selectedValues = readable([]),
52+
groupName = readable(undefined),
53+
} = ctx ?? {};
54+
55+
add({ value, selected });
56+
57+
$: if (hasGroup) {
58+
selected = $selectedValues.includes(value);
59+
}
4660
</script>
4761
4862
<input
@@ -53,9 +67,24 @@
5367
checked={selected}
5468
{id}
5569
{value}
56-
{name}
70+
name={$groupName ?? name}
5771
{title}
5872
{disabled}
73+
on:change={() => {
74+
if (disabled) return;
75+
if (!ref) return;
76+
const newSelected = ref.checked;
77+
selected = newSelected;
78+
if (hasGroup) {
79+
update({ value, selected: newSelected });
80+
} else {
81+
if (newSelected) {
82+
dispatch("select", id);
83+
} else {
84+
dispatch("deselect", id);
85+
}
86+
}
87+
}}
5988
/>
6089
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
6190
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
@@ -72,7 +101,22 @@
72101
on:click
73102
on:click|preventDefault={() => {
74103
if (disabled) return;
75-
selected = !selected;
104+
const newSelected = !selected;
105+
selected = newSelected;
106+
107+
if (ref) {
108+
ref.checked = newSelected;
109+
}
110+
111+
if (hasGroup) {
112+
update({ value, selected: newSelected });
113+
} else {
114+
if (newSelected) {
115+
dispatch("select", id);
116+
} else {
117+
dispatch("deselect", id);
118+
}
119+
}
76120
}}
77121
on:mouseover
78122
on:mouseenter
@@ -82,7 +126,9 @@
82126
if (disabled) return;
83127
if (e.key === " " || e.key === "Enter") {
84128
e.preventDefault();
85-
selected = !selected;
129+
if (ref) {
130+
ref.click();
131+
}
86132
}
87133
}}
88134
>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<script>
2+
/**
3+
* @generics {T extends string = string} T
4+
* @template {string} T
5+
* @event {T} select
6+
* @event {T} deselect
7+
*/
8+
9+
/**
10+
* Specify the selected tile values
11+
* @type {T[]}
12+
*/
13+
export let selected = [];
14+
15+
/** Set to `true` to disable the tile group */
16+
export let disabled = false;
17+
18+
/**
19+
* Specify a name attribute for the checkbox inputs
20+
* @type {string | undefined}
21+
*/
22+
export let name = undefined;
23+
24+
/** Specify the legend text */
25+
export let legend = "";
26+
27+
import { createEventDispatcher, setContext } from "svelte";
28+
import { readonly, writable } from "svelte/store";
29+
30+
const dispatch = createEventDispatcher();
31+
/**
32+
* @type {import("svelte/store").Writable<T[]>}
33+
*/
34+
const selectedValues = writable(selected);
35+
/**
36+
* @type {import("svelte/store").Writable<string | undefined>}
37+
*/
38+
const groupName = writable(name);
39+
40+
/**
41+
* @type {(data: { selected: boolean; value: T }) => void}
42+
*/
43+
const add = ({ selected: isSelected, value }) => {
44+
if (isSelected && !$selectedValues.includes(value)) {
45+
selectedValues.update((values) => [...values, value]);
46+
}
47+
};
48+
49+
/**
50+
* @type {(data: { value: T; selected: boolean }) => void}
51+
*/
52+
const update = ({ value, selected: isSelected }) => {
53+
if (isSelected) {
54+
if (!$selectedValues.includes(value)) {
55+
selectedValues.update((values) => [...values, value]);
56+
dispatch("select", value);
57+
}
58+
} else {
59+
if ($selectedValues.includes(value)) {
60+
selectedValues.update((values) => values.filter((v) => v !== value));
61+
dispatch("deselect", value);
62+
}
63+
}
64+
};
65+
66+
setContext("SelectableTileGroup", {
67+
selectedValues,
68+
groupName: readonly(groupName),
69+
add,
70+
update,
71+
});
72+
73+
$: selected = $selectedValues;
74+
$: selectedValues.set(selected);
75+
$: $groupName = name;
76+
</script>
77+
78+
<fieldset {disabled} class:bx--tile-group={true} {...$$restProps}>
79+
{#if legend}
80+
<legend class:bx--label={true}>{legend}</legend>
81+
{/if}
82+
<div>
83+
<slot />
84+
</div>
85+
</fieldset>

src/Tile/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ export { default as ClickableTile } from "./ClickableTile.svelte";
22
export { default as ExpandableTile } from "./ExpandableTile.svelte";
33
export { default as RadioTile } from "./RadioTile.svelte";
44
export { default as SelectableTile } from "./SelectableTile.svelte";
5+
export { default as SelectableTileGroup } from "./SelectableTileGroup.svelte";
56
export { default as Tile } from "./Tile.svelte";
67
export { default as TileGroup } from "./TileGroup.svelte";

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export { default as ClickableTile } from "./Tile/ClickableTile.svelte";
133133
export { default as ExpandableTile } from "./Tile/ExpandableTile.svelte";
134134
export { default as RadioTile } from "./Tile/RadioTile.svelte";
135135
export { default as SelectableTile } from "./Tile/SelectableTile.svelte";
136+
export { default as SelectableTileGroup } from "./Tile/SelectableTileGroup.svelte";
136137
export { default as Tile } from "./Tile/Tile.svelte";
137138
export { default as TileGroup } from "./Tile/TileGroup.svelte";
138139
export { default as TimePicker } from "./TimePicker/TimePicker.svelte";
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<svelte:options accessors />
2+
3+
<script lang="ts">
4+
import {
5+
SelectableTile,
6+
SelectableTileGroup,
7+
} from "carbon-components-svelte";
8+
import type { ComponentProps } from "svelte";
9+
10+
export let selected: ComponentProps<SelectableTileGroup>["selected"] = [];
11+
export let disabled: ComponentProps<SelectableTileGroup>["disabled"] = false;
12+
export let name: ComponentProps<SelectableTileGroup>["name"] = undefined;
13+
export let legend: ComponentProps<SelectableTileGroup>["legend"] = "";
14+
export let customClass = "";
15+
</script>
16+
17+
<SelectableTileGroup
18+
bind:selected
19+
{disabled}
20+
{name}
21+
{legend}
22+
class={customClass}
23+
on:select={(e) => {
24+
console.log("select", e.detail);
25+
}}
26+
on:deselect={(e) => {
27+
console.log("deselect", e.detail);
28+
}}
29+
>
30+
<SelectableTile value="option1">Option 1</SelectableTile>
31+
<SelectableTile value="option2">Option 2</SelectableTile>
32+
<SelectableTile value="option3">Option 3</SelectableTile>
33+
</SelectableTileGroup>

0 commit comments

Comments
 (0)