mertani-web-toolkit 0.1.27 → 0.1.29

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.
@@ -0,0 +1,4 @@
1
+ export interface IFieldOption {
2
+ id: string;
3
+ nama: string;
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,159 @@
1
+ <script lang="ts">
2
+ import { twMerge } from 'tailwind-merge';
3
+ import type { IFieldOption } from './Radio.js';
4
+
5
+ const {
6
+ id,
7
+ value = '',
8
+ options = [],
9
+ onChange,
10
+ required = false,
11
+ errorMessage,
12
+ disabled = false,
13
+ label = '',
14
+ subLabel = '',
15
+ class: className = '',
16
+ style: customStyle = ''
17
+ }: {
18
+ id: string;
19
+ value?: string;
20
+ options?: Array<IFieldOption>;
21
+ onChange?: (val: string) => void;
22
+ required?: boolean;
23
+ errorMessage?: string;
24
+ disabled?: boolean;
25
+ label?: string;
26
+ subLabel?: string;
27
+ class?: string;
28
+ style?: string;
29
+ } = $props();
30
+
31
+ const baseClasses = $derived(() => {
32
+ let classes = 'radio-container';
33
+ if (className) {
34
+ classes += ` ${className}`;
35
+ }
36
+ return classes;
37
+ });
38
+
39
+ const mergedClasses = $derived(twMerge(baseClasses(), className));
40
+ </script>
41
+
42
+ <div class={mergedClasses} style={customStyle}>
43
+ {#if label}
44
+ <label for={id} class="radio-label">
45
+ {label}
46
+ {#if required}
47
+ <span class="required-indicator">*</span>
48
+ {/if}
49
+ {#if subLabel}
50
+ <span class="label-subLabel">{subLabel}</span>
51
+ {/if}
52
+ </label>
53
+ {/if}
54
+
55
+ <div
56
+ class="flex flex-wrap items-center gap-6"
57
+ class:opacity-50={disabled}
58
+ class:pointer-events-none={disabled}
59
+ >
60
+ {#each options as option (option.id)}
61
+ <label
62
+ class="relative inline-flex items-center gap-2 text-sm text-[#212529] transition-colors"
63
+ class:active={value === option.id}
64
+ >
65
+ <input
66
+ type="radio"
67
+ class="absolute inset-0 h-full w-full cursor-pointer opacity-0"
68
+ name={id}
69
+ style={customStyle}
70
+ value={option.id}
71
+ checked={value === option.id}
72
+ {disabled}
73
+ on:change={() => onChange?.(option.id)}
74
+ />
75
+ <span
76
+ class="flex h-4 w-4 items-center justify-center rounded-full border bg-white {value ===
77
+ option.id
78
+ ? 'border-4 border-[#ffa000]'
79
+ : 'border-[#ced4da]'}"
80
+ ></span>
81
+ <span>{option.nama}</span>
82
+ </label>
83
+ {/each}
84
+ </div>
85
+
86
+ {#if errorMessage && required}
87
+ <p class="mt-1 text-xs text-red-500">{errorMessage}</p>
88
+ {/if}
89
+ </div>
90
+
91
+ <style>
92
+ .radio-container {
93
+ display: flex;
94
+ flex-direction: column;
95
+ width: 100%;
96
+ }
97
+
98
+ .radio-label {
99
+ font-size: 14px;
100
+ font-weight: 500;
101
+ color: #212529;
102
+ margin-bottom: 8px;
103
+ display: block;
104
+ }
105
+
106
+ .label-subLabel {
107
+ font-weight: 400;
108
+ color: #98a2b3;
109
+ margin-left: 4px;
110
+ }
111
+
112
+ .required-indicator {
113
+ color: #ef4444;
114
+ margin-left: 4px;
115
+ font-weight: 500;
116
+ }
117
+
118
+ .segmented-group {
119
+ background: #fff;
120
+ }
121
+ .segmented-option {
122
+ border-right: 1px solid #ced4da;
123
+ &:last-child {
124
+ border-right: none;
125
+ }
126
+ &.active {
127
+ background: #ffa000;
128
+ color: #fff;
129
+ }
130
+ &:not(.active):hover {
131
+ background: #f2f4f7;
132
+ }
133
+ .text {
134
+ width: 100%;
135
+ text-align: center;
136
+ }
137
+ }
138
+
139
+ .title {
140
+ font-size: 1.125rem;
141
+ font-weight: bold;
142
+ margin-bottom: 1rem;
143
+ }
144
+
145
+ .title.horizontal {
146
+ display: flex;
147
+ align-items: center;
148
+ justify-content: center;
149
+ margin: 0;
150
+ }
151
+
152
+ .title.horizontal.left {
153
+ justify-content: flex-start;
154
+ }
155
+
156
+ .title.horizontal.right {
157
+ justify-content: flex-end;
158
+ }
159
+ </style>
@@ -0,0 +1,17 @@
1
+ import type { IFieldOption } from './Radio.js';
2
+ type $$ComponentProps = {
3
+ id: string;
4
+ value?: string;
5
+ options?: Array<IFieldOption>;
6
+ onChange?: (val: string) => void;
7
+ required?: boolean;
8
+ errorMessage?: string;
9
+ disabled?: boolean;
10
+ label?: string;
11
+ subLabel?: string;
12
+ class?: string;
13
+ style?: string;
14
+ };
15
+ declare const Radio: import("svelte").Component<$$ComponentProps, {}, "">;
16
+ type Radio = ReturnType<typeof Radio>;
17
+ export default Radio;
@@ -0,0 +1,4 @@
1
+ export interface IFieldOption {
2
+ id: string;
3
+ nama: string;
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,153 @@
1
+ <script lang="ts">
2
+ import { twMerge } from 'tailwind-merge';
3
+ import type { IFieldOption } from './Segmented.js';
4
+
5
+ const {
6
+ id,
7
+ value = '',
8
+ options = [],
9
+ onChange,
10
+ required = false,
11
+ errorMessage,
12
+ disabled = false,
13
+ label = '',
14
+ subLabel = '',
15
+ class: className = '',
16
+ style: customStyle = ''
17
+ }: {
18
+ id: string;
19
+ value?: string;
20
+ options?: Array<IFieldOption>;
21
+ onChange?: (val: string) => void;
22
+ required?: boolean;
23
+ errorMessage?: string;
24
+ disabled?: boolean;
25
+ label?: string;
26
+ subLabel?: string;
27
+ class?: string;
28
+ style?: string;
29
+ } = $props();
30
+
31
+ const baseClasses = $derived(() => {
32
+ let classes = 'radio-container';
33
+ if (className) {
34
+ classes += ` ${className}`;
35
+ }
36
+ return classes;
37
+ });
38
+
39
+ const mergedClasses = $derived(twMerge(baseClasses(), className));
40
+ </script>
41
+
42
+ <div class={mergedClasses} style={customStyle}>
43
+ {#if label}
44
+ <label for={id} class="radio-label">
45
+ {label}
46
+ {#if required}
47
+ <span class="required-indicator">*</span>
48
+ {/if}
49
+ {#if subLabel}
50
+ <span class="label-subLabel">{subLabel}</span>
51
+ {/if}
52
+ </label>
53
+ {/if}
54
+
55
+ <div
56
+ class="segmented-group flex overflow-hidden rounded-lg border border-[#ced4da]"
57
+ class:opacity-50={disabled}
58
+ class:pointer-events-none={disabled}
59
+ >
60
+ {#each options as option (option.id)}
61
+ <label
62
+ class="segmented-option relative inline-flex flex-1 cursor-pointer items-center justify-center px-4 py-2 text-sm font-medium text-[#475467] transition-colors"
63
+ class:active={value === option.id}
64
+ >
65
+ <input
66
+ type="radio"
67
+ class="absolute inset-0 h-full w-full cursor-pointer opacity-0"
68
+ name={id}
69
+ style={customStyle}
70
+ value={option.id}
71
+ checked={value === option.id}
72
+ {disabled}
73
+ on:change={() => onChange?.(option.id)}
74
+ />
75
+ <span class="text">{option.nama}</span>
76
+ </label>
77
+ {/each}
78
+ </div>
79
+
80
+ {#if errorMessage && required}
81
+ <p class="mt-1 text-xs text-red-500">{errorMessage}</p>
82
+ {/if}
83
+ </div>
84
+
85
+ <style>
86
+ .radio-container {
87
+ display: flex;
88
+ flex-direction: column;
89
+ width: 100%;
90
+ }
91
+
92
+ .radio-label {
93
+ font-size: 14px;
94
+ font-weight: 500;
95
+ color: #212529;
96
+ margin-bottom: 8px;
97
+ display: block;
98
+ }
99
+
100
+ .label-subLabel {
101
+ font-weight: 400;
102
+ color: #98a2b3;
103
+ margin-left: 4px;
104
+ }
105
+
106
+ .required-indicator {
107
+ color: #ef4444;
108
+ margin-left: 4px;
109
+ font-weight: 500;
110
+ }
111
+
112
+ .segmented-group {
113
+ background: #fff;
114
+ }
115
+ .segmented-option {
116
+ border-right: 1px solid #ced4da;
117
+ &:last-child {
118
+ border-right: none;
119
+ }
120
+ &.active {
121
+ background: #ffa000;
122
+ color: #fff;
123
+ }
124
+ &:not(.active):hover {
125
+ background: #f2f4f7;
126
+ }
127
+ .text {
128
+ width: 100%;
129
+ text-align: center;
130
+ }
131
+ }
132
+
133
+ .title {
134
+ font-size: 1.125rem;
135
+ font-weight: bold;
136
+ margin-bottom: 1rem;
137
+ }
138
+
139
+ .title.horizontal {
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ margin: 0;
144
+ }
145
+
146
+ .title.horizontal.left {
147
+ justify-content: flex-start;
148
+ }
149
+
150
+ .title.horizontal.right {
151
+ justify-content: flex-end;
152
+ }
153
+ </style>
@@ -0,0 +1,17 @@
1
+ import type { IFieldOption } from './Segmented.js';
2
+ type $$ComponentProps = {
3
+ id: string;
4
+ value?: string;
5
+ options?: Array<IFieldOption>;
6
+ onChange?: (val: string) => void;
7
+ required?: boolean;
8
+ errorMessage?: string;
9
+ disabled?: boolean;
10
+ label?: string;
11
+ subLabel?: string;
12
+ class?: string;
13
+ style?: string;
14
+ };
15
+ declare const Segmented: import("svelte").Component<$$ComponentProps, {}, "">;
16
+ type Segmented = ReturnType<typeof Segmented>;
17
+ export default Segmented;
@@ -12,7 +12,7 @@
12
12
  errorMessage,
13
13
  disabled = false,
14
14
  label = '',
15
- subTitle = '',
15
+ subLabel = '',
16
16
  placeholder = 'Pilih',
17
17
  searchPlaceholder = 'Cari',
18
18
  emptyMessage = 'Tidak ada hasil',
@@ -28,7 +28,7 @@
28
28
  errorMessage?: string;
29
29
  disabled?: boolean;
30
30
  label?: string;
31
- subTitle?: string;
31
+ subLabel?: string;
32
32
  placeholder?: string;
33
33
  searchPlaceholder?: string;
34
34
  emptyMessage?: string;
@@ -197,8 +197,8 @@
197
197
  {#if required}
198
198
  <span class="required-indicator">*</span>
199
199
  {/if}
200
- {#if subTitle}
201
- <span class="label-subtitle">{subTitle}</span>
200
+ {#if subLabel}
201
+ <span class="label-subLabel">{subLabel}</span>
202
202
  {/if}
203
203
  </label>
204
204
  {/if}
@@ -299,7 +299,7 @@
299
299
  display: block;
300
300
  }
301
301
 
302
- .label-subtitle {
302
+ .label-subLabel {
303
303
  font-weight: 400;
304
304
  color: #98a2b3;
305
305
  margin-left: 4px;
@@ -391,7 +391,7 @@
391
391
  }
392
392
 
393
393
  .select-option.selected {
394
- color: #f79b09;
394
+ color: #ffa000;
395
395
  }
396
396
 
397
397
  .select-empty {
@@ -8,7 +8,7 @@ type $$ComponentProps = {
8
8
  errorMessage?: string;
9
9
  disabled?: boolean;
10
10
  label?: string;
11
- subTitle?: string;
11
+ subLabel?: string;
12
12
  placeholder?: string;
13
13
  searchPlaceholder?: string;
14
14
  emptyMessage?: string;
@@ -0,0 +1,141 @@
1
+ <script lang="ts">
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ const {
5
+ id,
6
+ value = '0',
7
+ min = 0,
8
+ max = Number.POSITIVE_INFINITY,
9
+ step = 1,
10
+ onChange,
11
+ label = '',
12
+ subLabel = '',
13
+ required = false,
14
+ errorMessage,
15
+ disabled = false,
16
+ class: className = '',
17
+ style: customStyle = ''
18
+ }: {
19
+ id: string;
20
+ value?: string | number;
21
+ min?: number;
22
+ max?: number;
23
+ step?: number;
24
+ onChange?: (val: string) => void;
25
+ label?: string;
26
+ subLabel?: string;
27
+ required?: boolean;
28
+ errorMessage?: string;
29
+ disabled?: boolean;
30
+ class?: string;
31
+ style?: string;
32
+ } = $props();
33
+
34
+ function toNum(v: any) {
35
+ const n = Number(v);
36
+ return isNaN(n) ? 0 : n;
37
+ }
38
+
39
+ function dec() {
40
+ const cur = toNum(value);
41
+ const next = Math.max(min, cur - (step ?? 1));
42
+ if (next !== cur) onChange?.(String(next));
43
+ }
44
+
45
+ function inc() {
46
+ const cur = toNum(value);
47
+ const next = Math.min(max, cur + (step ?? 1));
48
+ if (next !== cur) onChange?.(String(next));
49
+ }
50
+
51
+ const baseClasses = $derived(() => {
52
+ let classes = 'stepper-container';
53
+ if (className) {
54
+ classes += ` ${className}`;
55
+ }
56
+ return classes;
57
+ });
58
+
59
+ const mergedClasses = $derived(twMerge(baseClasses(), className));
60
+ </script>
61
+
62
+ <div class={mergedClasses} style={customStyle}>
63
+ {#if label}
64
+ <label for={id} class="stepper-label">
65
+ {label}
66
+ {#if required}
67
+ <span class="required-indicator">*</span>
68
+ {/if}
69
+ {#if subLabel}
70
+ <span class="label-subLabel">{subLabel}</span>
71
+ {/if}
72
+ </label>
73
+ {/if}
74
+ <div class="flex items-center">
75
+ <button
76
+ type="button"
77
+ class="grid h-9 w-9 place-items-center rounded-l-md border border-[#CED4DA] bg-[#E9ECEF] text-sm font-medium transition hover:bg-[#E9ECEF]"
78
+ on:click={dec}
79
+ style:color={toNum(value) <= (min ?? Number.NEGATIVE_INFINITY) || disabled
80
+ ? '#CED4DA'
81
+ : '#6C757D'}
82
+ {disabled}
83
+ >
84
+ -
85
+ </button>
86
+ <input
87
+ {id}
88
+ type="number"
89
+ class="h-9 w-16 border-t border-r-0 border-b border-l-0 border-[#CED4DA] bg-white pl-3 text-center text-sm font-medium text-[#2C2C30]"
90
+ value={value as any}
91
+ placeholder={''}
92
+ {disabled}
93
+ readonly
94
+ />
95
+ <button
96
+ type="button"
97
+ class="grid h-9 w-9 place-items-center rounded-r-md border border-[#CED4DA] bg-[#E9ECEF] text-sm font-medium text-[#6C757D] transition hover:bg-[#E9ECEF]"
98
+ on:click={inc}
99
+ style:color={toNum(value) >= (max ?? Number.POSITIVE_INFINITY) || disabled
100
+ ? '#CED4DA'
101
+ : '#6C757D'}
102
+ {disabled}
103
+ >
104
+ +
105
+ </button>
106
+ </div>
107
+
108
+ {#if errorMessage && required}
109
+ <p class="error-message">{errorMessage}</p>
110
+ {/if}
111
+ </div>
112
+
113
+ <style>
114
+ .stepper-container {
115
+ display: flex;
116
+ flex-direction: column;
117
+ width: 100%;
118
+ }
119
+ .stepper-label {
120
+ font-size: 14px;
121
+ font-weight: 500;
122
+ color: #212529;
123
+ margin-bottom: 8px;
124
+ display: block;
125
+ }
126
+ .label-subLabel {
127
+ font-weight: 400;
128
+ color: #98a2b3;
129
+ margin-left: 4px;
130
+ }
131
+ .required-indicator {
132
+ color: #ef4444;
133
+ margin-left: 4px;
134
+ font-weight: 500;
135
+ }
136
+ .error-message {
137
+ font-size: 12px;
138
+ color: #ef4444;
139
+ margin-top: 4px;
140
+ }
141
+ </style>
@@ -0,0 +1,18 @@
1
+ type $$ComponentProps = {
2
+ id: string;
3
+ value?: string | number;
4
+ min?: number;
5
+ max?: number;
6
+ step?: number;
7
+ onChange?: (val: string) => void;
8
+ label?: string;
9
+ subLabel?: string;
10
+ required?: boolean;
11
+ errorMessage?: string;
12
+ disabled?: boolean;
13
+ class?: string;
14
+ style?: string;
15
+ };
16
+ declare const Steppers: import("svelte").Component<$$ComponentProps, {}, "">;
17
+ type Steppers = ReturnType<typeof Steppers>;
18
+ export default Steppers;
@@ -10,7 +10,7 @@
10
10
  value = '',
11
11
  placeholder = '',
12
12
  label = '',
13
- subTitle = '',
13
+ subLabel = '',
14
14
  prefix = '',
15
15
  suffix = '',
16
16
  rightIcon,
@@ -29,7 +29,7 @@
29
29
  value?: string | number;
30
30
  placeholder?: string;
31
31
  label?: string;
32
- subTitle?: string;
32
+ subLabel?: string;
33
33
  prefix?: string;
34
34
  suffix?: string;
35
35
  rightIcon?: TIconName;
@@ -114,8 +114,8 @@
114
114
  {#if label}
115
115
  <label for={id} class="input-label">
116
116
  {label}
117
- {#if subTitle}
118
- <span class="label-subtitle">{subTitle}</span>
117
+ {#if subLabel}
118
+ <span class="label-subLabel">{subLabel}</span>
119
119
  {/if}
120
120
  </label>
121
121
  {/if}
@@ -173,7 +173,7 @@
173
173
  display: block;
174
174
  }
175
175
 
176
- .label-subtitle {
176
+ .label-subLabel {
177
177
  font-weight: 400;
178
178
  color: #98a2b3;
179
179
  margin-left: 4px;
@@ -5,7 +5,7 @@ type $$ComponentProps = {
5
5
  value?: string | number;
6
6
  placeholder?: string;
7
7
  label?: string;
8
- subTitle?: string;
8
+ subLabel?: string;
9
9
  prefix?: string;
10
10
  suffix?: string;
11
11
  rightIcon?: TIconName;
@@ -1,3 +1,4 @@
1
+ <!-- TODO Perbaiki border ring color -->
1
2
  <script lang="ts">
2
3
  import { twMerge } from 'tailwind-merge';
3
4
 
@@ -6,7 +7,7 @@
6
7
  value = '',
7
8
  placeholder = '',
8
9
  label = '',
9
- subTitle = '',
10
+ subLabel = '',
10
11
  rows = 4,
11
12
  disabled = false,
12
13
  readOnly = false,
@@ -20,7 +21,7 @@
20
21
  value?: string;
21
22
  placeholder?: string;
22
23
  label?: string;
23
- subTitle?: string;
24
+ subLabel?: string;
24
25
  rows?: number;
25
26
  disabled?: boolean;
26
27
  readOnly?: boolean;
@@ -39,7 +40,7 @@
39
40
  // Base classes untuk textarea
40
41
  const baseClasses = $derived(() => {
41
42
  let classes =
42
- 'w-full py-2.5 text-sm transition focus:outline-none rounded-md text-sm text-[#212529] transition focus:border-[#FFA000] focus:ring-2 focus:ring-[#FFA000]/25 focus:outline-none';
43
+ 'w-full py-2.5 text-sm transition rounded-md text-sm text-[#212529]';
43
44
  return classes;
44
45
  });
45
46
 
@@ -51,15 +52,15 @@
51
52
  {#if label}
52
53
  <label for={id} class="input-label">
53
54
  {label}
54
- {#if subTitle}
55
- <span class="label-subtitle">{subTitle}</span>
55
+ {#if subLabel}
56
+ <span class="label-subLabel">{subLabel}</span>
56
57
  {/if}
57
58
  </label>
58
59
  {/if}
59
60
  <div class="input-wrapper">
60
61
  <textarea
61
62
  {id}
62
- class="px-4 {mergedClasses}"
63
+ class="textarea-input px-4 {mergedClasses}"
63
64
  style={customStyle}
64
65
  {rows}
65
66
  {placeholder}
@@ -89,7 +90,7 @@
89
90
  display: block;
90
91
  }
91
92
 
92
- .label-subtitle {
93
+ .label-subLabel {
93
94
  font-weight: 400;
94
95
  color: #98a2b3;
95
96
  margin-left: 4px;
@@ -107,4 +108,22 @@
107
108
  border-color 0.2s,
108
109
  box-shadow 0.2s;
109
110
  }
111
+
112
+ .input-wrapper:focus-within {
113
+ border-color: #ffa000;
114
+ box-shadow: 0 0 0 2px rgba(255, 160, 0, 0.25);
115
+ outline: none;
116
+ }
117
+
118
+ .textarea-input {
119
+ border: none;
120
+ background: transparent;
121
+ outline: none;
122
+ width: 100%;
123
+ resize: vertical;
124
+ }
125
+
126
+ .textarea-input:focus {
127
+ outline: none;
128
+ }
110
129
  </style>
@@ -3,7 +3,7 @@ type $$ComponentProps = {
3
3
  value?: string;
4
4
  placeholder?: string;
5
5
  label?: string;
6
- subTitle?: string;
6
+ subLabel?: string;
7
7
  rows?: number;
8
8
  disabled?: boolean;
9
9
  readOnly?: boolean;
package/dist/index.d.ts CHANGED
@@ -2,6 +2,10 @@ export { default as Button } from './components/Button/Button.svelte';
2
2
  export { default as Icon } from './components/Icon/Icon.svelte';
3
3
  export { default as TextInput } from './components/inputs/TextInput/TextInput.svelte';
4
4
  export { default as TextareaInput } from './components/inputs/TextareaInput/TextareaInput.svelte';
5
+ export { default as SelectInput } from './components/inputs/SelectInput/SelectInput.svelte';
6
+ export { default as Radio } from './components/inputs/Radio/Radio.svelte';
7
+ export { default as Segmented } from './components/inputs/Segmented/Segmented.svelte';
8
+ export { default as Steppers } from './components/inputs/Steppers/Steppers.svelte';
5
9
  export { default as IMSLayout1 } from './layouts/IMS/IMSLayout1/IMSLayout1.svelte';
6
10
  export { default as Table } from './components/Table/Table.svelte';
7
11
  export { default as HeaderContent } from './components/HeaderContent/HeaderContent.svelte';
package/dist/index.js CHANGED
@@ -3,6 +3,10 @@ export { default as Button } from './components/Button/Button.svelte';
3
3
  export { default as Icon } from './components/Icon/Icon.svelte';
4
4
  export { default as TextInput } from './components/inputs/TextInput/TextInput.svelte';
5
5
  export { default as TextareaInput } from './components/inputs/TextareaInput/TextareaInput.svelte';
6
+ export { default as SelectInput } from './components/inputs/SelectInput/SelectInput.svelte';
7
+ export { default as Radio } from './components/inputs/Radio/Radio.svelte';
8
+ export { default as Segmented } from './components/inputs/Segmented/Segmented.svelte';
9
+ export { default as Steppers } from './components/inputs/Steppers/Steppers.svelte';
6
10
  export { default as IMSLayout1 } from './layouts/IMS/IMSLayout1/IMSLayout1.svelte';
7
11
  export { default as Table } from './components/Table/Table.svelte';
8
12
  export { default as HeaderContent } from './components/HeaderContent/HeaderContent.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mertani-web-toolkit",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "homepage": "https://storybook.mertani.com/",
5
5
  "scripts": {
6
6
  "dev": "vite dev",