@r2digisolutions/components 0.9.5 → 0.9.7

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.
@@ -40,7 +40,7 @@
40
40
 
41
41
  const variantClasses: Record<ButtonVariant, string> = {
42
42
  primary:
43
- 'bg-brand-500 text-white hover:bg-brand-600 active:bg-brand-700 focus-visible:ring-brand-500 shadow-sm hover:shadow-md active:scale-[0.98]',
43
+ 'bg-brand-600 text-white hover:bg-brand-700 active:bg-brand-800 focus-visible:ring-brand-500 shadow-sm hover:shadow-md active:scale-[0.98]',
44
44
  secondary:
45
45
  'bg-surface-elevated text-primary border border-border hover:bg-surface-overlay hover:border-border-strong focus-visible:ring-brand-500 shadow-sm active:scale-[0.98]',
46
46
  ghost:
@@ -26,8 +26,7 @@
26
26
  'inline-flex shrink-0 items-center justify-center rounded-full font-semibold tabular-nums transition-colors',
27
27
  dims,
28
28
  state === 'complete' && 'bg-brand-500 text-white',
29
- state === 'current' &&
30
- 'bg-brand-500/15 text-brand-700 ring-2 ring-brand-500/40 dark:text-brand-300',
29
+ state === 'current' && 'bg-brand-500 text-white',
31
30
  state === 'upcoming' && 'bg-surface-overlay text-muted ring-1 ring-border'
32
31
  ]}
33
32
  aria-current={state === 'current' ? 'step' : undefined}
@@ -18,6 +18,8 @@
18
18
  fullWidth?: boolean;
19
19
  align?: 'start' | 'end' | 'between';
20
20
  showCancel?: boolean;
