@streamscloud/kit 0.22.0 → 0.24.0

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.
Files changed (37) hide show
  1. package/dist/styles/_semantic.scss +16 -0
  2. package/dist/ui/badge/cmp.badge.svelte +30 -19
  3. package/dist/ui/badge/cmp.badge.svelte.d.ts +5 -3
  4. package/dist/ui/badge/index.d.ts +1 -1
  5. package/dist/ui/badge/types.d.ts +2 -1
  6. package/dist/ui/fit-box/cmp.fit-box.svelte +46 -0
  7. package/dist/ui/fit-box/cmp.fit-box.svelte.d.ts +42 -0
  8. package/dist/ui/fit-box/contain-fit.d.ts +8 -0
  9. package/dist/ui/fit-box/contain-fit.js +47 -0
  10. package/dist/ui/fit-box/index.d.ts +2 -0
  11. package/dist/ui/fit-box/index.js +1 -0
  12. package/dist/ui/fit-box/types.d.ts +2 -0
  13. package/dist/ui/fit-box/types.js +1 -0
  14. package/dist/ui/grid-card/fields/cmp.grid-card-progress-field.svelte +5 -60
  15. package/dist/ui/grid-card/fields/cmp.grid-card-progress-field.svelte.d.ts +5 -17
  16. package/dist/ui/page-toolbar/cmp.toolbar-icon-button.svelte +9 -8
  17. package/dist/ui/page-toolbar/cmp.toolbar-icon-button.svelte.d.ts +6 -5
  18. package/dist/ui/page-toolbar/cmp.toolbar-sort-select.svelte +17 -7
  19. package/dist/ui/page-toolbar/cmp.toolbar-sort-select.svelte.d.ts +5 -4
  20. package/dist/ui/page-toolbar/cmp.toolbar-value-stepper.svelte +8 -8
  21. package/dist/ui/page-toolbar/cmp.toolbar-value-stepper.svelte.d.ts +4 -4
  22. package/dist/ui/page-toolbar/cmp.toolbar-view-controls.svelte +11 -6
  23. package/dist/ui/page-toolbar/cmp.toolbar-view-controls.svelte.d.ts +7 -4
  24. package/dist/ui/page-toolbar/toolbar-view-controls-localization.d.ts +1 -0
  25. package/dist/ui/page-toolbar/toolbar-view-controls-localization.js +5 -1
  26. package/dist/ui/progress-bar/cmp.progress-bar.svelte +92 -0
  27. package/dist/ui/progress-bar/cmp.progress-bar.svelte.d.ts +32 -0
  28. package/dist/ui/progress-bar/index.d.ts +2 -0
  29. package/dist/ui/progress-bar/index.js +1 -0
  30. package/dist/ui/progress-bar/progress-bar-localization.d.ts +3 -0
  31. package/dist/ui/progress-bar/progress-bar-localization.js +9 -0
  32. package/dist/ui/progress-bar/types.d.ts +7 -0
  33. package/dist/ui/progress-bar/types.js +1 -0
  34. package/dist/ui/table/table-cells/table-badge-cell.svelte +1 -1
  35. package/dist/ui/table/table-cells/table-image-cell.svelte +19 -2
  36. package/dist/ui/table/types.d.ts +4 -2
  37. package/package.json +9 -1
@@ -61,6 +61,22 @@
61
61
  --sc-kit--color--info--hover: light-dark(#{$color-blue-700}, #{$color-blue-300});
62
62
  --sc-kit--color--info--soft: light-dark(#{$color-blue-50}, #{$color-blue-900});
63
63
 
64
+ // ============================================================
65
+ // COLOR — TONES (categorical palette; fg + soft bg, component-neutral)
66
+ // ============================================================
67
+ --sc-kit--tone--gray--fg: light-dark(#{$color-neutral-500}, #{$color-neutral-400});
68
+ --sc-kit--tone--gray--bg: light-dark(#{$color-neutral-50}, #{$color-dark-600});
69
+ --sc-kit--tone--blue--fg: light-dark(#{$color-blue-500}, #{$color-blue-400});
70
+ --sc-kit--tone--blue--bg: light-dark(#{$color-blue-50}, #{$color-blue-900});
71
+ --sc-kit--tone--green--fg: light-dark(#{$color-green-500}, #{$color-green-400});
72
+ --sc-kit--tone--green--bg: light-dark(#{$color-green-50}, #{$color-green-900});
73
+ --sc-kit--tone--red--fg: light-dark(#{$color-red-500}, #{$color-red-400});
74
+ --sc-kit--tone--red--bg: light-dark(#{$color-red-50}, #{$color-red-900});
75
+ --sc-kit--tone--orange--fg: light-dark(#{$color-orange-500}, #{$color-orange-400});
76
+ --sc-kit--tone--orange--bg: light-dark(#{$color-orange-100}, #{$color-orange-900});
77
+ --sc-kit--tone--teal--fg: light-dark(#3d7068, #5f938a);
78
+ --sc-kit--tone--teal--bg: light-dark(#eef4f3, #16302b);
79
+
64
80
  // ============================================================
65
81
  // COLOR — OVERLAYS
66
82
  // ============================================================
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">import { IconSlot } from '../icon';
2
- const { variant = 'neutral', size = 'md', style = 'soft', fullWidth = false, icon, children } = $props();
2
+ const { variant = 'neutral', size = 'md', style = 'soft', shape = 'pill', fullWidth = false, icon, children } = $props();
3
3
  </script>
4
4
 
5
- <span class="badge badge--{size} badge--{variant}-{style}" class:badge--full={fullWidth}>
5
+ <span class="badge badge--{size} badge--{variant}-{style}" class:badge--square={shape === 'square'} class:badge--full={fullWidth}>
6
6
  {#if icon}
7
7
  <span class="badge__icon" aria-hidden="true"><IconSlot icon={icon} /></span>
8
8
  {/if}
@@ -11,7 +11,7 @@ const { variant = 'neutral', size = 'md', style = 'soft', fullWidth = false, ico
11
11
 
12
12
  <!--
13
13
  @component
14
- Pill-shaped status indicator. Use `solid` for stronger emphasis, `soft` (default) for a calm tinted fill that pairs with body content. Optional leading `icon` renders in its own slot; the text content keeps its own ellipsis when it overflows.
14
+ Status indicator, pill-shaped by default (`shape="square"` for a 4px-radius chip). Use `solid` for stronger emphasis, `soft` (default) for a calm tinted fill that pairs with body content. Optional leading `icon` renders in its own slot; the text content keeps its own ellipsis when it overflows.
15
15
 
16
16
  ### CSS Custom Properties
17
17
  | Property | Description | Default |
@@ -20,7 +20,7 @@ Pill-shaped status indicator. Use `solid` for stronger emphasis, `soft` (default
20
20
  | `--sc-kit--badge--background` | Background color | per variant + style |
21
21
  | `--sc-kit--badge--height` | Badge height | per size (20 / 24px) |
22
22
  | `--sc-kit--badge--padding-inline` | Horizontal padding | `0.875rem 0.75rem` |
23
- | `--sc-kit--badge--border-radius` | Corner radius | `var(--sc-kit--radius--pill)` |
23
+ | `--sc-kit--badge--border-radius` | Corner radius | `shape="square"` → `var(--sc-kit--radius--sm)`, else `var(--sc-kit--radius--pill)` |
24
24
  | `--sc-kit--badge--font-size` | Content font size | per size |
25
25
  | `--sc-kit--badge--icon-size` | Leading icon size | per size (14 / 16px) |
26
26
  | `--sc-kit--badge--font-weight` | Font weight | `var(--sc-kit--font-weight--medium)` |
@@ -57,6 +57,9 @@ Pill-shaped status indicator. Use `solid` for stronger emphasis, `soft` (default
57
57
  text-overflow: ellipsis;
58
58
  white-space: nowrap;
59
59
  }
60
+ .badge--square {
61
+ --_badge--border-radius: var(--sc-kit--badge--border-radius, var(--sc-kit--radius--sm));
62
+ }
60
63
  .badge--full {
61
64
  display: flex;
62
65
  width: 100%;
@@ -73,42 +76,50 @@ Pill-shaped status indicator. Use `solid` for stronger emphasis, `soft` (default
73
76
  --_badge--icon-size: var(--sc-kit--badge--icon-size, 1rem);
74
77
  }
75
78
  .badge--neutral-soft {
76
- --_badge--color: var(--sc-kit--badge--color, var(--sc-kit--color--text--secondary));
77
- --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--color--bg--active));
79
+ --_badge--color: var(--sc-kit--badge--color, var(--sc-kit--tone--gray--fg));
80
+ --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--tone--gray--bg));
78
81
  }
79
82
  .badge--accent-soft {
80
- --_badge--color: var(--sc-kit--badge--color, var(--sc-kit--color--accent));
81
- --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--color--accent--soft));
83
+ --_badge--color: var(--sc-kit--badge--color, var(--sc-kit--tone--blue--fg));
84
+ --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--tone--blue--bg));
85
+ }
86
+ .badge--auxiliary-soft {
87
+ --_badge--color: var(--sc-kit--badge--color, var(--sc-kit--tone--teal--fg));
88
+ --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--tone--teal--bg));
82
89
  }
83
90
  .badge--success-soft {
84
- --_badge--color: var(--sc-kit--badge--color, var(--sc-kit--color--success));
85
- --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--color--success--soft));
91
+ --_badge--color: var(--sc-kit--badge--color, var(--sc-kit--tone--green--fg));
92
+ --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--tone--green--bg));
86
93
  }
87
94
  .badge--warning-soft {
88
- --_badge--color: var(--sc-kit--badge--color, var(--sc-kit--color--warning));
89
- --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--color--warning--soft));
95
+ --_badge--color: var(--sc-kit--badge--color, var(--sc-kit--tone--orange--fg));
96
+ --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--tone--orange--bg));
90
97
  }
