@juspay/svelte-ui-components 2.97.1 → 2.98.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.
@@ -8,10 +8,14 @@
8
8
  ontoggle,
9
9
  triggerClasses,
10
10
  classes,
11
- testId
11
+ testId,
12
+ disabled = false
12
13
  }: AccordionProperties = $props();
13
14
 
14
15
  function handleTriggerClick(): void {
16
+ if (disabled) {
17
+ return;
18
+ }
15
19
  expand = !expand;
16
20
  ontoggle?.(expand);
17
21
  }
@@ -20,9 +24,11 @@
20
24
  {#if trigger}
21
25
  <div
22
26
  class="accordion-trigger {triggerClasses ?? ''}"
27
+ class:disabled
23
28
  role="button"
24
- tabindex="0"
29
+ tabindex={disabled ? -1 : 0}
25
30
  aria-expanded={expand}
31
+ aria-disabled={disabled}
26
32
  onclick={handleTriggerClick}
27
33
  onkeydown={(event) => {
28
34
  if (event.key === 'Enter' || event.key === ' ') {
@@ -50,6 +56,10 @@
50
56
  cursor: var(--accordion-trigger-cursor, pointer);
51
57
  }
52
58
 
59
+ .accordion-trigger.disabled {
60
+ cursor: var(--accordion-trigger-disabled-cursor, not-allowed);
61
+ }
62
+
53
63
  .accordion {
54
64
  display: grid;
55
65
  grid-template-rows: 0fr;
@@ -9,6 +9,14 @@ export type OptionalAccordionProperties = {
9
9
  triggerClasses?: string;
10
10
  classes?: string;
11
11
  testId?: string;
12
+ /**
13
+ * Disables the built-in trigger: clicks and Enter/Space no longer toggle,
14
+ * the trigger is removed from the tab order, and `aria-disabled="true"` is
15
+ * emitted. The trigger element also gets a `disabled` class so consumers can
16
+ * style the locked state. `expand` is still honoured, so a disabled accordion
17
+ * can be shown open or closed under external (controlled) state.
18
+ */
19
+ disabled?: boolean;
12
20
  };
13
21
  export type AccordionEventProperties = {
14
22
  ontoggle?: (expanded: boolean) => void;
@@ -34,7 +34,8 @@
34
34
  ontouchend,
35
35
  icon,
36
36
  children,
37
- classes
37
+ classes,
38
+ style
38
39
  }: ButtonProperties = $props();
39
40
 
40
41
  // `loading` and the legacy Circular loaderType both render the spinner and disable the button.
@@ -65,6 +66,7 @@
65
66
  class="button-container variant-{variant} size-{size} {classes ?? ''}"
66
67
  class:icon-only={iconOnly}
67
68
  class:full-width={fullWidth}
69
+ style={style ?? null}
68
70
  >
69
71
  {#if showProgressBar}
70
72
  <div class="button-progress-bar"></div>
@@ -56,6 +56,13 @@ export type OptionalButtonProperties = {
56
56
  role?: string;
57
57
  disabled?: boolean;
58
58
  classes?: string;
59
+ /**
60
+ * Inline style applied to the outer `.button-container`. Use for per-instance
61
+ * dynamic values a static class can't express — e.g. a CSS custom property
62
+ * driven by runtime state (`--offset: {n}px`). Prefer `classes` / `--button-*`
63
+ * tokens for anything static.
64
+ */
65
+ style?: string;
59
66
  };
60
67
  export type ButtonEventProperties = {
61
68
  onclick?: (event: MouseEvent) => void;
@@ -68,7 +68,11 @@
68
68
  box-shadow: var(--choicebox-focus-ring, 0 0 0 3px rgba(33, 150, 243, 0.3));
69
69
  }
70
70
 
71
- .choicebox:not(.disabled):hover {
71
+ /* Exclude the selected state from hover: without :not(.selected) this rule
72
+ (specificity 0,3,0) outranks .choicebox.selected (0,2,0), so hovering a
73
+ selected card repaints it with the neutral hover border/fill until the
74
+ cursor leaves. Excluding selected lets .choicebox.selected show through. */
75
+ .choicebox:not(.disabled):not(.selected):hover {
72
76
  border-color: var(--choicebox-hover-border-color, #9e9e9e);
73
77
  background: var(--choicebox-hover-background, var(--choicebox-background, #ffffff));
74
78
  }
@@ -587,6 +587,9 @@
587
587
  </span>
588
588
  {/if}
589
589
  {/if}
590
+ {#if typeof row.item.icon === 'string' && row.item.icon.length > 0}
591
+ <Img src={row.item.icon} alt="" fallback="" classes="select-option-icon" />
592
+ {/if}
590
593
  <span class="select-option-label">{row.item.label}</span>
591
594
  {#if showSelectedTick && !multiple && value.includes(row.item.id)}
592
595
  <!-- eslint-disable-next-line svelte/no-at-html-tags -->
@@ -754,6 +757,17 @@
754
757
  transition: background 0.1s;
755
758
  }
756
759
 
760
+ /* Per-option leading icon (SelectItem.icon). Kept inline + vertically centred so
761
+ it needs no change to the option's display and can't regress icon-less lists. */
762
+ .select-option :global(.select-option-icon) {
763
+ --image-width: var(--select-option-icon-size, 16px);
764
+ --image-height: var(--select-option-icon-size, 16px);
765
+ --image-object-fit: contain;
766
+
767
+ vertical-align: middle;
768
+ margin-right: var(--select-option-icon-gap, 8px);
769
+ }
770
+
757
771
  .select-option.multi {
758
772
  display: flex;
759
773
  align-items: center;
@@ -5,6 +5,14 @@ export type SelectItem = {
5
5
  label: string;
6
6
  /** Optional per-option test id, emitted as `data-pw` on the option element. */
7
7
  testId?: string;
8
+ /**
9
+ * Optional image src (URL or data URI) rendered at the left of the option row,
10
+ * before the label — for icon pickers and any list whose options carry a glyph.
11
+ * Size is controlled by the `--select-option-icon-size` CSS variable (default 16px).
12
+ * Pair with the trigger's `leftIcon` (driven by the selected option's icon) to show
13
+ * the current selection in the closed trigger.
14
+ */
15
+ icon?: string;
8
16
  };
9
17
  export type MandatorySelectProperties = {
10
18
  items: SelectItem[] | string[];
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { TabItem, TabsProperties } from './properties';
3
+ import Img from '../Img/Img.svelte';
3
4
  import chevronLeftSvg from '../assets/chevron-left.svg?raw';
4
5
  import chevronRightSvg from '../assets/chevron-right.svg?raw';
5
6
 
@@ -191,9 +192,22 @@
191
192
  onkeydown={(event) => handleKeydown(event, index)}
192
193
  >
193
194
  {#if typeof tab === 'function'}
194
- {@render tab({ label, index, active: isActiveItem(index), subtitle: tabItem?.subtitle })}
195
+ {@render tab({
196
+ label,
197
+ index,
198
+ active: isActiveItem(index),
199
+ subtitle: tabItem?.subtitle,
200
+ icon: tabItem?.icon,
201
+ status: tabItem?.status
202
+ })}
195
203
  {:else}
204
+ {#if typeof tabItem?.icon === 'string' && tabItem.icon.length > 0}
205
+ <Img src={tabItem.icon} alt="" fallback="" classes="tabs-item-icon" />
206
+ {/if}
196
207
  {label}
208
+ {#if tabItem?.status && tabItem.status !== 'none'}
209
+ <span class="tabs-item-status status-{tabItem.status}" aria-hidden="true"></span>
210
+ {/if}
197
211
  {/if}
198
212
  </div>
199
213
  {/each}
@@ -327,6 +341,7 @@
327
341
  .tabs-item {
328
342
  display: flex;
329
343
  align-items: center;
344
+ gap: var(--tabs-item-gap, 8px);
330
345
  position: relative;
331
346
  padding: var(--tabs-item-padding, 12px 16px);
332
347
  font-size: var(--tabs-item-font-size, 14px);
@@ -344,6 +359,38 @@
344
359
  transition: var(--tabs-transition, color 0.2s ease, background 0.2s ease);
345
360
  }
346
361
 
362
+ .tabs-item :global(.tabs-item-icon) {
363
+ --image-width: var(--tabs-item-icon-size, 16px);
364
+ --image-height: var(--tabs-item-icon-size, 16px);
365
+ --image-object-fit: contain;
366
+
367
+ flex-shrink: 0;
368
+ }
369
+
370
+ /* Trailing status dot for nav/menu tabs. margin-left:auto pushes it to the row's
371
+ end (e.g. a settings menu); harmless on a normal tab bar where the item shrinks
372
+ to content. */
373
+ .tabs-item-status {
374
+ width: var(--tabs-item-status-size, 8px);
375
+ height: var(--tabs-item-status-size, 8px);
376
+ margin-left: auto;
377
+ border-radius: 50%;
378
+ flex-shrink: 0;
379
+ background: var(--tabs-item-status-default-color, transparent);
380
+ }
381
+
382
+ .tabs-item-status.status-pending {
383
+ background: var(--tabs-item-status-pending-color, #f59e0b);
384
+ }
385
+
386
+ .tabs-item-status.status-error {
387
+ background: var(--tabs-item-status-error-color, #e7000b);
388
+ }
389
+
390
+ .tabs-item-status.status-success {
391
+ background: var(--tabs-item-status-success-color, #16a34a);
392
+ }
393
+
347
394
  .tabs-item:hover:not(.active):not([aria-disabled]) {
348
395
  color: var(--tabs-hover-color, #333333);
349
396
  background: var(--tabs-hover-background, #f5f5f5);
@@ -4,6 +4,18 @@ export type TabItem = {
4
4
  label: string;
5
5
  testId?: string;
6
6
  subtitle?: string;
7
+ /**
8
+ * Optional image src (URL or data URI) rendered before the label in the default
9
+ * tab layout. Size is controlled by the `--tabs-item-icon-size` CSS variable
10
+ * (default 16px). Also forwarded to the `tab` snippet for custom layouts.
11
+ */
12
+ icon?: string;
13
+ /**
14
+ * Optional status dot rendered after the label — for nav/menu tabs that flag
15
+ * per-item state. `'none'` (default) renders nothing. Colours are themeable via
16
+ * `--tabs-item-status-{pending,error,success}-color`.
17
+ */
18
+ status?: 'none' | 'pending' | 'error' | 'success';
7
19
  };
8
20
  export type TabsProperties = MandatoryTabsProperties & OptionalTabsProperties & TabsEventProperties;
9
21
  export type MandatoryTabsProperties = {
@@ -16,12 +28,16 @@ export type OptionalTabsProperties = {
16
28
  testId?: string;
17
29
  scrollLeftIcon?: Snippet;
18
30
  scrollRightIcon?: Snippet;
19
- tab?: Snippet<[{
20
- label: string;
21
- index: number;
22
- active: boolean;
23
- subtitle?: string;
24
- }]>;
31
+ tab?: Snippet<[
32
+ {
33
+ label: string;
34
+ index: number;
35
+ active: boolean;
36
+ subtitle?: string;
37
+ icon?: string;
38
+ status?: TabItem['status'];
39
+ }
40
+ ]>;
25
41
  classes?: string;
26
42
  };
27
43
  export type TabsEventProperties = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/svelte-ui-components",
3
- "version": "2.97.1",
3
+ "version": "2.98.0",
4
4
  "description": "A themeable Svelte 5 UI component library with CSS custom property driven styling",
5
5
  "keywords": [
6
6
  "svelte",