@juspay/svelte-ui-components 2.80.2 → 2.80.4

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.
@@ -114,7 +114,7 @@
114
114
  line-height: var(--banner-line-height, 1.3);
115
115
  border-bottom: var(--banner-border-bottom, none);
116
116
  border: var(--banner-border);
117
- border-radius: var(--banner-border-radius, 0);
117
+ border-radius: var(--banner-border-radius, var(--radius, 4px));
118
118
  cursor: var(--banner-cursor, pointer);
119
119
  position: var(--banner-position, sticky);
120
120
  top: var(--banner-top, 0);
@@ -18,6 +18,7 @@
18
18
  import { formatNumber } from '../_chart/format';
19
19
  import { roundedRectPath } from '../_chart/paths';
20
20
  import type { LegendItem, BarRect } from '../_chart/types';
21
+ import { DEFAULT_CHART_CORNER_RADIUS } from '../_chart/types';
21
22
  import { SvelteMap } from 'svelte/reactivity';
22
23
 
23
24
  // ── Per-instance uid prefix for <defs> ids (A1-3) ─────────────
@@ -52,7 +53,7 @@
52
53
  showYAxis = true,
53
54
  showLegend = false,
54
55
  barPadding = 0.2,
55
- barRadius = 4,
56
+ barRadius = DEFAULT_CHART_CORNER_RADIUS,
56
57
  aspectRatio = 16 / 9,
57
58
  xAxisLabel,
58
59
  yAxisLabel,
