@streamscloud/kit 0.25.0 → 0.25.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -65,16 +65,22 @@
65
65
  // COLOR — TONES (categorical palette; fg + soft bg, component-neutral)
66
66
  // ============================================================
67
67
  --sc-kit--tone--gray--fg: light-dark(#{$color-neutral-500}, #{$color-neutral-400});
68
+ --sc-kit--tone--gray--muted: light-dark(#{$color-neutral-400}, #{$color-neutral-600});
68
69
  --sc-kit--tone--gray--bg: light-dark(#{$color-neutral-50}, #{$color-dark-600});
69
70
  --sc-kit--tone--blue--fg: light-dark(#{$color-blue-500}, #{$color-blue-400});
71
+ --sc-kit--tone--blue--muted: light-dark(#{$color-blue-200}, #{$color-blue-700});
70
72
  --sc-kit--tone--blue--bg: light-dark(#{$color-blue-50}, #{$color-blue-900});
71
73
  --sc-kit--tone--green--fg: light-dark(#{$color-green-500}, #{$color-green-400});
74
+ --sc-kit--tone--green--muted: light-dark(#{$color-green-200}, #{$color-green-700});
72
75
  --sc-kit--tone--green--bg: light-dark(#{$color-green-50}, #{$color-green-900});
73
76
  --sc-kit--tone--red--fg: light-dark(#{$color-red-500}, #{$color-red-400});
77
+ --sc-kit--tone--red--muted: light-dark(#{$color-red-200}, #{$color-red-700});
74
78
  --sc-kit--tone--red--bg: light-dark(#{$color-red-50}, #{$color-red-900});
75
79
  --sc-kit--tone--orange--fg: light-dark(#{$color-orange-500}, #{$color-orange-400});
80
+ --sc-kit--tone--orange--muted: light-dark(#{$color-orange-200}, #{$color-orange-700});
76
81
  --sc-kit--tone--orange--bg: light-dark(#{$color-orange-100}, #{$color-orange-900});
77
82
  --sc-kit--tone--teal--fg: light-dark(#3d7068, #5f938a);
83
+ --sc-kit--tone--teal--muted: light-dark(#3d7068, #5f938a);
78
84
  --sc-kit--tone--teal--bg: light-dark(#eef4f3, #16302b);
79
85
 
80
86
  // ============================================================
@@ -13,18 +13,22 @@ const isSelected = (candidate) => value.some((selected) => eq(selected, candidat
13
13
  const withValue = (list, candidate) => (list.some((item) => eq(item, candidate)) ? list : [...list, candidate]);
14
14
  const without = (list, candidate) => list.filter((item) => !eq(item, candidate));
15
15
  const optionDisplay = $derived(single ? 'checkmark' : 'checkbox');
16
- const leaves = $derived(options.flatMap((item) => (isSelectGroup(item) ? item.options : [item])));
17
- const selectedLeaves = $derived(leaves.filter((leaf) => isSelected(leaf.value)));
16
+ const selectedOptions = $derived(options
17
+ .flatMap((item) => isSelectGroup(item) ? (item.value !== undefined ? [{ label: item.label, value: item.value }, ...item.options] : item.options) : [item])
18
+ .filter((option) => isSelected(option.value)));
18
19
  const summary = $derived.by(() => {
19
- if (selectedLeaves.length === 0) {
20
+ if (selectedOptions.length === 0) {
20
21
  return label;
21
22
  }
22
- if (selectedLeaves.length === 1) {
23
- return `${label}: ${selectedLeaves[0].label}`;
23
+ if (selectedOptions.length === 1) {
24
+ return `${label}: ${selectedOptions[0].label}`;
24
25
  }
25
- return `${label}: ${localization.selectedCount(selectedLeaves.length)}`;
26
+ return `${label}: ${localization.selectedCount(selectedOptions.length)}`;
26
27
  });
27
28
  const groupSelected = (group) => {
29
+ if (group.value !== undefined) {
30
+ return isSelected(group.value);
31
+ }
28
32
  const selectedCount = group.options.filter((option) => isSelected(option.value)).length;
29
33
  if (selectedCount === 0) {
30
34
  return false;
@@ -41,7 +45,17 @@ const toggleOption = (option) => {
41
45
  emit(isSelected(option.value) ? without(value, option.value) : withValue(value, option.value));
42
46
  };
43
47
  const toggleGroup = (group) => {
48
+ if (group.value !== undefined) {
49
+ if (single) {
50
+ pickSingle(group.value);
51
+ return;
52
+ }
53
+ emit(isSelected(group.value) ? without(value, group.value) : withValue(value, group.value));
54
+ return;
55
+ }
44
56
  if (single) {
57
+ const hasSelectedChild = group.options.some((option) => isSelected(option.value));
58
+ emit(hasSelectedChild ? [] : group.options.slice(0, 1).map((option) => option.value));
45
59
  return;
46
60
  }
47
61
  const allSelected = group.options.every((option) => isSelected(option.value));
@@ -49,22 +63,14 @@ const toggleGroup = (group) => {
49
63
  for (const option of group.options) {
50
64
  next = allSelected ? without(next, option.value) : withValue(next, option.value);
51
65
  }
52
- if (group.value !== undefined) {
53
- next = allSelected ? without(next, group.value) : withValue(next, group.value);
54
- }
55
66
  emit(next);
56
67
  };
57
- const toggleChild = (group, child) => {
68
+ const toggleChild = (child) => {
58
69
  if (single) {
59
70
  pickSingle(child.value);
60
71
  return;
61
72
  }
62
- let next = isSelected(child.value) ? without(value, child.value) : withValue(value, child.value);
63
- if (group.value !== undefined) {
64
- const anyChildSelected = group.options.some((option) => next.some((selected) => eq(selected, option.value)));
65
- next = anyChildSelected ? withValue(next, group.value) : without(next, group.value);
66
- }
67
- emit(next);
73
+ emit(isSelected(child.value) ? without(value, child.value) : withValue(value, child.value));
68
74
  };
69
75
  const clearAll = () => emit([]);
70
76
  </script>
@@ -75,7 +81,7 @@ const clearAll = () => emit([]);
75
81
  {#if header}
76
82
  {@render header()}
77
83
  {/if}
78
- {#each options as item (isSelectGroup(item) ? item.label : item.value)}
84
+ {#each options as item (isSelectGroup(item) ? (item.value ?? item.options[0]?.value ?? item.label) : item.value)}
79
85
  {#if isSelectGroup(item)}
80
86
  <ToolbarOption
81
87
  size={optionSize}
@@ -87,7 +93,7 @@ const clearAll = () => emit([]);
87
93
  size={optionSize}
88
94
  indent
89
95
  selectable={{ selected: isSelected(child.value), display: optionDisplay }}
90
- on={{ click: () => toggleChild(item, child) }}>{child.label}</ToolbarOption>
96
+ on={{ click: () => toggleChild(child) }}>{child.label}</ToolbarOption>
91
97
  {/each}
92
98
  {:else}
93
99
  <ToolbarOption size={optionSize} selectable={{ selected: isSelected(item.value), display: optionDisplay }} on={{ click: () => toggleOption(item) }}
@@ -119,8 +125,11 @@ const clearAll = () => emit([]);
119
125
  A compact toolbar filter dropdown: a secondary-button trigger whose label summarises the selection
120
126
  (`Status` → `Status: Draft` → `Status: 2 selected`), and a `ToolbarOption` checklist panel with a
121
127
  `Clear all` footer. Multi-select by default; `single` restricts to one (checkmark glyph). `options` is a
122
- flat `SelectOption[]` or one level of groups (`SelectOptionGroup`) for a tree with a tri-state parent
123
- row and `children-include-parent` cascade.
128
+ flat `SelectOption[]` or one level of groups (`SelectOptionGroup`). Group behaviour depends on whether
129
+ the group has its own `value`: a label-group (no `value`) is a tri-state select-all header over its
130
+ children; a group WITH a `value` is an independent option — header and children toggle only themselves,
131
+ with no cascade or shared highlight. In `single` mode a value-group header selects its own value (re-click
132
+ deselects), same as any option.
124
133
  `header` injects content above the list (preset rows, a `ToolbarSectionTitle`, …).
125
134
 
126
135
  ### CSS Custom Properties
@@ -54,8 +54,11 @@ interface $$IsomorphicComponent {
54
54
  * A compact toolbar filter dropdown: a secondary-button trigger whose label summarises the selection
55
55
  * (`Status` → `Status: Draft` → `Status: 2 selected`), and a `ToolbarOption` checklist panel with a
56
56
  * `Clear all` footer. Multi-select by default; `single` restricts to one (checkmark glyph). `options` is a
57
- * flat `SelectOption[]` or one level of groups (`SelectOptionGroup`) for a tree with a tri-state parent
58
- * row and `children-include-parent` cascade.
57
+ * flat `SelectOption[]` or one level of groups (`SelectOptionGroup`). Group behaviour depends on whether
58
+ * the group has its own `value`: a label-group (no `value`) is a tri-state select-all header over its
59
+ * children; a group WITH a `value` is an independent option — header and children toggle only themselves,
60
+ * with no cascade or shared highlight. In `single` mode a value-group header selects its own value (re-click
61
+ * deselects), same as any option.
59
62
  * `header` injects content above the list (preset rows, a `ToolbarSectionTitle`, …).
60
63
  *
61
64
  * ### CSS Custom Properties
@@ -1,8 +1,10 @@
1
1
  <script lang="ts" generics="S">import { Button } from '../button';
2
+ import { IconText } from '../icon-text';
2
3
  import ToolbarFilterSelect from './cmp.toolbar-filter-select.svelte';
3
4
  import ToolbarIconButton from './cmp.toolbar-icon-button.svelte';
4
5
  import ToolbarSectionTitle from './cmp.toolbar-section-title.svelte';
5
6
  import { ToolbarSortSelectLocalization } from './toolbar-sort-select-localization';
7
+ import IconChevronDown from '@fluentui/svg-icons/icons/chevron_down_20_regular.svg?raw';
6
8
  import IconFilter from '@fluentui/svg-icons/icons/filter_20_regular.svg?raw';
7
9
  const { options, value, label, size = 'sm', trigger = 'icon', compare, on } = $props();
8
10
  const localization = new ToolbarSortSelectLocalization();
@@ -21,7 +23,9 @@ const onChange = (next) => {
21
23
  {/snippet}
22
24
 
23
25
  {#snippet buttonTrigger()}
24
- <Button type="presentational" variant="secondary" size={size} icon={IconFilter}>{localization.sorting}</Button>
26
+ <Button type="presentational" variant="secondary" size={size}>
27
+ <IconText icon={IconFilter} secondaryIcon={IconChevronDown} size={size} iconScale="large" trimText>{localization.sorting}</IconText>
28
+ </Button>
25
29
  {/snippet}
26
30
 
27
31
  <ToolbarFilterSelect
@@ -8,7 +8,7 @@ const currentLabel = $derived(currentStage?.label ?? '');
8
8
  const tooltipLabel = $derived(!showLabel ? currentLabel : '');
9
9
  const segmentColor = (s) => {
10
10
  const value = s.color ?? 'gray';
11
- return PROGRESS_BAR_TONES.includes(value) ? `var(--sc-kit--tone--${value}--fg)` : value;
11
+ return PROGRESS_BAR_TONES.includes(value) ? `var(--sc-kit--tone--${value}--muted)` : value;
12
12
  };
13
13
  </script>
14
14
 
@@ -45,7 +45,8 @@ const segmentColor = (s) => {
45
45
  Multi-stage workflow progress indicator. Pass `stages` as an array of `{ color, label? }`
46
46
  descriptors — the bar renders one segment per stage; segments `0..stage` (inclusive) are filled,
47
47
  the rest use the empty-segment color. Each stage's `color` is either a named tone from the shared
48
- palette (same schemes as `Badge`, e.g. `'green'`) or a raw CSS color / `var(--sc-kit--*)`. Useful
48
+ palette (resolved to its muted shade, e.g. `'green'` → `--sc-kit--tone--green--muted`) or a raw CSS
49
+ color / `var(--sc-kit--*)`. Useful
49
50
  for content-lifecycle indicators (Ideas → Draft → Review → Scheduled → Published) in cards, table
50
51
  cells, or post pages. `variant="pips"` renders compact fixed-size ticks (left-aligned) instead of
51
52
  full-width segments — for dense table cells. `showLabel={false}` drops the inline current-stage
@@ -16,7 +16,8 @@ type Props = {
16
16
  * Multi-stage workflow progress indicator. Pass `stages` as an array of `{ color, label? }`
17
17
  * descriptors — the bar renders one segment per stage; segments `0..stage` (inclusive) are filled,
18
18
  * the rest use the empty-segment color. Each stage's `color` is either a named tone from the shared
19
- * palette (same schemes as `Badge`, e.g. `'green'`) or a raw CSS color / `var(--sc-kit--*)`. Useful
19
+ * palette (resolved to its muted shade, e.g. `'green'` → `--sc-kit--tone--green--muted`) or a raw CSS
20
+ * color / `var(--sc-kit--*)`. Useful
20
21
  * for content-lifecycle indicators (Ideas → Draft → Review → Scheduled → Published) in cards, table
21
22
  * cells, or post pages. `variant="pips"` renders compact fixed-size ticks (left-aligned) instead of
22
23
  * full-width segments — for dense table cells. `showLabel={false}` drops the inline current-stage
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/kit",
3
- "version": "0.25.0",
3
+ "version": "0.25.2",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",