21
+ /** Hide the primary submit control (e.g. file-upload step that auto-advances). */
22
+ showSubmit?: boolean;
21
23
  /**
22
24
  * plain = bare row
23
25
  * bar = bordered footer chrome inside a card
@@ -44,6 +46,7 @@
44
46
  fullWidth = true,
45
47
  align = 'end',
46
48
  showCancel = true,
49
+ showSubmit = true,
47
50
  variant = 'bar',
48
51
  size = 'md',
49
52
  hint = '',
@@ -69,12 +72,11 @@
69
72
 
70
73
  <div
71
74
  class={[
72
- 'flex w-full flex-col gap-3 sm:flex-row sm:items-center',
75
+ 'gap-3 sm:flex-row sm:items-center flex w-full flex-col',
73
76
  alignClasses[align],
74
- variant === 'bar' &&
75
- 'rounded-b-2xl border-t border-border bg-surface/50 px-4 py-3 sm:px-5',
77
+ variant === 'bar' && 'rounded-b-2xl border-border bg-surface/50 px-4 py-3 sm:px-5 border-t',
76
78
  variant === 'sticky' &&
77
- 'shrink-0 border-t border-border bg-surface-elevated/95 px-4 py-3 shadow-[0_-8px_24px_rgb(0,0,0,0.06)] backdrop-blur-md dark:shadow-black/30 sm:px-5',
79
+ 'border-border bg-surface-elevated/95 px-4 py-3 backdrop-blur-md dark:shadow-black/30 sm:px-5 shrink-0 border-t shadow-[0_-8px_24px_rgb(0,0,0,0.06)]',
78
80
  variant === 'plain' && 'gap-2',
79
81
  className
80
82
  ]}
@@ -82,7 +84,7 @@
82
84
  aria-label="Form actions"
83
85
  >
84
86
  {#if hint || extra || dangerLabel}
85
- <div class="flex min-w-0 flex-1 flex-wrap items-center gap-2">
87
+ <div class="min-w-0 gap-2 flex flex-1 flex-wrap items-center">
86
88
  {#if dangerLabel}
87
89
  <Button
88
90
  type="button"
@@ -103,13 +105,13 @@
103
105
  {/if}
104
106
  </div>
105
107
  {:else if align === 'between'}
106
- <div class="hidden flex-1 sm:block" aria-hidden="true"></div>
108
+ <div class="sm:block hidden flex-1" aria-hidden="true"></div>
107
109
  {/if}
108
110
 
109
111
  <div
110
112
  class={[
111
- 'flex items-center gap-2',
112
- fullWidth && 'w-full flex-col-reverse sm:w-auto sm:flex-row'
113
+ 'gap-2 flex items-center',
114
+ fullWidth && 'sm:w-auto sm:flex-row w-full flex-col-reverse'
113
115
  ]}
114
116
  >
115
117
  {#if showCancel}
@@ -118,21 +120,23 @@
118
120
  variant="secondary"
119
121
  {size}
120
122
  disabled={busy}
121
- class={fullWidth ? 'w-full sm:w-auto' : ''}
123
+ class={fullWidth ? 'sm:w-auto w-full' : ''}
122
124
  onclick={() => oncancel?.()}
123
125
  >
124
126
  {resolvedCancelLabel}
125
127
  </Button>
126
128
  {/if}
127
- <Button
128
- type="submit"
129
- {size}
130
- loading={resolvedLoading}
131
- disabled={busy || submitDisabled}
132
- class={fullWidth ? 'w-full sm:w-auto' : ''}
133
- onclick={() => onsubmit?.()}
134
- >
135
- {resolvedSubmitLabel}
136
- </Button>
129
+ {#if showSubmit}
130
+ <Button
131
+ type="submit"
132
+ {size}
133
+ loading={resolvedLoading}
134
+ disabled={busy || submitDisabled}
135
+ class={fullWidth ? 'sm:w-auto w-full' : ''}
136
+ onclick={() => onsubmit?.()}
137
+ >
138
+ {resolvedSubmitLabel}
139
+ </Button>
140
+ {/if}
137
141
  </div>
138
142
  </div>
@@ -13,6 +13,8 @@ interface FormActionsProps {
13
13
  fullWidth?: boolean;
14
14
  align?: 'start' | 'end' | 'between';
15
15
  showCancel?: boolean;
16
+ /** Hide the primary submit control (e.g. file-upload step that auto-advances). */
17
+ showSubmit?: boolean;
16
18
  /**
17
19
  * plain = bare row
18
20
  * bar = bordered footer chrome inside a card
@@ -103,7 +103,7 @@
103
103
  variant === 'dots' ? circleSize.dot : circleSize.box,
104
104
  'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500/30 focus-visible:ring-offset-2 focus-visible:ring-offset-surface',
105
105
  state === 'complete' && 'border-brand-500 bg-brand-500 text-white',
106
- state === 'current' && 'border-brand-500 bg-surface-elevated text-brand-600',
106
+ state === 'current' && 'border-brand-500 bg-brand-500 text-white',
107
107
  state === 'error' && 'border-red-500 bg-red-50 text-red-600 dark:bg-red-950/40 dark:text-red-400',
108
108
  state === 'upcoming' && 'border-border bg-surface-elevated text-muted'
109
109
  ];
@@ -112,7 +112,7 @@
112
112
  const viewTop = vv?.offsetTop ?? 0;
113
113
  const gap = 8;
114
114
  const pad = 8;
115
- const estimated = { panelW: Math.max(trigger.width, 192), panelH: 280 };
115
+ const estimated = { panelW: 208, panelH: 280 };
116
116
  const canMeasure = opts.measure !== false && !!panelEl?.matches(':popover-open');
117
117
  const panelW = Math.min(
118
118
  canMeasure && panelEl?.offsetWidth ? panelEl.offsetWidth : estimated.panelW,
@@ -139,13 +139,12 @@
139
139
  if (panelEl) {
140
140
  const s = panelEl.style;
141
141
  s.setProperty('margin', '0');
142
- s.setProperty('inset', 'auto');
142
+ s.removeProperty('inset');
143
143
  s.setProperty('top', `${Math.round(top)}px`);
144
144
  s.setProperty('left', `${Math.round(left)}px`);
145
145
  s.setProperty('right', 'auto');
146
146
  s.setProperty('bottom', 'auto');
147
- s.setProperty('width', 'max-content');
148
- s.setProperty('min-width', `${Math.round(Math.min(panelW, trigger.width))}px`);
147
+ s.setProperty('width', '13rem');
149
148
  s.setProperty('visibility', placed ? 'visible' : 'hidden');
150
149
  }
151
150
  }
@@ -263,16 +262,16 @@
263
262
  });
264
263
  </script>
265
264
 
266
- <div class={['w-full min-w-[12rem] max-w-[16rem]', className]}>
265
+ <div class={['w-full max-w-[16rem] min-w-[12rem]', className]}>
267
266
  {#if label}
268
- <span class="mb-1.5 block text-sm font-medium text-primary">{label}</span>
267
+ <span class="mb-1.5 text-sm font-medium text-primary block">{label}</span>
269
268
  {/if}
270
269
 
271
270
  <div
272
271
  bind:this={triggerEl}
273
272
  class={[
274
- 'flex h-10 w-full items-center overflow-hidden rounded-xl border border-border bg-surface-elevated transition-colors',
275
- open && 'border-brand-500 ring-2 ring-brand-500/20',
273
+ 'h-10 rounded-xl border-border bg-surface-elevated flex w-full items-center overflow-hidden border transition-colors',
274
+ open && 'border-brand-500 ring-brand-500/20 ring-2',
276
275
  disabled && 'opacity-60'
277
276
  ]}
278
277
  >
@@ -285,13 +284,13 @@
285
284
  aria-haspopup="dialog"
286
285
  aria-controls={panelId}
287
286
  class={[
288
- 'flex h-full min-w-0 flex-1 items-center gap-2 px-3.5 text-left text-sm',
287
+ 'min-w-0 gap-2 px-3.5 text-sm flex h-full flex-1 items-center text-left',
289
288
  'hover:bg-surface-overlay focus-visible:outline-none',
290
289
  disabled && 'cursor-not-allowed'
291
290
  ]}
292
291
  >
293
292
  <svg
294
- class="h-4 w-4 shrink-0 text-muted"
293
+ class="h-4 w-4 text-muted shrink-0"
295
294
  viewBox="0 0 24 24"
296
295
  fill="none"
297
296
  stroke="currentColor"
@@ -312,10 +311,16 @@
312
311
  <button
313
312
  type="button"
314
313
  onclick={clear}
315
- class="h-full px-3 text-muted hover:bg-surface-overlay hover:text-primary"
314
+ class="px-3 text-muted hover:bg-surface-overlay hover:text-primary h-full"
316
315
  aria-label="Clear"
317
316
  >
318
- <svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
317
+ <svg
318
+ class="h-3.5 w-3.5"
319
+ viewBox="0 0 24 24"
320
+ fill="none"
321
+ stroke="currentColor"
322
+ stroke-width="2"
323
+ >
319
324
  <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
320
325
  </svg>
321
326
  </button>
@@ -332,18 +337,18 @@
332
337
  onbeforetoggle={handleBeforeToggle}
333
338
  ontoggle={handleToggle}
334
339
  data-placed={placed ? true : undefined}
335
- class="timepicker-popover m-0 overflow-hidden rounded-2xl border border-border bg-surface-elevated shadow-xl outline-none"
340
+ class="timepicker-popover m-0 w-52 rounded-2xl border-border bg-surface-elevated shadow-xl overflow-hidden border outline-none"
336
341
  >
337
- <div class="grid grid-cols-2 divide-x divide-border">
342
+ <div class="divide-border grid grid-cols-2 divide-x">
338
343
  <div class="flex flex-col">
339
344
  <span
340
- class="border-b border-border px-3 py-2 text-center text-[11px] font-medium tracking-wide text-muted uppercase"
345
+ class="border-border px-3 py-2 font-medium tracking-wide text-muted border-b text-center text-[11px] uppercase"
341
346
  >
342
347
  Hour
343
348
  </span>
344
349
  <div
345
350
  bind:this={hourListEl}
346
- class="max-h-52 overflow-y-auto p-1.5"
351
+ class="max-h-52 p-1.5 overflow-y-auto"
347
352
  role="listbox"
348
353
  aria-label="Hours"
349
354
  >
@@ -356,8 +361,8 @@
356
361
  aria-selected={selectedH === h}
357
362
  onclick={() => pickHour(h)}
358
363
  class={[
359
- 'w-full rounded-lg px-2 py-1.5 text-sm transition-colors',
360
- 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500/30',
364
+ 'rounded-lg px-2 py-1.5 text-sm w-full transition-colors',
365
+ 'focus-visible:ring-brand-500/30 focus-visible:ring-2 focus-visible:outline-none',
361
366
  disabledHour && 'cursor-not-allowed opacity-30',
362
367
  selectedH === h
363
368
  ? 'bg-brand-500 font-semibold text-white'
@@ -371,13 +376,13 @@
371
376
  </div>
372
377
  <div class="flex flex-col">
373
378
  <span
374
- class="border-b border-border px-3 py-2 text-center text-[11px] font-medium tracking-wide text-muted uppercase"
379
+ class="border-border px-3 py-2 font-medium tracking-wide text-muted border-b text-center text-[11px] uppercase"
375
380
  >
376
381
  Min
377
382
  </span>
378
383
  <div
379
384
  bind:this={minuteListEl}
380
- class="max-h-52 overflow-y-auto p-1.5"
385
+ class="max-h-52 p-1.5 overflow-y-auto"
381
386
  role="listbox"
382
387
  aria-label="Minutes"
383
388
  >
@@ -390,8 +395,8 @@
390
395
  aria-selected={selectedM === m}
391
396
  onclick={() => pickMinute(m)}
392
397
  class={[
393
- 'w-full rounded-lg px-2 py-1.5 text-sm transition-colors',
394
- 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500/30',
398
+ 'rounded-lg px-2 py-1.5 text-sm w-full transition-colors',
399
+ 'focus-visible:ring-brand-500/30 focus-visible:ring-2 focus-visible:outline-none',
395
400
  disabledMin && 'cursor-not-allowed opacity-30',
396
401
  selectedM === m
397
402
  ? 'bg-brand-500 font-semibold text-white'
@@ -412,8 +417,12 @@
412
417
  .timepicker-popover {
413
418
  position: fixed;
414
419
  inset: unset;
420
+ top: auto;
421
+ right: auto;
422
+ bottom: auto;
423
+ left: auto;
415
424
  margin: 0;
416
- width: max-content;
425
+ width: 13rem;
417
426
  height: max-content;
418
427
  }
419
428
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@r2digisolutions/components",
3
- "version": "0.9.5",
3
+ "version": "0.9.7",
4
4
  "private": false,
5
5
  "description": "R2DigiSolutions Svelte 5 component library — Atomic Design, Tailwind 4, Light/Dark mode",
6
6
  "license": "MIT",