91
98
  .badge--danger-soft {
92
- --_badge--color: var(--sc-kit--badge--color, var(--sc-kit--color--danger));
93
- --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--color--danger--soft));
99
+ --_badge--color: var(--sc-kit--badge--color, var(--sc-kit--tone--red--fg));
100
+ --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--tone--red--bg));
94
101
  }
95
102
  .badge--neutral-solid {
96
103
  --_badge--color: var(--sc-kit--badge--color, var(--sc-kit--color--bg--panel));
97
- --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--color--text--secondary));
104
+ --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--tone--gray--fg));
98
105
  }
99
106
  .badge--accent-solid {
100
107
  --_badge--color: var(--sc-kit--badge--color, var(--sc-kit--color--text--on-accent));
101
- --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--color--accent));
108
+ --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--tone--blue--fg));
109
+ }
110
+ .badge--auxiliary-solid {
111
+ --_badge--color: var(--sc-kit--badge--color, var(--sc-kit--color--text--on-accent));
112
+ --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--tone--teal--fg));
102
113
  }
103
114
  .badge--success-solid {
104
115
  --_badge--color: var(--sc-kit--badge--color, var(--sc-kit--color--text--on-accent));
105
- --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--color--success));
116
+ --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--tone--green--fg));
106
117
  }
107
118
  .badge--warning-solid {
108
119
  --_badge--color: var(--sc-kit--badge--color, var(--sc-kit--color--text--primary));
109
- --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--color--warning));
120
+ --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--tone--orange--fg));
110
121
  }
111
122
  .badge--danger-solid {
112
123
  --_badge--color: var(--sc-kit--badge--color, var(--sc-kit--color--text--on-accent));
113
- --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--color--danger));
124
+ --_badge--background: var(--sc-kit--badge--background, var(--sc-kit--tone--red--fg));
114
125
  }</style>
@@ -1,5 +1,5 @@
1
1
  import { type IconProp } from '../icon';
2
- import type { BadgeVariant } from './types';
2
+ import type { BadgeShape, BadgeVariant } from './types';
3
3
  import type { Snippet } from 'svelte';