@@ -76,6 +76,12 @@ export type OptionalBarChartProperties = {
76
76
  showXAxis?: boolean;
77
77
  showYAxis?: boolean;
78
78
  barPadding?: number;
79
+ /**
80
+ * Corner radius on bar/column shapes in pixels. Defaults to
81
+ * `DEFAULT_CHART_CORNER_RADIUS` (4), mirroring the design system's base
82
+ * `--radius` token. SVG `rx`/`ry` cannot read CSS `var()`, so pass this
83
+ * prop explicitly to track a changed `--radius` at runtime.
84
+ */
79
85
  barRadius?: number;
80
86
  aspectRatio?: number;
81
87
  xAxisLabel?: string;
@@ -4,10 +4,20 @@
4
4
 
5
5
  let {
6
6
  text,
7
+ variant = 'primary',
8
+ size = 'md',
9
+ iconOnly = false,
10
+ fullWidth = false,
11
+ href,
12
+ target,
13
+ rel,
14
+ loading = false,
15
+ allowHtml = false,
7
16
  enable = true,
8
17
  disabled = false,
9
18
  showLoader = false,
10
19
  loaderType,
20
+ showProgressBar = $bindable(false),
11
21
  type = 'button',
12
22
  testId,
13
23
  ariaLabel,
@@ -22,30 +32,46 @@
22
32
  onmouseleave,
23
33
  ontouchstart,
24
34
  ontouchend,
25
- showProgressBar = $bindable(false),
26
35
  icon,
27
36
  children,
28
37
  classes
29
38
  }: ButtonProperties = $props();
30
39
 
31
- let isDisabled = $derived(!enable || disabled || showLoader);
40
+ // `loading` and the legacy Circular loaderType both render the spinner and disable the button.
41
+ let showCircularLoader = $derived(loading || (showLoader && loaderType === 'Circular'));
42
+ // The legacy ProgressBar loader stays clickable until the first click starts the bar, then
43
+ // it disables (the documented flow). So it must NOT count toward "disabled" before it starts.
44
+ let isProgressBarLoader = $derived(showLoader && loaderType === 'ProgressBar');
45
+ let isBusy = $derived(showCircularLoader || showProgressBar);
46
+ let isDisabled = $derived(disabled || !enable || showCircularLoader || showProgressBar);
47
+ let resolvedRel = $derived(rel ?? (target === '_blank' ? 'noopener noreferrer' : null));
32
48
 
33
49
  function handleButtonClick(event: MouseEvent): void {
34
- if (showProgressBar) {
50
+ if (isDisabled) {
51
+ // Anchors have no native disabled state — block navigation/handler explicitly.
52
+ if (href) {
53
+ event.preventDefault();
54
+ }
35
55
  return;
36
56
  }
37
57
  onclick?.(event);
38
- if (showLoader && loaderType === 'ProgressBar') {
58
+ if (isProgressBarLoader) {
39
59
  showProgressBar = true;
40
60
  }
41
61
  }
42
62
  </script>
43
63
 
44
- <div class="button-container {classes ?? ''}">
64
+ <div
65
+ class="button-container variant-{variant} size-{size} {classes ?? ''}"
66
+ class:icon-only={iconOnly}
67
+ class:full-width={fullWidth}
68
+ >
45
69
  {#if showProgressBar}
46
70
  <div class="button-progress-bar"></div>
47
71
  {/if}
48
- <button
72
+ <svelte:element
73
+ this={href ? 'a' : 'button'}
74
+ class="button-el"
49
75
  class:disabled={isDisabled}
50
76
  onclick={handleButtonClick}
51
77
  {onkeydown}
@@ -55,28 +81,38 @@
55
81
  {onmouseleave}
56
82
  {ontouchstart}
57
83
  {ontouchend}
58
- disabled={isDisabled}
59
- {type}
60
84
  data-pw={testId}
61
- aria-label={ariaLabel}
62
- aria-expanded={ariaExpanded}
63
- aria-selected={ariaSelected}
64
- {role}
85
+ role={role ?? null}
86
+ aria-label={ariaLabel ?? null}
87
+ aria-expanded={ariaExpanded ?? null}
88
+ aria-selected={ariaSelected ?? null}
89
+ aria-busy={isBusy || null}
90
+ type={href ? null : type}
91
+ disabled={href ? null : isDisabled}
92
+ href={href ? (isDisabled ? null : href) : null}
93
+ target={href ? target : null}
94
+ rel={href ? resolvedRel : null}
95
+ aria-disabled={href && isDisabled ? 'true' : null}
96
+ tabindex={href && isDisabled ? -1 : null}
65
97
  >
66
- {#if showLoader && loaderType === 'Circular'}
98
+ {#if showCircularLoader}
67
99
  <div class="button-loader"><Loader /></div>
68
100
  {/if}
69
101
  {#if typeof icon === 'function'}
70
102
  <div class="button-icon">{@render icon()}</div>
71
103
  {/if}
72
104
  {#if typeof text === 'string' && text.length > 0}
73
- <!-- eslint-disable-next-line svelte/no-at-html-tags -->
74
- <div class="button-text">{@html text}</div>
105
+ {#if allowHtml}
106
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
107
+ <div class="button-text">{@html text}</div>
108
+ {:else}
109
+ <div class="button-text">{text}</div>
110
+ {/if}
75
111
  {/if}
76
112
  {#if typeof children === 'function'}
77
113
  {@render children()}
78
114
  {/if}
79
- </button>
115
+ </svelte:element>
80
116
  </div>
81
117
 
82
118
  <style>
@@ -84,23 +120,92 @@
84
120
  position: relative;
85
121
  width: var(--button-width, fit-content);
86
122
  }
87
- button {
123
+
124
+ /* ---- Variant defaults (set the internal --_btn-* layer; explicit --button-* always wins) ---- */
125
+ .variant-primary {
126
+ --_btn-color: #3a4550;
127
+ --_btn-text-color: #ffffff;
128
+ --_btn-border: none;
129
+ }
130
+
131
+ .variant-secondary {
132
+ --_btn-color: transparent;
133
+ --_btn-text-color: #3a4550;
134
+ --_btn-border: 1px solid #cbd5e1;
135
+ --_btn-hover-color: #f1f5f9;
136
+ --_btn-hover-text-color: #3a4550;
137
+ --_btn-hover-border: 1px solid #cbd5e1;
138
+ }
139
+
140
+ .variant-ghost {
141
+ --_btn-color: transparent;
142
+ --_btn-text-color: #3a4550;
143
+ --_btn-border: none;
144
+ --_btn-hover-color: #f1f5f9;
145
+ --_btn-hover-text-color: #3a4550;
146
+ --_btn-hover-border: none;
147
+ }
148
+
149
+ .variant-destructive {
150
+ --_btn-color: #e7000b;
151
+ --_btn-text-color: #ffffff;
152
+ --_btn-border: none;
153
+ --_btn-hover-color: #c10007;
154
+ --_btn-hover-text-color: #ffffff;
155
+ --_btn-hover-border: none;
156
+ }
157
+
158
+ /* ---- Size defaults (md reproduces the legacy 16px padding / 14px font) ---- */
159
+ .size-sm {
160
+ --_btn-padding: 8px 12px;
161
+ --_btn-font-size: 13px;
162
+ }
163
+
164
+ .size-md {
165
+ --_btn-padding: 16px;
166
+ --_btn-font-size: 14px;
167
+ }
168
+
169
+ .size-lg {
170
+ --_btn-padding: 20px 28px;
171
+ --_btn-font-size: 16px;
172
+ }
173
+
174
+ .icon-only {
175
+ --_btn-padding: 8px;
176
+ }
177
+
178
+ .icon-only.size-sm {
179
+ --_btn-padding: 6px;
180
+ }
181
+
182
+ .icon-only.size-lg {
183
+ --_btn-padding: 12px;
184
+ }
185
+
186
+ .full-width {
187
+ --button-width: 100%;
188
+ }
189
+
190
+ .button-el {
88
191
  max-height: var(--button-max-height);
89
192
  max-width: var(--button-max-width);
90
193
  min-width: var(--button-min-width);
91
194
  font-family: var(--button-font-family);
92
195
  font-weight: var(--button-font-weight, 500);
93
- font-size: var(--button-font-size, 14px);
94
- background-color: var(--button-color, #3a4550);
95
- color: var(--button-text-color, white);
196
+ font-size: var(--button-font-size, var(--_btn-font-size, 14px));
197
+ background-color: var(--button-color, var(--_btn-color, #3a4550));
198
+ color: var(--button-text-color, var(--_btn-text-color, white));
96
199
  height: var(--button-height, fit-content);
97
- padding: var(--button-padding, 16px);
200
+ padding: var(--button-padding, var(--_btn-padding, 16px));
98
201
  margin: var(--button-margin);
99
- border-radius: var(--button-border-radius, var(--radius, 4px));
202
+ border-radius: var(--button-border-radius, var(--radius, 6px));
100
203
  width: var(--button-width, fit-content);
101
204
  cursor: var(--cursor, pointer);
102
205
  opacity: var(--opacity, 1);
103
- border: var(--button-border, none);
206
+ border: var(--button-border, var(--_btn-border, none));
207
+ box-sizing: border-box;
208
+ text-decoration: none;
104
209
  display: flex;
105
210
  justify-content: var(--button-justify-content, center);
106
211
  align-items: center;
@@ -110,14 +215,16 @@
110
215
  box-shadow: var(--button-box-shadow, none);
111
216
  }
112
217
 
113
- .disabled {
218
+ .button-el.disabled,
219
+ .button-el:disabled {
114
220
  cursor: var(--disabled-cursor, not-allowed);
115
221
  opacity: var(--disabled-opacity, 0.4);
116
- color: var(--disabled-text-color, var(--button-text-color, white));
222
+ color: var(--disabled-text-color, var(--button-text-color, var(--_btn-text-color, white)));
117
223
  font-size: var(--disabled-font-size);
118
224
  font-weight: var(--disabled-font-weight);
119
- border: var(--disabled-border);
120
- background: var(--disabled-background-color, var(--button-color, #3a4550));
225
+ /* Preserve the variant border when disabled so secondary (bordered) stays distinct from ghost. */
226
+ border: var(--disabled-border, var(--button-border, var(--_btn-border, none)));
227
+ background: var(--disabled-background-color, var(--button-color, var(--_btn-color, #3a4550)));
121
228
  box-shadow: var(
122
229
  --button-disabled-box-shadow,
123
230
  var(--disabled-box-shadow, var(--button-box-shadow, none))
@@ -138,21 +245,30 @@
138
245
  display: var(--button-text-display);
139
246
  }
140
247
 
141
- button:hover {
142
- background: var(--button-hover-color, var(--button-color, #3a4550));
143
- color: var(--button-hover-text-color, var(--button-text-color, white));
144
- border: var(--button-hover-border, var(--button-border, none));
248
+ .button-el:hover:not(.disabled) {
249
+ background: var(
250
+ --button-hover-color,
251
+ var(--_btn-hover-color, var(--button-color, var(--_btn-color, #3a4550)))
252
+ );
253
+ color: var(
254
+ --button-hover-text-color,
255
+ var(--_btn-hover-text-color, var(--button-text-color, var(--_btn-text-color, white)))
256
+ );
257
+ border: var(
258
+ --button-hover-border,
259
+ var(--_btn-hover-border, var(--button-border, var(--_btn-border, none)))
260
+ );
145
261
  transform: var(--button-hover-transform);
146
262
  box-shadow: var(--button-hover-box-shadow, var(--button-box-shadow, none));
147
263
  }
148
264
 
149
- button:active {
265
+ .button-el:active:not(.disabled) {
150
266
  transform: var(--button-active-transform);
151
- background: var(--button-active-background, var(--button-color, #3a4550));
267
+ background: var(--button-active-background, var(--button-color, var(--_btn-color, #3a4550)));
152
268
  box-shadow: var(--button-active-box-shadow, var(--button-box-shadow, none));
153
269
  }
154
270
 
155
- button:focus-visible {
271
+ .button-el:focus-visible {
156
272
  box-shadow: var(--button-focus-visible-box-shadow, var(--button-box-shadow, none));
157
273
  }
158
274
 
@@ -1,12 +1,51 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  export type LoaderType = 'Circular' | 'ProgressBar';
3
+ /** Visual style of the button. Maps to a built-in palette via `--button-*` variables. */
4
+ export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'destructive';
5
+ /** Size preset controlling padding/height/font-size. */
6
+ export type ButtonSize = 'sm' | 'md' | 'lg';
3
7
  export type ButtonProperties = OptionalButtonProperties & ButtonEventProperties;
4
8
  export type OptionalButtonProperties = {
5
9
  text?: string;
10
+ /**
11
+ * Visual style: `primary` (default, filled), `secondary` (outlined), `ghost`
12
+ * (transparent), `destructive` (danger). Each maps to the `--button-*` variables, so an
13
+ * explicit `--button-color` / `classes` override always wins over the variant default.
14
+ */
15
+ variant?: ButtonVariant;
16
+ /** Size preset: `sm` / `md` (default) / `lg`. Controls padding, height, and font size. */
17
+ size?: ButtonSize;
18
+ /** Square padding for an icon-only button. Pair with `ariaLabel` for accessibility. */
19
+ iconOnly?: boolean;
20
+ /** Stretch the button to the full width of its container. */
21
+ fullWidth?: boolean;
22
+ /**
23
+ * Render the button as an `<a>` styled identically. Use for button-styled links/navigation.
24
+ * When set, `type` is ignored; a disabled link is rendered inert via `aria-disabled`.
25
+ */
26
+ href?: string;
27
+ /** Anchor target (only with `href`), e.g. `_blank`. */
28
+ target?: string;
29
+ /** Anchor rel (only with `href`). Defaults to `noopener noreferrer` when `target="_blank"`. */
30
+ rel?: string;
31
+ /**
32
+ * Show a loading state: renders the circular loader, sets `aria-busy`, and disables the
33
+ * button. Preferred over the legacy `showLoader`/`loaderType` pair.
34
+ */
35
+ loading?: boolean;
36
+ /**
37
+ * Render the `text` as raw HTML. Defaults to `false` (safe plain-text rendering). Only enable
38
+ * for trusted, non-user-derived markup.
39
+ */
40
+ allowHtml?: boolean;
41
+ /** @deprecated Use `disabled` instead. Retained for backward compatibility. */
6
42
  enable?: boolean;
7
- showProgressBar?: boolean;
43
+ /** @deprecated Prefer `loading`. Shows a loader; pair with `loaderType`. */
8
44
  showLoader?: boolean;
45
+ /** @deprecated Used with `showLoader`. `'Circular'` or `'ProgressBar'`. */
9
46
  loaderType?: LoaderType;
47
+ /** @deprecated Drives the progress-bar animation when `loaderType='ProgressBar'`. */
48
+ showProgressBar?: boolean;
10
49
  type?: 'submit' | 'reset' | 'button';
11
50
  testId?: string;
12
51
  icon?: Snippet;