lapikit 0.4.2 → 0.4.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.
@@ -4,6 +4,9 @@ export function ripple(el, options = {}) {
4
4
  const rippleContainer = document.createElement('div');
5
5
  addClasses();
6
6
  setOptions(options);
7
+ function isAriaDisabled() {
8
+ return el.getAttribute('aria-disabled') === 'true';
9
+ }
7
10
  function addClasses(center) {
8
11
  const shouldBeCentered = center || options.center;
9
12
  if (!rippleContainer.classList.contains('kit-ripple--effect')) {
@@ -17,7 +20,7 @@ export function ripple(el, options = {}) {
17
20
  }
18
21
  }
19
22
  function setOptions(options) {
20
- if (options.disabled || el.hasAttribute('aria-disabled')) {
23
+ if (options.disabled || isAriaDisabled()) {
21
24
  rippleContainer.remove();
22
25
  }
23
26
  else {
@@ -37,7 +40,7 @@ export function ripple(el, options = {}) {
37
40
  }
38
41
  }
39
42
  function createRipple(e, center) {
40
- if (options.disabled || el.hasAttribute('aria-disabled')) {
43
+ if (options.disabled || isAriaDisabled()) {
41
44
  return;
42
45
  }
43
46
  if (e instanceof KeyboardEvent) {
@@ -0,0 +1 @@
1
+ export * from './ripple.ts';
@@ -0,0 +1 @@
1
+ export * from "./ripple.js";
@@ -0,0 +1,5 @@
1
+ import type { RippleProps } from '../utils/types/index.ts';
2
+ export declare function ripple(el: HTMLElement, options?: RippleProps): {
3
+ destroy(): void;
4
+ update(newOptions: RippleProps): void;
5
+ };
@@ -0,0 +1,96 @@
1
+ const triggerEvents = ['pointerdown', 'touchstart', 'keydown'];
2
+ const cancelEvents = ['mouseleave', 'dragleave', 'touchmove', 'touchcancel', 'pointerup', 'keyup'];
3
+ export function ripple(el, options = {}) {
4
+ const rippleContainer = document.createElement('div');
5
+ addClasses();
6
+ setOptions(options);
7
+ function isAriaDisabled() {
8
+ return el.getAttribute('aria-disabled') === 'true';
9
+ }
10
+ function addClasses(center) {
11
+ const shouldBeCentered = center || options.center;
12
+ if (!rippleContainer.classList.contains('kit-ripple--effect')) {
13
+ rippleContainer.classList.add('kit-ripple--effect');
14
+ }
15
+ if (!shouldBeCentered && rippleContainer.classList.contains('kit-ripple--center')) {
16
+ rippleContainer.classList.remove('kit-ripple--center');
17
+ }
18
+ if (shouldBeCentered) {
19
+ rippleContainer.classList.add('kit-ripple--center');
20
+ }
21
+ }
22
+ function setOptions(options) {
23
+ if (options.disabled || isAriaDisabled()) {
24
+ rippleContainer.remove();
25
+ }
26
+ else {
27
+ el.appendChild(rippleContainer);
28
+ }
29
+ if (options.duration && options.duration < 0) {
30
+ options.duration = undefined;
31
+ }
32
+ if (options.component) {
33
+ rippleContainer.style.setProperty('--system-ripple-radius', `var(--${options.component}-shape)`);
34
+ }
35
+ if (options.color) {
36
+ rippleContainer.style.setProperty('--system-ripple-color', options.color);
37
+ }
38
+ if (options.duration) {
39
+ rippleContainer.style.setProperty('--system-animation-ripple-duration', `${options.duration}ms`);
40
+ }
41
+ }
42
+ function createRipple(e, center) {
43
+ if (options.disabled || isAriaDisabled()) {
44
+ return;
45
+ }
46
+ if (e instanceof KeyboardEvent) {
47
+ if (!['Enter', 'Space'].includes(e.code) || e.repeat) {
48
+ return;
49
+ }
50
+ e.preventDefault();
51
+ const click = new PointerEvent('pointerdown');
52
+ createRipple(click, true);
53
+ return;
54
+ }
55
+ addClasses(center);
56
+ const rect = el.getBoundingClientRect();
57
+ const clientX = window.TouchEvent && e instanceof TouchEvent
58
+ ? e.touches[0].clientX
59
+ : e.clientX;
60
+ const clientY = window.TouchEvent && e instanceof TouchEvent
61
+ ? e.touches[0].clientY
62
+ : e.clientY;
63
+ const x = clientX - rect.left > el.offsetWidth / 2 ? 0 : el.offsetWidth;
64
+ const y = clientY - rect.top > el.offsetHeight / 2 ? 0 : el.offsetHeight;
65
+ const radius = Math.hypot(x - (clientX - rect.left), y - (clientY - rect.top));
66
+ const ripple = document.createElement('div');
67
+ ripple.classList.add('kit-ripple');
68
+ ripple.style.left = `${clientX - rect.left - radius}px`;
69
+ ripple.style.top = `${clientY - rect.top - radius}px`;
70
+ ripple.style.width = ripple.style.height = `${radius * 2}px`;
71
+ rippleContainer.appendChild(ripple);
72
+ function removeRipple() {
73
+ if (ripple === null) {
74
+ return;
75
+ }
76
+ ripple.style.opacity = '0';
77
+ setTimeout(() => {
78
+ ripple.remove();
79
+ }, options.duration || 1000);
80
+ cancelEvents.forEach((event) => el.removeEventListener(event, removeRipple));
81
+ }
82
+ cancelEvents.forEach((event) => el.addEventListener(event, removeRipple, { passive: true }));
83
+ }
84
+ triggerEvents.forEach((event) => el.addEventListener(event, createRipple, { passive: event === 'touchstart' }));
85
+ return {
86
+ destroy() {
87
+ triggerEvents.forEach((event) => {
88
+ el.removeEventListener(event, createRipple);
89
+ });
90
+ },
91
+ update(newOptions) {
92
+ options = newOptions;
93
+ setOptions(newOptions);
94
+ }
95
+ };
96
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-align-end-horizontal-icon lucide-align-end-horizontal"><rect width="6" height="16" x="4" y="2" rx="2"/><rect width="6" height="9" x="14" y="9" rx="2"/><path d="M22 22H2"/></svg>
@@ -1,2 +1,2 @@
1
- const lapikitComponents = ['sheet'];
1
+ const lapikitComponents = ['sheet', 'app', 'btn', 'icon'];
2
2
  export default lapikitComponents;
@@ -0,0 +1,146 @@
1
+ <script lang="ts">
2
+ let { ref = $bindable(), children, ...rest } = $props();
3
+ </script>
4
+
5
+ <div id="kit-app" bind:this={ref} {...rest} class={['kit-application', rest.class]}>
6
+ {@render children?.()}
7
+ </div>
8
+
9
+ <style>
10
+ :root {
11
+ color-scheme: light;
12
+
13
+ --kit-h-neutral: 220;
14
+ --kit-h-success: 145;
15
+ --kit-h-warning: 35;
16
+ --kit-h-danger: 5;
17
+ --kit-h-info: 205;
18
+
19
+ --kit-bg: hsl(0 0% 100%);
20
+ --kit-fg: hsl(222 20% 10%);
21
+ --kit-muted: hsl(220 10% 45%);
22
+
23
+ --kit-surface-1: hsl(0 0% 100%);
24
+ --kit-surface-2: hsl(220 20% 98%);
25
+ --kit-surface-3: hsl(220 18% 94%);
26
+
27
+ --kit-border: hsl(220 16% 88%);
28
+
29
+ --kit-accent: hsl(220 90% 56%);
30
+
31
+ --kit-radius-1: 8px;
32
+ --kit-radius-2: 12px;
33
+ --kit-space-1: 6px;
34
+ --kit-space-2: 10px;
35
+ --kit-space-3: 14px;
36
+
37
+ --kit-focus: hsl(35, 90%, 56%);
38
+
39
+ --kit-disabled-opacity: 0.55;
40
+
41
+ --kit-font:
42
+ ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
43
+ 'Segoe UI Symbol', 'Noto Color Emoji';
44
+ }
45
+
46
+ :root {
47
+ --kit-neutral-bg: color-mix(in oklab, var(--kit-neutral), var(--kit-bg) 85%);
48
+ --kit-neutral-bd: color-mix(in oklab, var(--kit-neutral), var(--kit-border) 70%);
49
+ --kit-neutral-fg: var(--kit-fg);
50
+
51
+ --kit-success-bg: color-mix(in oklab, var(--kit-success), var(--kit-bg) 85%);
52
+ --kit-success-bd: color-mix(in oklab, var(--kit-success), var(--kit-border) 70%);
53
+ --kit-success-fg: var(--kit-fg);
54
+
55
+ --kit-warning-bg: color-mix(in oklab, var(--kit-warning), var(--kit-bg) 85%);
56
+ --kit-warning-bd: color-mix(in oklab, var(--kit-warning), var(--kit-border) 70%);
57
+ --kit-warning-fg: var(--kit-fg);
58
+
59
+ --kit-danger-bg: color-mix(in oklab, var(--kit-danger), var(--kit-bg) 85%);
60
+ --kit-danger-bd: color-mix(in oklab, var(--kit-danger), var(--kit-border) 70%);
61
+ --kit-danger-fg: var(--kit-fg);
62
+
63
+ --kit-info-bg: color-mix(in oklab, var(--kit-info), var(--kit-bg) 85%);
64
+ --kit-info-bd: color-mix(in oklab, var(--kit-info), var(--kit-border) 70%);
65
+ --kit-info-fg: var(--kit-fg);
66
+ }
67
+
68
+ :root {
69
+ --kit-outline-w: 1px;
70
+ --btn-radius: 8px;
71
+
72
+ --kit-btn-gap: 8px;
73
+
74
+ --bg: #111827;
75
+ --fg: #ffffff;
76
+ --bg-hover: #0b1220;
77
+ --border: rgba(255, 255, 255, 0.08);
78
+ --focus: blue;
79
+ --font:
80
+ ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
81
+ 'Segoe UI Symbol', 'Noto Color Emoji';
82
+ }
83
+
84
+ :global(.outline) {
85
+ --container-shape-start-start: var(--btn-radius);
86
+ --container-shape-start-end: var(--btn-radius);
87
+ --container-shape-end-start: var(--btn-radius);
88
+ --container-shape-end-end: var(--btn-radius);
89
+
90
+ border-width: var(--kit-outline-w);
91
+ inset: 0;
92
+ border-style: solid;
93
+ position: absolute;
94
+ box-sizing: border-box;
95
+ border-color: var(--outline-color);
96
+ z-index: 1;
97
+ border-start-start-radius: var(--container-shape-start-start);
98
+ border-start-end-radius: var(--container-shape-start-end);
99
+ border-end-start-radius: var(--container-shape-end-start);
100
+ border-end-end-radius: var(--container-shape-end-end);
101
+ }
102
+
103
+ :global(.kit-ripple) {
104
+ background-color: currentColor;
105
+ opacity: 0.1;
106
+ position: absolute;
107
+ border-radius: 50%;
108
+ pointer-events: none;
109
+ -webkit-transition: 0.6s;
110
+ transition: 0.6s;
111
+ -webkit-animation: animation-l-ripple var(--system-animation-ripple-duration, 0.4s)
112
+ cubic-bezier(0.4, 0, 0.2, 1);
113
+ animation: animation-l-ripple var(--system-animation-ripple-duration, 0.4s)
114
+ cubic-bezier(0.4, 0, 0.2, 1);
115
+ border-radius: var(--system-ripple-radius);
116
+ }
117
+
118
+ :global(.kit-ripple--center) {
119
+ top: 50% !important;
120
+ left: 50% !important;
121
+ translate: -50% -50% !important;
122
+ }
123
+
124
+ :global(.kit-ripple--effect) {
125
+ position: absolute;
126
+ left: 0;
127
+ right: 0;
128
+ top: 0;
129
+ bottom: 0;
130
+ overflow: hidden;
131
+ background: none;
132
+ pointer-events: none;
133
+ z-index: 999;
134
+ border-radius: var(--system-ripple-radius);
135
+ }
136
+
137
+ @keyframes -global-animation-l-ripple {
138
+ from {
139
+ scale: 0;
140
+ }
141
+
142
+ to {
143
+ scale: 1;
144
+ }
145
+ }
146
+ </style>
@@ -0,0 +1,6 @@
1
+ declare const App: import("svelte").Component<{
2
+ ref?: any;
3
+ children: any;
4
+ } & Record<string, any>, {}, "ref">;
5
+ type App = ReturnType<typeof App>;
6
+ export default App;
@@ -0,0 +1,538 @@
1
+ <script lang="ts">
2
+ import { useClassName, useStyles } from '../../utils/index.js';
3
+ import { makeComponentProps } from '../../compiler/mapped-code.js';
4
+ import { ripple } from '../../animations/index.js';
5
+ import type { ButtonProps } from './btn.types.js';
6
+
7
+ let {
8
+ ref = $bindable(),
9
+ is = 'button',
10
+ children,
11
+ class: className = '',
12
+ style: styleAttr = '',
13
+ 's-class': sClass,
14
+ 's-style': sStyle,
15
+ variant = 'filled',
16
+ density = 'default',
17
+ loading,
18
+ active = false,
19
+ size = 'default',
20
+ rounded,
21
+ disabled = false,
22
+ block,
23
+ href,
24
+ input,
25
+ type,
26
+ checked,
27
+ value,
28
+ label,
29
+ wide,
30
+ noRipple,
31
+ load,
32
+ append,
33
+ prepend,
34
+ icon,
35
+ ...rest
36
+ }: ButtonProps = $props();
37
+
38
+ let safeVariant = $derived(
39
+ variant === 'filled' || variant === 'outline' || variant === 'text' || variant === 'link'
40
+ ? variant
41
+ : 'filled'
42
+ );
43
+ let safeSize = $derived(
44
+ size === 'default'
45
+ ? 'md'
46
+ : size === 'xs' || size === 'sm' || size === 'md' || size === 'lg' || size === 'xl'
47
+ ? size
48
+ : 'md'
49
+ );
50
+ let safeDensity = $derived(
51
+ density === 'compact' || density === 'comfortable' || density === 'default'
52
+ ? density
53
+ : 'default'
54
+ );
55
+
56
+ let { classProps, styleProps, restProps } = $derived(
57
+ makeComponentProps(rest as Record<string, unknown>)
58
+ );
59
+
60
+ let componentClass = $derived(
61
+ useClassName({
62
+ baseClass: 'kit-btn',
63
+ className: `${className ?? ''} kit-btn--${safeVariant}`.trim(),
64
+ sClass,
65
+ classProps
66
+ })
67
+ );
68
+
69
+ let componentStyle = $derived(
70
+ useStyles({
71
+ styleAttr,
72
+ sStyle,
73
+ styleProps
74
+ })
75
+ );
76
+
77
+ const isInput = $derived(!!input);
78
+ const isDisabled = $derived(!!disabled);
79
+ const tag = $derived((href && 'a') || (isInput && 'input') || is || 'button');
80
+ const inputWrapperTag = $derived(type === 'checkbox' || type === 'radio' ? 'label' : 'div');
81
+ const resolvedHref = $derived(isDisabled ? undefined : href);
82
+ const resolvedDisabled = $derived((tag === 'button' && isDisabled) || undefined);
83
+
84
+ const resolvedType = $derived(() => {
85
+ if (tag !== 'button') return type;
86
+ return type ?? 'button';
87
+ });
88
+ </script>
89
+
90
+ {#if isInput}
91
+ <svelte:element
92
+ this={inputWrapperTag}
93
+ bind:this={ref}
94
+ class={componentClass}
95
+ style={componentStyle}
96
+ data-size={safeSize}
97
+ data-variant={safeVariant}
98
+ data-loading={loading}
99
+ data-active={active}
100
+ data-disabled={isDisabled}
101
+ data-density={safeDensity}
102
+ data-rounded={rounded}
103
+ aria-busy={disabled}
104
+ aria-disabled={isDisabled || undefined}
105
+ data-block={block}
106
+ data-wide={wide}
107
+ data-icon={icon}
108
+ use:ripple={{
109
+ component: 'btn',
110
+ disabled: noRipple || disabled
111
+ }}
112
+ >
113
+ {#if safeVariant === 'outline'}
114
+ <span class="outline"></span>
115
+ {/if}
116
+ <input
117
+ {...restProps}
118
+ type={type || 'button'}
119
+ {checked}
120
+ {value}
121
+ aria-label={String(label || value)}
122
+ disabled={isDisabled}
123
+ />
124
+ {#if loading}
125
+ <span class="spinner">...</span>
126
+ {/if}
127
+ </svelte:element>
128
+ {:else}
129
+ <svelte:element
130
+ this={tag}
131
+ bind:this={ref}
132
+ class={componentClass}
133
+ style={componentStyle}
134
+ {...restProps}
135
+ type={resolvedType()}
136
+ href={resolvedHref}
137
+ data-size={safeSize}
138
+ data-variant={safeVariant}
139
+ data-loading={loading}
140
+ data-active={active}
141
+ data-disabled={isDisabled}
142
+ data-density={safeDensity}
143
+ data-rounded={rounded}
144
+ disabled={resolvedDisabled}
145
+ aria-busy={disabled}
146
+ aria-disabled={isDisabled || undefined}
147
+ data-block={block}
148
+ data-wide={wide}
149
+ data-icon={icon}
150
+ use:ripple={{
151
+ component: 'btn',
152
+ disabled: noRipple || disabled
153
+ }}
154
+ >
155
+ {#if safeVariant === 'outline'}
156
+ <span class="outline"></span>
157
+ {/if}
158
+
159
+ <span class="kit-btn__inner">
160
+ {#if prepend}
161
+ <span class="kit-btn__prepend">
162
+ {@render prepend?.()}
163
+ </span>
164
+ {/if}
165
+ <span class="kit-btn__content">
166
+ {@render children?.()}
167
+ </span>
168
+ {#if append}
169
+ <span class="kit-btn__append">
170
+ {@render append?.()}
171
+ </span>
172
+ {/if}
173
+ </span>
174
+
175
+ {#if loading}
176
+ <span class="spinner">
177
+ {#if load}
178
+ {@render load?.()}
179
+ {:else}
180
+ ...
181
+ {/if}
182
+ </span>
183
+ {/if}
184
+ </svelte:element>
185
+ {/if}
186
+
187
+ <style>
188
+ input {
189
+ border: 0;
190
+ padding: 0;
191
+ background: transparent;
192
+ color: inherit;
193
+ cursor: pointer;
194
+ }
195
+ /* typography consistency */
196
+ button,
197
+ input,
198
+ select,
199
+ textarea {
200
+ font: inherit;
201
+ color: inherit;
202
+ }
203
+
204
+ /* remove native button styles */
205
+ button {
206
+ appearance: none;
207
+ background: none;
208
+ border: none;
209
+ }
210
+
211
+ .kit-btn {
212
+ --kit-btn-bg: var(--kit-surface-2);
213
+ --kit-btn-fg: var(--kit-fg);
214
+ --kit-btn-bd: var(--kit-border);
215
+ --kit-btn-bg--hover: color-mix(in oklab, var(--kit-btn-bg), var(--kit-fg) 6%);
216
+ --kit-btn-bg--active: color-mix(in oklab, var(--kit-btn-bg), var(--kit-fg) 10%);
217
+ --kit-btn-bd--hover: color-mix(in oklab, var(--kit-btn-bd), var(--kit-fg) 12%);
218
+ --kit-btn-font: var(--kit-font);
219
+ --kit-btn-h-xs: 28px;
220
+ --kit-btn-h-sm: 32px;
221
+ --kit-btn-h-md: 40px;
222
+ --kit-btn-h-lg: 48px;
223
+ --kit-btn-h-xl: 56px;
224
+ --kit-btn-px-xs: 10px;
225
+ --kit-btn-px-sm: 12px;
226
+ --kit-btn-px-md: 16px;
227
+ --kit-btn-px-lg: 20px;
228
+ --kit-btn-px-xl: 24px;
229
+ --btn-density-scale: 1;
230
+ --btn-density-height-scale: 1;
231
+ --btn-radius: 8px;
232
+ --btn-shape: var(--btn-radius);
233
+ --kit-btn-gap: 0.45em;
234
+
235
+ position: relative;
236
+ display: inline-flex;
237
+ box-sizing: border-box;
238
+ align-items: center;
239
+ justify-content: center;
240
+ font-family: var(--kit-btn-font);
241
+ background: var(--btn-bg);
242
+ color: var(--btn-fg);
243
+ height: max(28px, calc(var(--btn-h) * var(--btn-density-height-scale)));
244
+ padding-inline: calc(var(--btn-px) * var(--btn-density-scale));
245
+ border-radius: var(--btn-radius);
246
+ text-decoration: none;
247
+ white-space: nowrap;
248
+ user-select: none;
249
+ cursor: pointer;
250
+ border: 0;
251
+ transition:
252
+ background 150ms ease,
253
+ transform 50ms ease;
254
+ }
255
+
256
+ .kit-btn:hover {
257
+ background: var(--btn-hover-bg);
258
+ color: var(--btn-hover-fg);
259
+ text-decoration: var(--btn-decoration);
260
+ }
261
+
262
+ .kit-btn:has(> :is(input[type='checkbox'], input[type='radio']):checked) {
263
+ background: var(--btn-active-bg);
264
+ color: var(--btn-active-fg);
265
+ text-decoration: var(--btn-decoration);
266
+ }
267
+
268
+ .kit-btn:has(> :is(input[type='checkbox'], input[type='radio']):checked):hover {
269
+ background: var(--btn-hover-bg);
270
+ color: var(--btn-hover-fg);
271
+ text-decoration: var(--btn-decoration);
272
+ }
273
+
274
+ .kit-btn:active,
275
+ .kit-btn[data-active='true'] {
276
+ background: var(--btn-active-bg);
277
+ color: var(--btn-active-fg);
278
+ text-decoration: var(--btn-decoration);
279
+ transform: translateY(1px);
280
+ }
281
+
282
+ .kit-btn[data-active='true']:hover {
283
+ background: var(--btn-hover-bg);
284
+ color: var(--btn-hover-fg);
285
+ text-decoration: var(--btn-decoration);
286
+ }
287
+
288
+ :is(.kit-btn:focus-visible, .kit-btn:has(> input:focus-visible)) {
289
+ outline: 2px solid var(--kit-focus);
290
+ outline-offset: 2px;
291
+ }
292
+
293
+ .kit-btn > input:focus-visible {
294
+ outline: none;
295
+ box-shadow: none;
296
+ }
297
+
298
+ .kit-btn > :is(input[type='checkbox'], input[type='radio']) {
299
+ appearance: none;
300
+ margin: 0;
301
+ cursor: pointer;
302
+ }
303
+
304
+ .kit-btn > :is(input[type='checkbox'], input[type='radio']):after {
305
+ --btn-content: attr(aria-label);
306
+ content: var(--btn-content);
307
+ cursor: pointer;
308
+ }
309
+
310
+ /* variant */
311
+ .kit-btn[data-variant='filled'] {
312
+ /* default style*/
313
+ --btn-bg: var(--kit-accent);
314
+ --btn-fg: white;
315
+ --btn-hover-bg: color-mix(in oklab, var(--kit-accent), black 10%);
316
+ --btn-hover-fg: var(--btn-fg);
317
+ --btn-active-bg: color-mix(in oklab, var(--kit-accent), black 16%);
318
+ --btn-active-fg: var(--btn-fg);
319
+ }
320
+
321
+ .kit-btn[data-variant='outline'] {
322
+ --outline-color: var(--kit-accent);
323
+ }
324
+
325
+ .kit-btn[data-variant='outline'],
326
+ .kit-btn[data-variant='text'] {
327
+ --btn-bg: transparent;
328
+ --btn-fg: var(--kit-accent);
329
+ --btn-hover-bg: color-mix(in oklab, var(--kit-accent), transparent 80%);
330
+ --btn-hover-fg: var(--btn-fg);
331
+ --btn-active-bg: color-mix(in oklab, var(--kit-accent), transparent 92%);
332
+ --btn-active-fg: var(--btn-fg);
333
+ }
334
+
335
+ .kit-btn[data-variant='link'] {
336
+ --btn-bg: transparent;
337
+ --btn-fg: var(--kit-accent);
338
+ --btn-hover-fg: var(--btn-fg);
339
+ --btn-active-fg: var(--btn-fg);
340
+ --btn-decoration: underline;
341
+ height: inherit;
342
+ padding: 0;
343
+ }
344
+
345
+ /* size */
346
+ .kit-btn[data-size='xs'] {
347
+ --btn-h: var(--kit-btn-h-xs);
348
+ --btn-height: var(--kit-btn-h-xs);
349
+ --btn-px: var(--kit-btn-px-xs);
350
+ font-size: 12px;
351
+ }
352
+ .kit-btn[data-size='xs'] :global(.kit-icon:not([data-size])) {
353
+ --kit-icon-current-size: var(--kit-icon-size-xs);
354
+ }
355
+
356
+ .kit-btn[data-size='sm'] {
357
+ --btn-h: var(--kit-btn-h-sm);
358
+ --btn-height: var(--kit-btn-h-sm);
359
+ --btn-px: var(--kit-btn-px-sm);
360
+ font-size: 13px;
361
+ }
362
+ .kit-btn[data-size='sm'] :global(.kit-icon:not([data-size])) {
363
+ --kit-icon-current-size: var(--kit-icon-size-sm);
364
+ }
365
+
366
+ .kit-btn[data-size='md'] {
367
+ --btn-h: var(--kit-btn-h-md);
368
+ --btn-height: var(--kit-btn-h-md);
369
+ --btn-px: var(--kit-btn-px-md);
370
+ font-size: 14px;
371
+ }
372
+ .kit-btn[data-size='md'] :global(.kit-icon:not([data-size])) {
373
+ --kit-icon-current-size: var(--kit-icon-size-md);
374
+ }
375
+
376
+ .kit-btn[data-size='lg'] {
377
+ --btn-h: var(--kit-btn-h-lg);
378
+ --btn-height: var(--kit-btn-h-lg);
379
+ --btn-px: var(--kit-btn-px-lg);
380
+ font-size: 15px;
381
+ }
382
+ .kit-btn[data-size='lg'] :global(.kit-icon:not([data-size])) {
383
+ --kit-icon-current-size: var(--kit-icon-size-lg);
384
+ }
385
+
386
+ .kit-btn[data-size='xl'] {
387
+ --btn-h: var(--kit-btn-h-xl);
388
+ --btn-height: var(--kit-btn-h-xl);
389
+ --btn-px: var(--kit-btn-px-xl);
390
+ font-size: 16px;
391
+ }
392
+ .kit-btn[data-size='xl'] :global(.kit-icon:not([data-size])) {
393
+ --kit-icon-current-size: var(--kit-icon-size-xl);
394
+ }
395
+
396
+ /* density */
397
+ .kit-btn[data-density='default'] {
398
+ --btn-density-scale: 1;
399
+ --btn-density-height-scale: 1;
400
+ }
401
+
402
+ .kit-btn[data-density='compact'] {
403
+ --btn-density-scale: 0.9;
404
+ --btn-density-height-scale: 0.92;
405
+ }
406
+
407
+ .kit-btn[data-density='comfortable'] {
408
+ --btn-density-scale: 1.1;
409
+ --btn-density-height-scale: 1.08;
410
+ }
411
+
412
+ /* rounded */
413
+ .kit-btn[data-rounded='0'] {
414
+ --btn-radius: 0;
415
+ }
416
+
417
+ .kit-btn[data-rounded='xs'] {
418
+ --btn-radius: 2px;
419
+ }
420
+
421
+ .kit-btn[data-rounded='sm'] {
422
+ --btn-radius: 4px;
423
+ }
424
+
425
+ .kit-btn[data-rounded='md'] {
426
+ --btn-radius: 8px;
427
+ }
428
+
429
+ .kit-btn[data-rounded='lg'] {
430
+ --btn-radius: 16px;
431
+ }
432
+
433
+ .kit-btn[data-rounded='xl'] {
434
+ --btn-radius: 99999px;
435
+ }
436
+
437
+ .kit-btn[data-wide='true'] {
438
+ width: 100%;
439
+ max-width: 16rem;
440
+ }
441
+
442
+ .kit-btn .outline {
443
+ pointer-events: none;
444
+ }
445
+
446
+ .kit-btn__inner {
447
+ display: inline-flex;
448
+ align-items: center;
449
+ justify-content: center;
450
+ gap: var(--kit-btn-gap);
451
+ line-height: 1;
452
+ }
453
+
454
+ .kit-btn__content,
455
+ .kit-btn__prepend,
456
+ .kit-btn__append {
457
+ display: inline-flex;
458
+ align-items: center;
459
+ justify-content: center;
460
+ line-height: 1;
461
+ }
462
+
463
+ .kit-btn__content :global(.kit-icon),
464
+ .kit-btn__prepend :global(.kit-icon),
465
+ .kit-btn__append :global(.kit-icon) {
466
+ display: inline-flex;
467
+ align-items: center;
468
+ justify-content: center;
469
+ vertical-align: middle;
470
+ }
471
+
472
+ .kit-btn__content :global(svg),
473
+ .kit-btn__prepend :global(svg),
474
+ .kit-btn__append :global(svg),
475
+ .kit-btn__content :global(img),
476
+ .kit-btn__prepend :global(img),
477
+ .kit-btn__append :global(img) {
478
+ display: block;
479
+ }
480
+
481
+ .kit-btn[data-loading='true'] .kit-btn__inner,
482
+ .kit-btn[data-loading='true'] input {
483
+ opacity: 0;
484
+ }
485
+
486
+ .kit-btn[data-loading='true'] {
487
+ pointer-events: none;
488
+ user-select: none;
489
+ cursor: pointer;
490
+ }
491
+
492
+ .spinner {
493
+ position: absolute;
494
+ inset: 0;
495
+ display: flex;
496
+ align-items: center;
497
+ justify-content: center;
498
+ }
499
+
500
+ .kit-btn[data-block='true'] {
501
+ display: flex;
502
+ width: 100%;
503
+ }
504
+
505
+ .kit-btn[data-icon='true'] {
506
+ width: max(28px, calc(var(--btn-h) * var(--btn-density-height-scale)));
507
+ min-width: max(28px, calc(var(--btn-h) * var(--btn-density-height-scale)));
508
+ padding-inline: 0;
509
+ }
510
+
511
+ .kit-btn[data-icon='true'] .kit-btn__inner {
512
+ gap: 0;
513
+ }
514
+
515
+ .kit-btn[data-disabled='true'] {
516
+ pointer-events: none;
517
+ background: color-mix(in oklab, var(--btn-bg), transparent 70%);
518
+ color: color-mix(in oklab, var(--btn-fg), transparent 45%);
519
+
520
+ user-select: none;
521
+ cursor: not-allowed;
522
+ }
523
+
524
+ .kit-btn[data-disabled='true'] :global(.kit-icon) {
525
+ color: color-mix(in oklab, var(--btn-fg), transparent 45%) !important;
526
+ --kit-icon-color: color-mix(in oklab, var(--btn-fg), transparent 45%) !important;
527
+ }
528
+
529
+ .kit-btn[data-disabled='true'] :global(.kit-icon img),
530
+ .kit-btn[data-disabled='true'] :global(.kit-icon .kit-icon__mask) {
531
+ opacity: 0.7;
532
+ filter: grayscale(0.2);
533
+ }
534
+
535
+ .kit-btn[data-disabled='true'] > input {
536
+ cursor: not-allowed;
537
+ }
538
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { ButtonProps } from './btn.types.ts';
2
+ declare const Btn: import("svelte").Component<ButtonProps, {}, "ref">;
3
+ type Btn = ReturnType<typeof Btn>;
4
+ export default Btn;
@@ -0,0 +1,26 @@
1
+ import type { Component, RoundedType, SizeType } from '../../utils/types/index.js';
2
+ import type { Snippet } from 'svelte';
3
+ export interface ButtonProps extends Component {
4
+ ref?: HTMLElement | null;
5
+ is?: 'button' | 'a' | 'input';
6
+ input?: boolean;
7
+ href?: string;
8
+ variant?: 'outline' | 'text' | 'filled' | 'link';
9
+ density?: 'compact' | 'comfortable' | 'default';
10
+ active?: boolean;
11
+ loading?: boolean;
12
+ disabled?: boolean;
13
+ checked?: boolean;
14
+ size?: SizeType;
15
+ rounded?: RoundedType;
16
+ type?: 'button' | 'submit' | 'reset' | 'checkbox' | 'radio';
17
+ value?: string | number | boolean;
18
+ label?: string;
19
+ block?: boolean;
20
+ wide?: boolean;
21
+ noRipple?: boolean;
22
+ icon?: boolean;
23
+ load?: Snippet;
24
+ append?: Snippet;
25
+ prepend?: Snippet;
26
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,186 @@
1
+ <script lang="ts">
2
+ import { useClassName, useStyles } from '../../utils/index.js';
3
+ import { makeComponentProps } from '../../compiler/mapped-code.js';
4
+ import type { IconProps } from './icon.types.js';
5
+
6
+ let {
7
+ ref = $bindable(),
8
+ is = 'i',
9
+ children = undefined,
10
+ class: className = '',
11
+ style: styleAttr = '',
12
+ 's-class': sClass,
13
+ 's-style': sStyle,
14
+ name = undefined,
15
+ src = undefined,
16
+ icon = undefined,
17
+ size = 'default',
18
+ alt = '',
19
+ label = undefined,
20
+ decorative = true,
21
+ loading = 'eager',
22
+ decoding = 'async',
23
+ color = undefined,
24
+ colorMode = 'auto',
25
+ imgFilter = undefined,
26
+ ...rest
27
+ }: IconProps = $props();
28
+
29
+ let { classProps, styleProps, restProps } = $derived(
30
+ makeComponentProps(rest as Record<string, unknown>)
31
+ );
32
+
33
+ let componentClass = $derived(
34
+ useClassName({
35
+ baseClass: 'kit-icon',
36
+ className: `${className ?? ''}`.trim(),
37
+ sClass,
38
+ classProps
39
+ })
40
+ );
41
+
42
+ let componentStyle = $derived(
43
+ useStyles({
44
+ styleAttr,
45
+ sStyle,
46
+ styleProps
47
+ })
48
+ );
49
+
50
+ let safeSize = $derived(
51
+ size === 'default' || size === undefined
52
+ ? undefined
53
+ : size === 'xs' || size === 'sm' || size === 'md' || size === 'lg' || size === 'xl'
54
+ ? size
55
+ : 'md'
56
+ );
57
+
58
+ let resolvedSrc = $derived(src ?? (icon?.includes('/') ? icon : undefined));
59
+ let resolvedName = $derived(name ?? (!icon?.includes('/') ? icon : undefined));
60
+ let hasChildren = $derived(!!children);
61
+ let hasContent = $derived(hasChildren || !!resolvedSrc || !!resolvedName);
62
+ let classWithIconName = $derived(
63
+ !hasChildren && !resolvedSrc && resolvedName
64
+ ? `${componentClass} ${resolvedName}`.trim()
65
+ : componentClass
66
+ );
67
+
68
+ let safeLoading = $derived(loading === 'lazy' || loading === 'eager' ? loading : 'eager');
69
+ let safeDecoding = $derived(
70
+ decoding === 'async' || decoding === 'sync' || decoding === 'auto' ? decoding : 'async'
71
+ );
72
+ let safeColorMode = $derived(
73
+ colorMode === 'auto' || colorMode === 'none' || colorMode === 'mask' || colorMode === 'filter'
74
+ ? colorMode
75
+ : 'auto'
76
+ );
77
+
78
+ let iconRole = $derived(!decorative && (label || alt) ? 'img' : undefined);
79
+ let iconAriaLabel = $derived(!decorative ? label || alt || undefined : undefined);
80
+ let iconAriaHidden = $derived(decorative ? true : undefined);
81
+ let imgAlt = $derived(decorative ? '' : alt || label || '');
82
+ let isSvgSrc = $derived(!!resolvedSrc && /\.svg($|\?)/i.test(resolvedSrc));
83
+ let useMaskMode = $derived(
84
+ !!resolvedSrc &&
85
+ (color ? safeColorMode === 'mask' || (safeColorMode === 'auto' && isSvgSrc) : false)
86
+ );
87
+ let useFilterMode = $derived(
88
+ !!resolvedSrc && !!imgFilter && (safeColorMode === 'filter' || safeColorMode === 'auto')
89
+ );
90
+ let mergedStyle = $derived(
91
+ [componentStyle, color ? `color:${color}` : '', color ? `--kit-icon-color:${color}` : '']
92
+ .filter(Boolean)
93
+ .join('; ')
94
+ );
95
+ </script>
96
+
97
+ {#if hasContent}
98
+ <svelte:element
99
+ this={is}
100
+ bind:this={ref}
101
+ class={classWithIconName}
102
+ style={mergedStyle}
103
+ data-size={safeSize}
104
+ role={iconRole}
105
+ aria-label={iconAriaLabel}
106
+ aria-hidden={iconAriaHidden}
107
+ {...restProps}
108
+ >
109
+ {#if hasChildren}
110
+ {@render children?.()}
111
+ {:else if resolvedSrc}
112
+ {#if useMaskMode}
113
+ <span
114
+ class="kit-icon__mask"
115
+ style={`--kit-icon-mask-image: url("${resolvedSrc}")`}
116
+ aria-hidden="true"
117
+ ></span>
118
+ {:else}
119
+ <img
120
+ src={resolvedSrc}
121
+ alt={imgAlt}
122
+ loading={safeLoading}
123
+ decoding={safeDecoding}
124
+ style={useFilterMode ? `filter:${imgFilter}` : undefined}
125
+ />
126
+ {/if}
127
+ {/if}
128
+ </svelte:element>
129
+ {/if}
130
+
131
+ <style>
132
+ .kit-icon {
133
+ --kit-icon-size-xs: 0.875rem;
134
+ --kit-icon-size-sm: 1rem;
135
+ --kit-icon-size-md: 1.125rem;
136
+ --kit-icon-size-lg: 1.25rem;
137
+ --kit-icon-size-xl: 1.375rem;
138
+ --kit-icon-current-size: var(--kit-icon-size-md);
139
+
140
+ display: inline-flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ text-indent: 0;
144
+ line-height: 1;
145
+ font-size: var(--kit-icon-current-size);
146
+ vertical-align: middle;
147
+ }
148
+
149
+ .kit-icon[data-size='xs'] {
150
+ --kit-icon-current-size: var(--kit-icon-size-xs);
151
+ }
152
+ .kit-icon[data-size='sm'] {
153
+ --kit-icon-current-size: var(--kit-icon-size-sm);
154
+ }
155
+ .kit-icon[data-size='md'] {
156
+ --kit-icon-current-size: var(--kit-icon-size-md);
157
+ }
158
+ .kit-icon[data-size='lg'] {
159
+ --kit-icon-current-size: var(--kit-icon-size-lg);
160
+ }
161
+ .kit-icon[data-size='xl'] {
162
+ --kit-icon-current-size: var(--kit-icon-size-xl);
163
+ }
164
+
165
+ .kit-icon :global(svg),
166
+ .kit-icon img,
167
+ .kit-icon .kit-icon__mask {
168
+ width: var(--kit-icon-current-size);
169
+ height: var(--kit-icon-current-size);
170
+ flex-shrink: 0;
171
+ display: block;
172
+ }
173
+
174
+ .kit-icon .kit-icon__mask {
175
+ display: inline-block;
176
+ background-color: var(--kit-icon-color, currentColor);
177
+ mask-image: var(--kit-icon-mask-image);
178
+ mask-repeat: no-repeat;
179
+ mask-size: contain;
180
+ mask-position: center;
181
+ -webkit-mask-image: var(--kit-icon-mask-image);
182
+ -webkit-mask-repeat: no-repeat;
183
+ -webkit-mask-size: contain;
184
+ -webkit-mask-position: center;
185
+ }
186
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { IconProps } from './icon.types.ts';
2
+ declare const Icon: import("svelte").Component<IconProps, {}, "ref">;
3
+ type Icon = ReturnType<typeof Icon>;
4
+ export default Icon;
@@ -0,0 +1,16 @@
1
+ import type { Component, SizeType } from '../../utils/types/index.js';
2
+ export interface IconProps extends Component {
3
+ ref?: HTMLElement | null;
4
+ name?: string;
5
+ src?: string;
6
+ icon?: string;
7
+ size?: SizeType;
8
+ alt?: string;
9
+ label?: string;
10
+ decorative?: boolean;
11
+ loading?: 'eager' | 'lazy';
12
+ decoding?: 'sync' | 'async' | 'auto';
13
+ color?: string;
14
+ colorMode?: 'auto' | 'none' | 'mask' | 'filter';
15
+ imgFilter?: string;
16
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1 +1,4 @@
1
1
  export { default as KitSheet } from './sheet/sheet.svelte';
2
+ export { default as KitApp } from './app/app.svelte';
3
+ export { default as KitBtn } from './btn/btn.svelte';
4
+ export { default as KitIcon } from './icon/icon.svelte';
@@ -1,2 +1,5 @@
1
1
  // components
2
2
  export { default as KitSheet } from './sheet/sheet.svelte';
3
+ export { default as KitApp } from './app/app.svelte';
4
+ export { default as KitBtn } from './btn/btn.svelte';
5
+ export { default as KitIcon } from './icon/icon.svelte';
@@ -1,5 +1,13 @@
1
+ import type { Snippet } from 'svelte';
1
2
  import type { SClassProp, SStyleProp } from '../../compiler/types/index.js';
3
+ type IdElementType = string | undefined;
4
+ type ClassNameType = string | string[] | undefined;
5
+ type KitClassNameType = string | string[] | undefined;
6
+ type KitStylePropertiesType = Record<string, boolean | string> | undefined;
7
+ type StylePropertiesType = string | undefined;
2
8
  export type PropValue = string | boolean | number | null | undefined;
9
+ export type SizeType = 'default' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
10
+ export type RoundedType = 0 | '0' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
3
11
  export interface useClassNameProps {
4
12
  baseClass?: string;
5
13
  className?: string;
@@ -11,3 +19,22 @@ export interface useStylesProps {
11
19
  sStyle?: SStyleProp;
12
20
  styleProps?: Record<string, PropValue>;
13
21
  }
22
+ export interface Base {
23
+ id?: IdElementType;
24
+ class?: ClassNameType;
25
+ style?: StylePropertiesType;
26
+ 's-class'?: KitClassNameType;
27
+ 's-style'?: KitStylePropertiesType;
28
+ [key: string]: any;
29
+ }
30
+ export interface Component extends Base {
31
+ children?: Snippet;
32
+ }
33
+ export interface RippleProps {
34
+ component?: string;
35
+ center?: boolean;
36
+ color?: string;
37
+ duration?: number;
38
+ disabled?: boolean;
39
+ }
40
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lapikit",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -89,10 +89,12 @@
89
89
  "devDependencies": {
90
90
  "@eslint/compat": "^1.2.5",
91
91
  "@eslint/js": "^9.18.0",
92
+ "@mdi/js": "^7.4.47",
92
93
  "@sveltejs/adapter-auto": "^4.0.0",
93
94
  "@sveltejs/kit": "^2.16.0",
94
95
  "@sveltejs/package": "^2.0.0",
95
96
  "@sveltejs/vite-plugin-svelte": "^6.2.1",
97
+ "@tabler/icons-svelte": "^3.40.0",
96
98
  "@testing-library/jest-dom": "^6.6.3",
97
99
  "@testing-library/svelte": "^5.2.4",
98
100
  "eslint": "^9.18.0",
@@ -100,6 +102,8 @@
100
102
  "eslint-plugin-svelte": "^3.0.0",
101
103
  "globals": "^16.0.0",
102
104
  "jsdom": "^26.0.0",
105
+ "lucide-svelte": "^0.577.0",
106
+ "mingcute_icon": "^2.9.71",
103
107
  "prettier": "^3.4.2",
104
108
  "prettier-plugin-svelte": "^3.3.3",
105
109
  "publint": "^0.3.2",