4
4
  type Props = {
5
5
  /** Color variant @default 'neutral' */
@@ -8,6 +8,8 @@ type Props = {
8
8
  size?: 'sm' | 'md';
9
9
  /** Filled chip vs soft-tinted fill @default 'soft' */
10
10
  style?: 'soft' | 'solid';
11
+ /** Corner style — pill (fully rounded) vs square (4px radius). @default 'pill' */
12
+ shape?: BadgeShape;
11
13
  /** Stretch to fill the parent's inline axis, centering the content. @default false */
12
14
  fullWidth?: boolean;
13
15
  /** Optional leading icon. */
@@ -15,7 +17,7 @@ type Props = {
15
17
  children: Snippet;
16
18
  };
17
19
  /**
18
- * Pill-shaped status indicator. Use `solid` for stronger emphasis, `soft` (default) for a calm tinted fill that pairs with body content. Optional leading `icon` renders in its own slot; the text content keeps its own ellipsis when it overflows.
20
+ * Status indicator, pill-shaped by default (`shape="square"` for a 4px-radius chip). Use `solid` for stronger emphasis, `soft` (default) for a calm tinted fill that pairs with body content. Optional leading `icon` renders in its own slot; the text content keeps its own ellipsis when it overflows.
19
21
  *
20
22
  * ### CSS Custom Properties
21
23
  * | Property | Description | Default |
@@ -24,7 +26,7 @@ type Props = {
24
26
  * | `--sc-kit--badge--background` | Background color | per variant + style |
25
27
  * | `--sc-kit--badge--height` | Badge height | per size (20 / 24px) |
26
28
  * | `--sc-kit--badge--padding-inline` | Horizontal padding | `0.875rem 0.75rem` |
27
- * | `--sc-kit--badge--border-radius` | Corner radius | `var(--sc-kit--radius--pill)` |
29
+ * | `--sc-kit--badge--border-radius` | Corner radius | `shape="square"` → `var(--sc-kit--radius--sm)`, else `var(--sc-kit--radius--pill)` |
28
30
  * | `--sc-kit--badge--font-size` | Content font size | per size |
29
31
  * | `--sc-kit--badge--icon-size` | Leading icon size | per size (14 / 16px) |
30
32
  * | `--sc-kit--badge--font-weight` | Font weight | `var(--sc-kit--font-weight--medium)` |
@@ -1,2 +1,2 @@
1
1
  export { default as Badge } from './cmp.badge.svelte';
2
- export type { BadgeVariant } from './types';
2
+ export type { BadgeShape, BadgeVariant } from './types';
@@ -1 +1,2 @@
1
- export type BadgeVariant = 'neutral' | 'accent' | 'success' | 'warning' | 'danger';
1
+ export type BadgeVariant = 'neutral' | 'accent' | 'auxiliary' | 'success' | 'warning' | 'danger';
2
+ export type BadgeShape = 'pill' | 'square';
@@ -0,0 +1,46 @@
1
+ <script lang="ts">import { containFit } from './contain-fit';
2
+ const { basis = 'box', allowUpscale = false, alignInline = 'center', alignBlock = 'center', children } = $props();
3
+ </script>
4
+
5
+ <div class="fit-box">
6
+ <div class="fit-box__scaler" class:fit-box__scaler--natural={basis === 'content'} use:containFit={{ allowUpscale, alignInline, alignBlock }}>
7
+ {@render children()}
8
+ </div>
9
+ </div>
10
+
11
+ <!--
12
+ @component
13
+ Scales its content as one unit to fit the box like `object-fit: contain` — proportions preserved, bounded by both the box's width and
14
+ height, no crop, no scroll. A generic fit primitive for **cards and media content** (a card, an image/video, a fixed-size widget), not a
15
+ page-layout tool.
16
+
17
+ How it works: the box is `FitBox`'s **parent**, which must have a **definite size** (fixed / `%` / grid-track / aspect-ratio+width — a
18
+ content-driven `height: auto` parent collapses and nothing shows). The content is measured, then transform-scaled by
19
+ `min(boxW / contentW, boxH / contentH)` and positioned; a `ResizeObserver` re-fits on any box or content change.
20
+
21
+ Pick the mode with `basis`:
22
+ - `basis="box"` (default) — fluid, width-driven content (cards, text, single-aspect media). Fills the box width, scales down to fit the
23
+ height. Never enlarges.
24
+ - `basis="content"` — intrinsic content (`<img>` / `<video>`, fixed-size widgets). Keeps the content's natural size and fits both axes;
25
+ set `allowUpscale` to also enlarge it to fill.
26
+
27
+ `alignInline` / `alignBlock` place the content along an axis when fitting leaves free space there. Requires client-side JS (scales via
28
+ `transform`, so `position: fixed` descendants are relative to the box); content with a hard fixed width wider than the box is clipped
29
+ rather than contained.
30
+ -->
31
+
32
+ <style>.fit-box {
33
+ position: relative;
34
+ inline-size: 100%;
35
+ block-size: 100%;
36
+ overflow: hidden;
37
+ }
38
+ .fit-box__scaler {
39
+ position: absolute;
40
+ inline-size: 100%;
41
+ max-inline-size: 100%;
42
+ visibility: hidden;
43
+ }
44
+ .fit-box__scaler--natural {
45
+ inline-size: auto;
46
+ }</style>
@@ -0,0 +1,42 @@
1
+ import type { FitAlign, FitBasis } from './types';
2
+ import type { Snippet } from 'svelte';
3
+ type Props = {
4
+ /**
5
+ * What the content's width is based on before it is scaled to fit:
6
+ * - `'box'` — content is laid out at the **box width** first (use for fluid, width-driven content: a card, a text block, single-aspect
7
+ * media). Height follows from that width; the content is then scaled **down** only if too tall. Never enlarges.
8
+ * - `'content'` — content keeps its **own natural size** (use for intrinsic content: `<img>` / `<video>`, a fixed-size widget). It is
9
+ * scaled to fit **both** axes and may be enlarged when `allowUpscale` is set.
10
+ * @default 'box'
11
+ */
12
+ basis?: FitBasis;
13
+ /** Allow scaling content up past its natural size to fill the box. Takes effect only with `basis="content"` (`'box'` never enlarges). @default false */
14
+ allowUpscale?: boolean;
15
+ /** Inline-axis (horizontal) placement of the fitted content, visible only when free width remains after fitting. @default 'center' */
16
+ alignInline?: FitAlign;
17
+ /** Block-axis (vertical) placement of the fitted content, visible only when free height remains after fitting. @default 'center' */
18
+ alignBlock?: FitAlign;
19
+ children: Snippet;
20
+ };
21
+ /**
22
+ * Scales its content as one unit to fit the box like `object-fit: contain` — proportions preserved, bounded by both the box's width and
23
+ * height, no crop, no scroll. A generic fit primitive for **cards and media content** (a card, an image/video, a fixed-size widget), not a
24
+ * page-layout tool.
25
+ *
26
+ * How it works: the box is `FitBox`'s **parent**, which must have a **definite size** (fixed / `%` / grid-track / aspect-ratio+width — a
27
+ * content-driven `height: auto` parent collapses and nothing shows). The content is measured, then transform-scaled by
28
+ * `min(boxW / contentW, boxH / contentH)` and positioned; a `ResizeObserver` re-fits on any box or content change.
29
+ *
30
+ * Pick the mode with `basis`:
31
+ * - `basis="box"` (default) — fluid, width-driven content (cards, text, single-aspect media). Fills the box width, scales down to fit the
32
+ * height. Never enlarges.
33
+ * - `basis="content"` — intrinsic content (`<img>` / `<video>`, fixed-size widgets). Keeps the content's natural size and fits both axes;
34
+ * set `allowUpscale` to also enlarge it to fill.
35
+ *
36
+ * `alignInline` / `alignBlock` place the content along an axis when fitting leaves free space there. Requires client-side JS (scales via
37
+ * `transform`, so `position: fixed` descendants are relative to the box); content with a hard fixed width wider than the box is clipped
38
+ * rather than contained.
39
+ */
40
+ declare const Cmp: import("svelte").Component<Props, {}, "">;
41
+ type Cmp = ReturnType<typeof Cmp>;
42
+ export default Cmp;
@@ -0,0 +1,8 @@
1
+ import type { FitAlign } from './types';
2
+ import type { Action } from 'svelte/action';
3
+ export type ContainFitParams = {
4
+ allowUpscale?: boolean;
5
+ alignInline?: FitAlign;
6
+ alignBlock?: FitAlign;
7
+ };
8
+ export declare const containFit: Action<HTMLElement, ContainFitParams | undefined>;
@@ -0,0 +1,47 @@
1
+ const offset = (align, free) => (align === 'start' ? 0 : align === 'end' ? free : free / 2);
2
+ export const containFit = (node, params = {}) => {
3
+ const container = node.parentElement;
4
+ let allowUpscale = params.allowUpscale ?? false;
5
+ let alignInline = params.alignInline ?? 'center';
6
+ let alignBlock = params.alignBlock ?? 'center';
7
+ const apply = () => {
8
+ if (!container) {
9
+ return;
10
+ }
11
+ const boxWidth = container.clientWidth;
12
+ const boxHeight = container.clientHeight;
13
+ const naturalWidth = node.offsetWidth;
14
+ const naturalHeight = node.offsetHeight;
15
+ if (!boxWidth || !boxHeight || !naturalWidth || !naturalHeight) {
16
+ return;
17
+ }
18
+ let scale = Math.min(boxWidth / naturalWidth, boxHeight / naturalHeight);
19
+ if (!allowUpscale) {
20
+ scale = Math.min(scale, 1);
21
+ }
22
+ const left = offset(alignInline, boxWidth - naturalWidth * scale);
23
+ const top = offset(alignBlock, boxHeight - naturalHeight * scale);
24
+ node.style.transformOrigin = '0 0';
25
+ node.style.transform = `scale(${scale})`;
26
+ node.style.left = `${left}px`;
27
+ node.style.top = `${top}px`;
28
+ node.style.visibility = 'visible';
29
+ };
30
+ const observer = new ResizeObserver(apply);
31
+ if (container) {
32
+ observer.observe(container);
33
+ }
34
+ observer.observe(node);
35
+ apply();
36
+ return {
37
+ update(next = {}) {
38
+ allowUpscale = next.allowUpscale ?? false;
39
+ alignInline = next.alignInline ?? 'center';
40
+ alignBlock = next.alignBlock ?? 'center';
41
+ apply();
42
+ },
43
+ destroy() {
44
+ observer.disconnect();
45
+ }
46
+ };
47
+ };
@@ -0,0 +1,2 @@
1
+ export { default as FitBox } from './cmp.fit-box.svelte';
2
+ export type { FitAlign, FitBasis } from './types';
@@ -0,0 +1 @@
1
+ export { default as FitBox } from './cmp.fit-box.svelte';
@@ -0,0 +1,2 @@
1
+ export type FitAlign = 'start' | 'center' | 'end';
2
+ export type FitBasis = 'box' | 'content';
@@ -0,0 +1 @@
1
+ export {};
@@ -1,69 +1,14 @@
1
- <script lang="ts">import GridCardField from './cmp.grid-card-field.svelte';
1
+ <script lang="ts">import { ProgressBar } from '../../progress-bar';
2
+ import GridCardField from './cmp.grid-card-field.svelte';
2
3
  const { label, stage, stages } = $props();
3
- const currentStage = $derived(stages[Math.max(0, Math.min(stage, stages.length - 1))]);
4
- const currentLabel = $derived(currentStage?.label ?? '');
5
4
  </script>
6
5
 
7
6
  <GridCardField label={label}>
8
- <div class="grid-card-progress-field">
9
- <div
10
- class="grid-card-progress-field__bar"
11
- role="progressbar"
12
- aria-valuemin={1}
13
- aria-valuemax={stages.length}
14
- aria-valuenow={stage + 1}
15
- aria-label={currentLabel}>
16
- {#each stages as s, i (i)}
17
- <span class="grid-card-progress-field__segment" class:grid-card-progress-field__segment--filled={i <= stage} style:--_seg-color={s.color}></span>
18
- {/each}
19
- </div>
20
- {#if currentLabel}
21
- <span class="grid-card-progress-field__label">{currentLabel}</span>
22
- {/if}
23
- </div>
7
+ <ProgressBar stage={stage} stages={stages} />
24
8
  </GridCardField>
25
9
 
26
10
  <!--
27
11
  @component
28
- Multi-stage workflow progress indicator. Pass `stages` as an array of `{ color, label? }`
29
- descriptors the bar renders one segment per stage; segments `0..stage` (inclusive) are
30
- filled with the per-stage color, the rest are muted. Consumer supplies the per-stage color
31
- (string — any valid CSS color or `var(--sc-kit--color--*)` reference). Useful for content
32
- lifecycle indicators (Ideas → Draft → Review → Scheduled → Published).
33
-
34
- ### CSS Custom Properties
35
- | Property | Description | Default |
36
- |---|---|---|
37
- | `--sc-kit--grid-card-progress-field--segment--height` | Bar segment height | `4px` |
38
- | `--sc-kit--grid-card-progress-field--segment--gap` | Gap between segments | `2px` |
39
- | `--sc-kit--grid-card-progress-field--segment--background--empty` | Empty segment color | `var(--sc-kit--color--bg--active)` |
12
+ Grid-card field pairing a field label with a multi-stage `ProgressBar`. See `ProgressBar`
13
+ (`@streamscloud/kit/ui/progress-bar`) for the bar's own CSS Custom Properties.
40
14
  -->
41
-
42
- <style>.grid-card-progress-field {
43
- --_gcp--seg-height: var(--sc-kit--grid-card-progress-field--segment--height, 0.25rem);
44
- --_gcp--seg-gap: var(--sc-kit--grid-card-progress-field--segment--gap, 0.125rem);
45
- --_gcp--seg-empty: var(--sc-kit--grid-card-progress-field--segment--background--empty, var(--sc-kit--color--bg--active));
46
- display: flex;
47
- flex-direction: column;
48
- gap: 0.125rem;
49
- min-width: 0;
50
- }
51
- .grid-card-progress-field__bar {
52
- display: flex;
53
- gap: var(--_gcp--seg-gap);
54
- width: 100%;
55
- }
56
- .grid-card-progress-field__segment {
57
- flex: 1;
58
- height: var(--_gcp--seg-height);
59
- background: var(--_gcp--seg-empty);
60
- border-radius: 0.0625rem;
61
- }
62
- .grid-card-progress-field__segment--filled {
63
- background: var(--_seg-color);
64
- }
65
- .grid-card-progress-field__label {
66
- font-size: 0.625rem;
67
- color: var(--sc-kit--color--text--secondary);
68
- line-height: var(--sc-kit--leading--tight);
69
- }</style>
@@ -1,29 +1,17 @@
1
+ import { type ProgressBarStage } from '../../progress-bar';
1
2
  type Props = {
2
3
  label: string;
3
4
  /** Current stage index, 0-based. Stages 0..stage (inclusive) are filled. */
4
5
  stage: number;
5
6
  /**
6
7
  * Stage descriptors — at minimum `{ color }` per stage (consumer provides the CSS color).
7
- * Length determines the total segment count. Pass arbitrary number of stages.
8
+ * Length determines the total segment count.
8
9
  */
9
- stages: {
10
- color: string;
11
- label?: string;
12
- }[];
10
+ stages: ProgressBarStage[];
13
11
  };
14
12
  /**
15
- * Multi-stage workflow progress indicator. Pass `stages` as an array of `{ color, label? }`
16
- * descriptors the bar renders one segment per stage; segments `0..stage` (inclusive) are
17
- * filled with the per-stage color, the rest are muted. Consumer supplies the per-stage color
18
- * (string — any valid CSS color or `var(--sc-kit--color--*)` reference). Useful for content
19
- * lifecycle indicators (Ideas → Draft → Review → Scheduled → Published).
20
- *
21
- * ### CSS Custom Properties
22
- * | Property | Description | Default |
23
- * |---|---|---|
24
- * | `--sc-kit--grid-card-progress-field--segment--height` | Bar segment height | `4px` |
25
- * | `--sc-kit--grid-card-progress-field--segment--gap` | Gap between segments | `2px` |
26
- * | `--sc-kit--grid-card-progress-field--segment--background--empty` | Empty segment color | `var(--sc-kit--color--bg--active)` |
13
+ * Grid-card field pairing a field label with a multi-stage `ProgressBar`. See `ProgressBar`
14
+ * (`@streamscloud/kit/ui/progress-bar`) for the bar's own CSS Custom Properties.
27
15
  */
28
16
  declare const Cmp: import("svelte").Component<Props, {}, "">;
29
17
  type Cmp = ReturnType<typeof Cmp>;
@@ -4,7 +4,7 @@ const { icon, 'aria-label': ariaLabel, size = 'sm', active = false, disabled = f
4
4
 
5
5
  {#if presentational}
6
6
  <span class="toolbar-icon-button toolbar-icon-button--{size}" class:toolbar-icon-button--active={active}>
7
- <Icon src={icon} size="md" />
7
+ <Icon src={icon} size={size === 'sm' ? 'control-md' : 'md'} />
8
8
  </span>
9
9
  {:else}
10
10
  <button
@@ -15,28 +15,29 @@ const { icon, 'aria-label': ariaLabel, size = 'sm', active = false, disabled = f
15
15
  aria-label={ariaLabel}
16
16
  aria-pressed={active}
17
17
  onclick={() => on?.click?.()}>
18
- <Icon src={icon} size="md" />
18
+ <Icon src={icon} size={size === 'sm' ? 'control-md' : 'md'} />
19
19
  </button>
20
20
  {/if}
21
21
 
22
22
  <!--
23
23
  @component
24
- A flat, square toolbar icon button (28px at `sm`, 32px at `md`, with a 20px glyph). Background appears on
25
- hover only; `active` tints the glyph with the accent color (no background). Pass `presentational` to render
26
- a non-interactive `<span>` for use inside a wrapper that owns the click + semantics (e.g. a Popover trigger).
27
- Shared by `ToolbarViewControls` and the `ToolbarFilterSelect` icon trigger so they stay visually identical.
24
+ A flat, square toolbar icon button (28px at `sm`, 32px at `md`, with a 16px glyph at `sm` / 20px at `md`).
25
+ Background appears on hover only; `active` tints the glyph with the primary text color (no background). Pass
26
+ `presentational` to render a non-interactive `<span>` for use inside a wrapper that owns the click +
27
+ semantics (e.g. a Popover trigger). Shared by `ToolbarViewControls` and the `ToolbarSortSelect` icon trigger
28
+ so they stay visually identical.
28
29
 
29
30
  ### CSS Custom Properties
30
31
  | Property | Description | Default |
31
32
  |---|---|---|
32
33
  | `--sc-kit--toolbar-icon-button--color` | Idle glyph color | `var(--sc-kit--color--text--muted)` |
33
- | `--sc-kit--toolbar-icon-button--color--active` | Active glyph color | `var(--sc-kit--color--accent)` |
34
+ | `--sc-kit--toolbar-icon-button--color--active` | Active glyph color | `var(--sc-kit--color--text--primary)` |
34
35
  | `--sc-kit--toolbar-icon-button--background--hover` | Hover background | `var(--sc-kit--color--bg--hover)` |
35
36
  -->
36
37
 
37
38
  <style>.toolbar-icon-button {
38
39
  --_tib--color: var(--sc-kit--toolbar-icon-button--color, var(--sc-kit--color--text--muted));
39
- --_tib--color-active: var(--sc-kit--toolbar-icon-button--color--active, var(--sc-kit--color--accent));
40
+ --_tib--color-active: var(--sc-kit--toolbar-icon-button--color--active, var(--sc-kit--color--text--primary));
40
41
  --_tib--background-hover: var(--sc-kit--toolbar-icon-button--background--hover, var(--sc-kit--color--bg--hover));
41
42
  --_tib--size: var(--_tib--size-height);
42
43
  --sc-kit--icon--color: var(--_tib--color);
@@ -15,16 +15,17 @@ type Props = {
15
15
  };
16
16
  };
17
17
  /**
18
- * A flat, square toolbar icon button (28px at `sm`, 32px at `md`, with a 20px glyph). Background appears on
19
- * hover only; `active` tints the glyph with the accent color (no background). Pass `presentational` to render
20
- * a non-interactive `<span>` for use inside a wrapper that owns the click + semantics (e.g. a Popover trigger).
21
- * Shared by `ToolbarViewControls` and the `ToolbarFilterSelect` icon trigger so they stay visually identical.
18
+ * A flat, square toolbar icon button (28px at `sm`, 32px at `md`, with a 16px glyph at `sm` / 20px at `md`).
19
+ * Background appears on hover only; `active` tints the glyph with the primary text color (no background). Pass
20
+ * `presentational` to render a non-interactive `<span>` for use inside a wrapper that owns the click +
21
+ * semantics (e.g. a Popover trigger). Shared by `ToolbarViewControls` and the `ToolbarSortSelect` icon trigger
22
+ * so they stay visually identical.
22
23
  *
23
24
  * ### CSS Custom Properties
24
25
  * | Property | Description | Default |
25
26
  * |---|---|---|
26
27
  * | `--sc-kit--toolbar-icon-button--color` | Idle glyph color | `var(--sc-kit--color--text--muted)` |
27
- * | `--sc-kit--toolbar-icon-button--color--active` | Active glyph color | `var(--sc-kit--color--accent)` |
28
+ * | `--sc-kit--toolbar-icon-button--color--active` | Active glyph color | `var(--sc-kit--color--text--primary)` |
28
29
  * | `--sc-kit--toolbar-icon-button--background--hover` | Hover background | `var(--sc-kit--color--bg--hover)` |
29
30
  */
30
31
  declare const Cmp: import("svelte").Component<Props, {}, "">;
@@ -3,7 +3,7 @@ import ToolbarIconButton from './cmp.toolbar-icon-button.svelte';
3
3
  import ToolbarSectionTitle from './cmp.toolbar-section-title.svelte';
4
4
  import { ToolbarSortSelectLocalization } from './toolbar-sort-select-localization';
5
5
  import IconFilter from '@fluentui/svg-icons/icons/filter_20_regular.svg?raw';
6
- const { options, value, label, size = 'sm', icon, compare, on } = $props();
6
+ const { options, value, label, size = 'sm', trigger, compare, on } = $props();
7
7
  const localization = new ToolbarSortSelectLocalization();
8
8
  const orderByLabel = $derived(label ?? localization.orderBy);
9
9
  const selection = $derived([value]);
@@ -14,18 +14,28 @@ const onChange = (next) => {
14
14
  };
15
15
  </script>
16
16
 
17
- <ToolbarFilterSelect label={orderByLabel} single footer={false} size={size} options={options} value={selection} compare={compare} on={{ change: onChange }}>
17
+ {#snippet defaultTrigger()}
18
+ <ToolbarIconButton presentational icon={IconFilter} size={size} />
19
+ {/snippet}
20
+
21
+ <ToolbarFilterSelect
22
+ label={orderByLabel}
23
+ single
24
+ footer={false}
25
+ size={size}
26
+ options={options}
27
+ value={selection}
28
+ compare={compare}
29
+ trigger={trigger ?? defaultTrigger}
30
+ on={{ change: onChange }}>
18
31
  {#snippet header()}
19
32
  <ToolbarSectionTitle>{orderByLabel}</ToolbarSectionTitle>
20
33
  {/snippet}
21
- {#snippet trigger()}
22
- <ToolbarIconButton presentational icon={icon ?? IconFilter} size={size} />
23
- {/snippet}
24
34
  </ToolbarFilterSelect>
25
35
 
26
36
  <!--
27
37
  @component
28
38
  A toolbar sort control: a `ToolbarFilterSelect` in single-select mode behind a flat icon trigger
29
- (`ToolbarIconButton`), with an `Order by` section title above the options. Stand-alone, or composed into
30
- `ToolbarViewControls` via its `prepend` snippet.
39
+ (`ToolbarIconButton` by default, or a custom `trigger` snippet), with an `Order by` section title above the
40
+ options. Stand-alone, or composed into `ToolbarViewControls` via its `prepend` snippet.
31
41
  -->
@@ -1,4 +1,5 @@
1
1
  import type { SelectItem } from '../select';
2
+ import type { Snippet } from 'svelte';
2
3
  declare function $$render<S>(): {
3
4
  props: {
4
5
  options: SelectItem<S>[];
@@ -7,8 +8,8 @@ declare function $$render<S>(): {
7
8
  label?: string;
8
9
  /** Button size preset — sm = 28px, md = 32px. @default 'sm' */
9
10
  size?: "sm" | "md";
10
- /** Trigger glyph (a string SVG source). @default a filter glyph */
11
- icon?: string;
11
+ /** Custom presentational trigger content replaces the default filter-icon button (the popover owns the click and is named by `label`). */
12
+ trigger?: Snippet;
12
13
  /** Equality comparator — required when `S` is an object. */
13
14
  compare?: (a: S, b: S) => boolean;
14
15
  on?: {
@@ -36,8 +37,8 @@ interface $$IsomorphicComponent {
36
37
  }
37
38
  /**
38
39
  * A toolbar sort control: a `ToolbarFilterSelect` in single-select mode behind a flat icon trigger
39
- * (`ToolbarIconButton`), with an `Order by` section title above the options. Stand-alone, or composed into
40
- * `ToolbarViewControls` via its `prepend` snippet.
40
+ * (`ToolbarIconButton` by default, or a custom `trigger` snippet), with an `Order by` section title above the
41
+ * options. Stand-alone, or composed into `ToolbarViewControls` via its `prepend` snippet.
41
42
  */
42
43
  declare const Cmp: $$IsomorphicComponent;
43
44
  type Cmp<S> = InstanceType<typeof Cmp<S>>;
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">import { CONTROL_ICON_SIZE, Icon, scaleUp } from '../icon';
2
2
  import { ToolbarValueStepperLocalization } from './toolbar-value-stepper-localization';
3
- import IconChevronLeft from '@fluentui/svg-icons/icons/chevron_left_16_regular.svg?raw';
4
- import IconChevronRight from '@fluentui/svg-icons/icons/chevron_right_16_regular.svg?raw';
3
+ import IconAdd from '@fluentui/svg-icons/icons/add_16_regular.svg?raw';
4
+ import IconSubtract from '@fluentui/svg-icons/icons/subtract_16_regular.svg?raw';
5
5
  const { value, min = 0, max = 100, step = 1, valueSuffix = '', size = 'sm', on } = $props();
6
6
  const localization = new ToolbarValueStepperLocalization();
7
7
  const iconSize = $derived(scaleUp(CONTROL_ICON_SIZE[size]));
@@ -17,25 +17,25 @@ const increase = () => {
17
17
 
18
18
  <div class="toolbar-value-stepper toolbar-value-stepper--{size}" role="group" aria-label={localization.value}>
19
19
  <button type="button" class="toolbar-value-stepper__btn" onclick={decrease} disabled={atMin} aria-label={localization.decreaseValue}>
20
- <Icon src={IconChevronLeft} size={iconSize} />
20
+ <Icon src={IconSubtract} size={iconSize} />
21
21
  </button>
22
22
  <span class="toolbar-value-stepper__value">{value}{valueSuffix}</span>
23
23
  <button type="button" class="toolbar-value-stepper__btn" onclick={increase} disabled={atMax} aria-label={localization.increaseValue}>
24
- <Icon src={IconChevronRight} size={iconSize} />
24
+ <Icon src={IconAdd} size={iconSize} />
25
25
  </button>
26
26
  </div>
27
27
 
28
28
  <!--
29
29
  @component
30
- A compact `[<] value [>]` stepper for toolbars — increment / decrement a numeric value between `min`
31
- and `max` by `step`, with an optional `valueSuffix` (e.g. ` %`). Bound arrows disable at the limits.
30
+ A compact `[] value [+]` stepper for toolbars — increment / decrement a numeric value between `min`
31
+ and `max` by `step`, with an optional `valueSuffix` (e.g. ` %`). The / + buttons disable at the limits.
32
32
  Shares the field-height preset so it lines up with other toolbar controls.
33
33
 
34
34
  ### CSS Custom Properties
35
35
  | Property | Description | Default |
36
36
  |---|---|---|
37
- | `--sc-kit--toolbar-value-stepper--value--min-inline-size` | Min width of the value readout (keeps arrows from shifting) | `2.25rem` |
38
- | `--sc-kit--toolbar-value-stepper--icon--color` | Arrow icon color | `var(--sc-kit--color--accent)` |
37
+ | `--sc-kit--toolbar-value-stepper--value--min-inline-size` | Min width of the value readout (keeps the buttons from shifting) | `2.25rem` |
38
+ | `--sc-kit--toolbar-value-stepper--icon--color` | Button icon color | `var(--sc-kit--color--accent)` |
39
39
  -->
40
40
 
41
41
  <style>.toolbar-value-stepper {
@@ -15,15 +15,15 @@ type Props = {
15
15
  };
16
16
  };
17
17
  /**
18
- * A compact `[<] value [>]` stepper for toolbars — increment / decrement a numeric value between `min`
19
- * and `max` by `step`, with an optional `valueSuffix` (e.g. ` %`). Bound arrows disable at the limits.
18
+ * A compact `[] value [+]` stepper for toolbars — increment / decrement a numeric value between `min`
19
+ * and `max` by `step`, with an optional `valueSuffix` (e.g. ` %`). The / + buttons disable at the limits.
20
20
  * Shares the field-height preset so it lines up with other toolbar controls.
21
21
  *
22
22
  * ### CSS Custom Properties
23
23
  * | Property | Description | Default |
24
24
  * |---|---|---|
25
- * | `--sc-kit--toolbar-value-stepper--value--min-inline-size` | Min width of the value readout (keeps arrows from shifting) | `2.25rem` |
26
- * | `--sc-kit--toolbar-value-stepper--icon--color` | Arrow icon color | `var(--sc-kit--color--accent)` |
25
+ * | `--sc-kit--toolbar-value-stepper--value--min-inline-size` | Min width of the value readout (keeps the buttons from shifting) | `2.25rem` |
26
+ * | `--sc-kit--toolbar-value-stepper--icon--color` | Button icon color | `var(--sc-kit--color--accent)` |
27
27
  */
28
28
  declare const Cmp: import("svelte").Component<Props, {}, "">;
29
29
  type Cmp = ReturnType<typeof Cmp>;
@@ -3,8 +3,9 @@ import ToolbarIconButton from './cmp.toolbar-icon-button.svelte';
3
3
  import { ToolbarViewControlsLocalization } from './toolbar-view-controls-localization';
4
4
  import IconAppsList from '@fluentui/svg-icons/icons/apps_list_20_regular.svg?raw';
5
5
  import IconGrid from '@fluentui/svg-icons/icons/grid_20_regular.svg?raw';
6
+ import IconCard from '@fluentui/svg-icons/icons/square_20_regular.svg?raw';
6
7
  import { untrack } from 'svelte';
7
- const { view, size = 'sm', viewStorageKey, prepend, on } = $props();
8
+ const { view, size = 'sm', showCard = false, viewStorageKey, prepend, on } = $props();
8
9
  const localization = new ToolbarViewControlsLocalization();
9
10
  let storage = null;
10
11
  $effect(() => untrack(() => {
@@ -13,7 +14,7 @@ $effect(() => untrack(() => {
13
14
  }
14
15
  storage = LocalStorageItem.of(viewStorageKey);
15
16
  const saved = storage.get();
16
- if (saved === 'list' || saved === 'grid') {
17
+ if (saved === 'list' || saved === 'grid' || (saved === 'card' && showCard)) {
17
18
  on?.viewChange?.(saved);
18
19
  }
19
20
  }));
@@ -29,22 +30,26 @@ const setView = (next) => {
29
30
  {/if}
30
31
  <ToolbarIconButton icon={IconAppsList} size={size} active={view === 'list'} aria-label={localization.listView} on={{ click: () => setView('list') }} />
31
32
  <ToolbarIconButton icon={IconGrid} size={size} active={view === 'grid'} aria-label={localization.gridView} on={{ click: () => setView('grid') }} />
33
+ {#if showCard}
34
+ <ToolbarIconButton icon={IconCard} size={size} active={view === 'card'} aria-label={localization.cardView} on={{ click: () => setView('card') }} />
35
+ {/if}
32
36
  </div>
33
37
 
34
38
  <!--
35
39
  @component
36
40
  A toolbar view switcher: a `list` / `grid` toggle built on the shared `ToolbarIconButton` (flat, 28px at
37
- `sm` / 32px at `md`, 20px glyph). Pass `viewStorageKey` to persist the view across reloads, and compose a
38
- leading control — e.g. `ToolbarSortSelect` — through the `prepend` snippet.
41
+ `sm` / 32px at `md`, 20px glyph), with an opt-in `card` button via `showCard`. Pass `viewStorageKey` to
42
+ persist the view across reloads, and compose a leading control — e.g. `ToolbarSortSelect` — through the
43
+ `prepend` snippet.
39
44
 
40
45
  ### CSS Custom Properties
41
46
  | Property | Description | Default |
42
47
  |---|---|---|
43
- | `--sc-kit--toolbar-view-controls--gap` | Gap between the controls | `var(--sc-kit--space--3)` |
48
+ | `--sc-kit--toolbar-view-controls--gap` | Gap between the controls | `var(--sc-kit--space--2)` |
44
49
  -->
45
50
 
46
51
  <style>.toolbar-view-controls {
47
- --_tvc--gap: var(--sc-kit--toolbar-view-controls--gap, var(--sc-kit--space--3));
52
+ --_tvc--gap: var(--sc-kit--toolbar-view-controls--gap, var(--sc-kit--space--2));
48
53
  display: inline-flex;
49
54
  align-items: center;
50
55
  gap: var(--_tvc--gap);
@@ -1,9 +1,11 @@
1
1
  import type { Snippet } from 'svelte';
2
- type ViewMode = 'list' | 'grid';
2
+ type ViewMode = 'list' | 'grid' | 'card';
3
3
  type Props = {
4
4
  view: ViewMode;
5
5
  /** Button size preset — sm = 28px, md = 32px. @default 'sm' */
6
6
  size?: 'sm' | 'md';
7
+ /** Reveal the `card` view button alongside list / grid. @default false */
8
+ showCard?: boolean;
7
9
  /** Persist the view mode under this localStorage key. */
8
10
  viewStorageKey?: string;
9
11
  /** Leading content before the view toggle — e.g. a composed `ToolbarSortSelect`. */
@@ -14,13 +16,14 @@ type Props = {
14
16
  };
15
17
  /**
16
18
  * A toolbar view switcher: a `list` / `grid` toggle built on the shared `ToolbarIconButton` (flat, 28px at
17
- * `sm` / 32px at `md`, 20px glyph). Pass `viewStorageKey` to persist the view across reloads, and compose a
18
- * leading control — e.g. `ToolbarSortSelect` — through the `prepend` snippet.
19
+ * `sm` / 32px at `md`, 20px glyph), with an opt-in `card` button via `showCard`. Pass `viewStorageKey` to
20
+ * persist the view across reloads, and compose a leading control — e.g. `ToolbarSortSelect` — through the
21
+ * `prepend` snippet.
19
22
  *
20
23
  * ### CSS Custom Properties
21
24
  * | Property | Description | Default |
22
25
  * |---|---|---|
23
- * | `--sc-kit--toolbar-view-controls--gap` | Gap between the controls | `var(--sc-kit--space--3)` |
26
+ * | `--sc-kit--toolbar-view-controls--gap` | Gap between the controls | `var(--sc-kit--space--2)` |
24
27
  */
25
28
  declare const Cmp: import("svelte").Component<Props, {}, "">;
26
29
  type Cmp = ReturnType<typeof Cmp>;
@@ -2,4 +2,5 @@ export declare class ToolbarViewControlsLocalization {
2
2
  get label(): string;
3
3
  get listView(): string;
4
4
  get gridView(): string;
5
+ get cardView(): string;
5
6
  }
@@ -2,7 +2,8 @@ import { AppLocale } from '../../core/locale';
2
2
  const loc = {
3
3
  label: { en: 'View options', no: 'Visningsalternativer' },
4
4
  listView: { en: 'List view', no: 'Listevisning' },
5
- gridView: { en: 'Grid view', no: 'Rutenettvisning' }
5
+ gridView: { en: 'Grid view', no: 'Rutenettvisning' },
6
+ cardView: { en: 'Card view', no: 'Kortvisning' }
6
7
  };
7
8
  export class ToolbarViewControlsLocalization {
8
9
  get label() {
@@ -14,4 +15,7 @@ export class ToolbarViewControlsLocalization {
14
15
  get gridView() {
15
16
  return loc.gridView[AppLocale.current];
16
17
  }
18
+ get cardView() {
19
+ return loc.cardView[AppLocale.current];
20
+ }
17
21
  }
@@ -0,0 +1,92 @@
1
+ <script lang="ts">import { Tooltip } from '../tooltip';
2
+ import { ProgressBarLocalization } from './progress-bar-localization';
3
+ import { PROGRESS_BAR_TONES } from './types';
4
+ const { stage, stages, showLabel = true } = $props();
5
+ const localization = new ProgressBarLocalization();
6
+ const currentStage = $derived(stages[Math.max(0, Math.min(stage, stages.length - 1))]);
7
+ const currentLabel = $derived(currentStage?.label ?? '');
8
+ const tooltipLabel = $derived(!showLabel ? currentLabel : '');
9
+ const segmentColor = (s) => {
10
+ const value = s.color ?? 'gray';
11
+ return PROGRESS_BAR_TONES.includes(value) ? `var(--sc-kit--tone--${value}--fg)` : value;
12
+ };
13
+ </script>
14
+
15
+ <div class="progress-bar">
16
+ {#if tooltipLabel}
17
+ <Tooltip text={tooltipLabel}>
18
+ {@render track()}
19
+ </Tooltip>
20
+ {:else}
21
+ {@render track()}
22
+ {/if}
23
+ {#if showLabel && currentLabel}
24
+ <span class="progress-bar__label">{currentLabel}</span>
25
+ {/if}
26
+ </div>
27
+
28
+ {#snippet track()}
29
+ <div
30
+ class="progress-bar__track"
31
+ role="progressbar"
32
+ aria-valuemin={1}
33
+ aria-valuemax={stages.length}
34
+ aria-valuenow={stage + 1}
35
+ aria-valuetext={currentLabel || undefined}
36
+ aria-label={localization.label}>
37
+ {#each stages as s, i (i)}
38
+ <span class="progress-bar__segment" class:progress-bar__segment--filled={i <= stage} style:--_--pb--segment-color={segmentColor(s)}></span>
39
+ {/each}
40
+ </div>
41
+ {/snippet}
42
+
43
+ <!--
44
+ @component
45
+ Multi-stage workflow progress indicator. Pass `stages` as an array of `{ color, label? }`
46
+ descriptors — the bar renders one segment per stage; segments `0..stage` (inclusive) are filled,
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
49
+ for content-lifecycle indicators (Ideas → Draft → Review → Scheduled → Published) in cards, table
50
+ cells, or post pages. `showLabel={false}` drops the inline current-stage label (e.g. in a compact
51
+ table cell) and surfaces it in a hover tooltip instead.
52
+
53
+ ### CSS Custom Properties
54
+ | Property | Description | Default |
55
+ |---|---|---|
56
+ | `--sc-kit--progress-bar--segment--height` | Bar segment height | `4px` |
57
+ | `--sc-kit--progress-bar--segment--gap` | Gap between segments | `2px` |
58
+ | `--sc-kit--progress-bar--segment--background--empty` | Empty segment color | `var(--sc-kit--color--bg--active)` |
59
+ | `--sc-kit--progress-bar--track--padding-block` | Vertical padding around the bar (enlarges the hover/click target) | `5px` |
60
+ -->
61
+
62
+ <style>.progress-bar {
63
+ --_pb--seg-height: var(--sc-kit--progress-bar--segment--height, 0.25rem);
64
+ --_pb--seg-gap: var(--sc-kit--progress-bar--segment--gap, 0.125rem);
65
+ --_pb--seg-empty: var(--sc-kit--progress-bar--segment--background--empty, var(--sc-kit--color--bg--active));
66
+ --_pb--track-padding-block: var(--sc-kit--progress-bar--track--padding-block, 0.3125rem);
67
+ display: flex;
68
+ flex-direction: column;
69
+ gap: 0.125rem;
70
+ min-width: 0;
71
+ }
72
+ .progress-bar__track {
73
+ display: flex;
74
+ align-items: center;
75
+ gap: var(--_pb--seg-gap);
76
+ width: 100%;
77
+ padding-block: var(--_pb--track-padding-block);
78
+ }
79
+ .progress-bar__segment {
80
+ flex: 1;
81
+ height: var(--_pb--seg-height);
82
+ background: var(--_pb--seg-empty);
83
+ border-radius: 0.0625rem;
84
+ }
85
+ .progress-bar__segment--filled {
86
+ background: var(--_--pb--segment-color);
87
+ }
88
+ .progress-bar__label {
89
+ font-size: 0.625rem;
90
+ color: var(--sc-kit--color--text--secondary);
91
+ line-height: var(--sc-kit--leading--tight);
92
+ }</style>
@@ -0,0 +1,32 @@
1
+ import { type ProgressBarStage } from './types';
2
+ type Props = {
3
+ /** Current stage index, 0-based. Stages 0..stage (inclusive) are filled. */
4
+ stage: number;
5
+ /**
6
+ * Stage descriptors — at minimum `{ color }` per stage (consumer provides the CSS color).
7
+ * Length determines the total segment count.
8
+ */
9
+ stages: ProgressBarStage[];
10
+ /** Show the current stage's label below the bar. When `false`, the label surfaces in a tooltip on hover instead. @default true */
11
+ showLabel?: boolean;
12
+ };
13
+ /**
14
+ * Multi-stage workflow progress indicator. Pass `stages` as an array of `{ color, label? }`
15
+ * descriptors — the bar renders one segment per stage; segments `0..stage` (inclusive) are filled,
16
+ * the rest use the empty-segment color. Each stage's `color` is either a named tone from the shared
17
+ * palette (same schemes as `Badge`, e.g. `'green'`) or a raw CSS color / `var(--sc-kit--*)`. Useful
18
+ * for content-lifecycle indicators (Ideas → Draft → Review → Scheduled → Published) in cards, table
19
+ * cells, or post pages. `showLabel={false}` drops the inline current-stage label (e.g. in a compact
20
+ * table cell) and surfaces it in a hover tooltip instead.
21
+ *
22
+ * ### CSS Custom Properties
23
+ * | Property | Description | Default |
24
+ * |---|---|---|
25
+ * | `--sc-kit--progress-bar--segment--height` | Bar segment height | `4px` |
26
+ * | `--sc-kit--progress-bar--segment--gap` | Gap between segments | `2px` |
27
+ * | `--sc-kit--progress-bar--segment--background--empty` | Empty segment color | `var(--sc-kit--color--bg--active)` |
28
+ * | `--sc-kit--progress-bar--track--padding-block` | Vertical padding around the bar (enlarges the hover/click target) | `5px` |
29
+ */
30
+ declare const Cmp: import("svelte").Component<Props, {}, "">;
31
+ type Cmp = ReturnType<typeof Cmp>;
32
+ export default Cmp;
@@ -0,0 +1,2 @@
1
+ export { default as ProgressBar } from './cmp.progress-bar.svelte';
2
+ export type { ProgressBarStage, ProgressBarTone } from './types';
@@ -0,0 +1 @@
1
+ export { default as ProgressBar } from './cmp.progress-bar.svelte';
@@ -0,0 +1,3 @@
1
+ export declare class ProgressBarLocalization {
2
+ get label(): string;
3
+ }
@@ -0,0 +1,9 @@
1
+ import { AppLocale } from '../../core/locale';
2
+ const loc = {
3
+ label: { en: 'Progress', no: 'Fremdrift' }
4
+ };
5
+ export class ProgressBarLocalization {
6
+ get label() {
7
+ return loc.label[AppLocale.current];
8
+ }
9
+ }
@@ -0,0 +1,7 @@
1
+ export declare const PROGRESS_BAR_TONES: readonly ["gray", "blue", "green", "red", "orange", "teal"];
2
+ export type ProgressBarTone = (typeof PROGRESS_BAR_TONES)[number];
3
+ export type ProgressBarStage = {
4
+ /** A named tone from the shared palette (→ `--sc-kit--tone--<tone>--fg`) or any raw CSS color / `var(--sc-kit--*)`. @default 'gray' */
5
+ color?: ProgressBarTone | (string & {});
6
+ label?: string;
7
+ };
@@ -0,0 +1 @@
1
+ export const PROGRESS_BAR_TONES = ['gray', 'blue', 'green', 'red', 'orange', 'teal'];
@@ -6,7 +6,7 @@ const variant = $derived(column.variantFactory ? column.variantFactory(item) : '
6
6
 
7
7
  {#if getValueForColumn(column, item) !== null}
8
8
  <div class="table-badge-cell" class:table-badge-cell--centered={centered}>
9
- <Badge variant={variant} fullWidth={column.fullWidth}>{getValueForColumn(column, item)}</Badge>
9
+ <Badge variant={variant} shape={column.shape ?? 'pill'} fullWidth={column.fullWidth}>{getValueForColumn(column, item)}</Badge>
10
10
  </div>
11
11
  {/if}
12
12
 
@@ -21,6 +21,14 @@ const getValueForColumn = (column, item) => (column.valueFactory ? column.valueF
21
21
  </div>
22
22
  </ProportionalContainer>
23
23
  </div>
24
+ {:else if column.format === 'vertical-cover'}
25
+ <div class="table-image-cell__image table-image-cell__image--vertical-cover">
26
+ <Image src={getValueForColumn(column, item)} />
27
+ </div>
28
+ {:else if column.format === 'horizontal-cover'}
29
+ <div class="table-image-cell__image table-image-cell__image--horizontal-cover">
30
+ <Image src={getValueForColumn(column, item)} />
31
+ </div>
24
32
  {/if}
25
33
  </div>
26
34
 
@@ -35,6 +43,7 @@ const getValueForColumn = (column, item) => (column.valueFactory ? column.valueF
35
43
  justify-content: center;
36
44
  }
37
45
  .table-image-cell__image {
46
+ --_table-image-cell--cover-height: var(--_table-image-cell--image--size, 2.5em);
38
47
  display: flex;
39
48
  justify-content: center;
40
49
  align-items: center;
@@ -51,10 +60,18 @@ const getValueForColumn = (column, item) => (column.valueFactory ? column.valueF
51
60
  }
52
61
  .table-image-cell__image--contain {
53
62
  --sc-kit--image--object-fit: contain;
54
- height: 2.5em;
63
+ height: var(--_table-image-cell--cover-height);
55
64
  }
56
65
  .table-image-cell__image--cover {
57
- height: 2.5em;
66
+ height: var(--_table-image-cell--cover-height);
67
+ }
68
+ .table-image-cell__image--vertical-cover {
69
+ height: var(--_table-image-cell--cover-height);
70
+ width: calc(var(--_table-image-cell--cover-height) * 9 / 16);
71
+ }
72
+ .table-image-cell__image--horizontal-cover {
73
+ height: var(--_table-image-cell--cover-height);
74
+ width: calc(var(--_table-image-cell--cover-height) * 16 / 9);
58
75
  }
59
76
  .table-image-cell__cover-image-placer {
60
77
  width: 100%;
@@ -1,5 +1,5 @@
1
1
  import type { Repository } from '../../core/repository';
2
- import type { BadgeVariant } from '../badge';
2
+ import type { BadgeShape, BadgeVariant } from '../badge';
3
3
  import type { ButtonSize, ButtonVariant } from '../button';
4
4
  import { DynamicComponentModel } from '../dynamic-component';
5
5
  import type { IconProp } from '../icon';
@@ -37,6 +37,8 @@ export interface ITableBadgeColumn<T> extends ITableColumn<T> {
37
37
  valueFactory?: ((item: T) => string) | null;
38
38
  /** Stretch the badge to fill the column width. @default false */
39
39
  fullWidth?: boolean;
40
+ /** Corner style of the rendered badge. @default 'pill' */
41
+ shape?: BadgeShape;
40
42
  }
41
43
  export interface ITableByColumn<T> extends ITableColumn<T> {
42
44
  type: 'by';
@@ -101,7 +103,7 @@ export interface ITableTextColumn<T> extends ITableColumn<T> {
101
103
  valueFactory?: ((item: T) => string) | null;
102
104
  }
103
105
  export type TableTextColumnFormat = 'text' | 'html' | 'money' | 'date' | 'date-time';
104
- export type TableImageColumnFormat = 'circle' | 'square-contain' | 'square-cover';
106
+ export type TableImageColumnFormat = 'circle' | 'square-contain' | 'square-cover' | 'vertical-cover' | 'horizontal-cover';
105
107
  export type TableItemAction<T> = {
106
108
  title: string;
107
109
  icon?: IconProp;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/kit",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",
@@ -195,6 +195,10 @@
195
195
  "types": "./dist/ui/file-uploader/index.d.ts",
196
196
  "svelte": "./dist/ui/file-uploader/index.js"
197
197
  },
198
+ "./ui/fit-box": {
199
+ "types": "./dist/ui/fit-box/index.d.ts",
200
+ "svelte": "./dist/ui/fit-box/index.js"
201
+ },
198
202
  "./ui/form-field": {
199
203
  "types": "./dist/ui/form-field/index.d.ts",
200
204
  "svelte": "./dist/ui/form-field/index.js"
@@ -331,6 +335,10 @@
331
335
  "types": "./dist/ui/progress/index.d.ts",
332
336
  "svelte": "./dist/ui/progress/index.js"
333
337
  },
338
+ "./ui/progress-bar": {
339
+ "types": "./dist/ui/progress-bar/index.d.ts",
340
+ "svelte": "./dist/ui/progress-bar/index.js"
341
+ },
334
342
  "./ui/proportional-container": {
335
343
  "types": "./dist/ui/proportional-container/index.d.ts",
336
344
  "svelte": "./dist/ui/proportional-container/index.js"