podo-ui 0.9.7 → 1.0.2
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.
- package/cdn/podo-datepicker.css +1 -1
- package/cdn/podo-datepicker.js +1 -1
- package/cdn/podo-datepicker.min.css +1 -1
- package/cdn/podo-datepicker.min.js +1 -1
- package/cdn/podo-ui.css +4 -1
- package/cdn/podo-ui.min.css +1 -1
- package/dist/react/atom/editor.d.ts.map +1 -1
- package/dist/react/atom/editor.js +94 -2
- package/dist/svelte/actions/portal.d.ts +18 -0
- package/dist/svelte/actions/portal.js +42 -0
- package/dist/svelte/atom/Avatar.svelte +97 -0
- package/dist/svelte/atom/Avatar.svelte.d.ts +31 -0
- package/dist/svelte/atom/Button.svelte +86 -0
- package/dist/svelte/atom/Button.svelte.d.ts +26 -0
- package/dist/svelte/atom/Checkbox.svelte +56 -0
- package/dist/svelte/atom/Checkbox.svelte.d.ts +16 -0
- package/dist/svelte/atom/Chip.svelte +60 -0
- package/dist/svelte/atom/Chip.svelte.d.ts +25 -0
- package/dist/svelte/atom/Editor.svelte +1314 -0
- package/dist/svelte/atom/Editor.svelte.d.ts +30 -0
- package/dist/svelte/atom/EditorView.svelte +16 -0
- package/dist/svelte/atom/EditorView.svelte.d.ts +9 -0
- package/dist/svelte/atom/File.svelte +33 -0
- package/dist/svelte/atom/File.svelte.d.ts +14 -0
- package/dist/svelte/atom/Input.svelte +80 -0
- package/dist/svelte/atom/Input.svelte.d.ts +19 -0
- package/dist/svelte/atom/Label.svelte +43 -0
- package/dist/svelte/atom/Label.svelte.d.ts +19 -0
- package/dist/svelte/atom/Radio.svelte +69 -0
- package/dist/svelte/atom/Radio.svelte.d.ts +26 -0
- package/dist/svelte/atom/RadioGroup.svelte +46 -0
- package/dist/svelte/atom/RadioGroup.svelte.d.ts +16 -0
- package/dist/svelte/atom/Select.svelte +65 -0
- package/dist/svelte/atom/Select.svelte.d.ts +26 -0
- package/dist/svelte/atom/Textarea.svelte +53 -0
- package/dist/svelte/atom/Textarea.svelte.d.ts +13 -0
- package/dist/svelte/atom/Toggle.svelte +48 -0
- package/dist/svelte/atom/Toggle.svelte.d.ts +14 -0
- package/dist/svelte/atom/Tooltip.svelte +78 -0
- package/dist/svelte/atom/Tooltip.svelte.d.ts +23 -0
- package/dist/svelte/atom/avatar.module.scss +82 -0
- package/dist/svelte/atom/editor-view.module.scss +251 -0
- package/dist/svelte/atom/input.module.scss +98 -0
- package/dist/svelte/atom/textarea.module.scss +17 -0
- package/dist/svelte/atom/tooltip.module.scss +227 -0
- package/dist/svelte/index.d.ts +26 -0
- package/dist/svelte/index.js +30 -0
- package/dist/svelte/molecule/DatePicker.svelte +986 -0
- package/dist/svelte/molecule/DatePicker.svelte.d.ts +71 -0
- package/dist/svelte/molecule/Field.svelte +81 -0
- package/dist/svelte/molecule/Field.svelte.d.ts +26 -0
- package/dist/svelte/molecule/Pagination.svelte +95 -0
- package/dist/svelte/molecule/Pagination.svelte.d.ts +14 -0
- package/dist/svelte/molecule/Tab.svelte +69 -0
- package/dist/svelte/molecule/Tab.svelte.d.ts +26 -0
- package/dist/svelte/molecule/TabPanel.svelte +24 -0
- package/dist/svelte/molecule/TabPanel.svelte.d.ts +14 -0
- package/dist/svelte/molecule/Table.svelte +109 -0
- package/dist/svelte/molecule/Table.svelte.d.ts +54 -0
- package/dist/svelte/molecule/Toast.svelte +111 -0
- package/dist/svelte/molecule/Toast.svelte.d.ts +25 -0
- package/dist/svelte/molecule/ToastProvider.svelte +74 -0
- package/dist/svelte/molecule/ToastProvider.svelte.d.ts +8 -0
- package/dist/svelte/molecule/field.module.scss +22 -0
- package/dist/svelte/molecule/pagination.module.scss +61 -0
- package/dist/svelte/molecule/toast-container.module.scss +70 -0
- package/dist/svelte/molecule/toast.module.scss +12 -0
- package/dist/svelte/stores/toast.d.ts +45 -0
- package/dist/svelte/stores/toast.js +55 -0
- package/dist/svelte/stores/validation.d.ts +15 -0
- package/dist/svelte/stores/validation.js +38 -0
- package/global.scss +1 -0
- package/package.json +32 -5
- package/vite-fonts.scss +1 -1
|
@@ -368,12 +368,104 @@ const Editor = ({ value = '', width = '100%', height = '400px', minHeight, maxHe
|
|
|
368
368
|
}
|
|
369
369
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
370
370
|
}, [onChange, addToHistory]);
|
|
371
|
+
// 이미지를 커서 위치에 삽입하는 함수
|
|
372
|
+
const insertImageAtCursor = useCallback((src, alt = '') => {
|
|
373
|
+
const selection = window.getSelection();
|
|
374
|
+
if (!selection || selection.rangeCount === 0)
|
|
375
|
+
return;
|
|
376
|
+
const range = selection.getRangeAt(0);
|
|
377
|
+
const img = document.createElement('img');
|
|
378
|
+
img.src = src;
|
|
379
|
+
img.alt = alt;
|
|
380
|
+
img.style.maxWidth = '100%';
|
|
381
|
+
range.deleteContents();
|
|
382
|
+
range.insertNode(img);
|
|
383
|
+
// 커서를 이미지 다음으로 이동
|
|
384
|
+
const newRange = document.createRange();
|
|
385
|
+
newRange.setStartAfter(img);
|
|
386
|
+
newRange.collapse(true);
|
|
387
|
+
selection.removeAllRanges();
|
|
388
|
+
selection.addRange(newRange);
|
|
389
|
+
handleInput();
|
|
390
|
+
}, []);
|
|
391
|
+
// 드래그 앤 드롭 이벤트 핸들러
|
|
392
|
+
const handleDragOver = useCallback((e) => {
|
|
393
|
+
e.preventDefault();
|
|
394
|
+
e.stopPropagation();
|
|
395
|
+
}, []);
|
|
396
|
+
const handleDrop = useCallback((e) => {
|
|
397
|
+
e.preventDefault();
|
|
398
|
+
e.stopPropagation();
|
|
399
|
+
const files = e.dataTransfer?.files;
|
|
400
|
+
if (!files || files.length === 0)
|
|
401
|
+
return;
|
|
402
|
+
// 이미지 파일만 처리
|
|
403
|
+
for (let i = 0; i < files.length; i++) {
|
|
404
|
+
const file = files[i];
|
|
405
|
+
if (file.type.startsWith('image/')) {
|
|
406
|
+
const reader = new FileReader();
|
|
407
|
+
reader.onload = (event) => {
|
|
408
|
+
const dataUrl = event.target?.result;
|
|
409
|
+
if (dataUrl) {
|
|
410
|
+
// 드롭 위치에 커서 설정
|
|
411
|
+
if (editorRef.current) {
|
|
412
|
+
editorRef.current.focus();
|
|
413
|
+
}
|
|
414
|
+
insertImageAtCursor(dataUrl, file.name || 'dropped-image');
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
reader.readAsDataURL(file);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}, [insertImageAtCursor]);
|
|
371
421
|
// 붙여넣기 이벤트 핸들러 - 지원하지 않는 스타일 제거
|
|
372
422
|
const handlePaste = useCallback((e) => {
|
|
373
|
-
e.preventDefault();
|
|
374
423
|
const clipboardData = e.clipboardData;
|
|
375
424
|
if (!clipboardData)
|
|
376
425
|
return;
|
|
426
|
+
// 클립보드에서 이미지 파일 확인
|
|
427
|
+
const items = clipboardData.items;
|
|
428
|
+
if (items) {
|
|
429
|
+
for (let i = 0; i < items.length; i++) {
|
|
430
|
+
const item = items[i];
|
|
431
|
+
if (item.type.startsWith('image/')) {
|
|
432
|
+
e.preventDefault();
|
|
433
|
+
const file = item.getAsFile();
|
|
434
|
+
if (file) {
|
|
435
|
+
const reader = new FileReader();
|
|
436
|
+
reader.onload = (event) => {
|
|
437
|
+
const dataUrl = event.target?.result;
|
|
438
|
+
if (dataUrl) {
|
|
439
|
+
insertImageAtCursor(dataUrl, file.name || 'pasted-image');
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
reader.readAsDataURL(file);
|
|
443
|
+
}
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
// 파일로 붙여넣은 경우 (드래그 앤 드롭 등)
|
|
449
|
+
const files = clipboardData.files;
|
|
450
|
+
if (files && files.length > 0) {
|
|
451
|
+
for (let i = 0; i < files.length; i++) {
|
|
452
|
+
const file = files[i];
|
|
453
|
+
if (file.type.startsWith('image/')) {
|
|
454
|
+
e.preventDefault();
|
|
455
|
+
const reader = new FileReader();
|
|
456
|
+
reader.onload = (event) => {
|
|
457
|
+
const dataUrl = event.target?.result;
|
|
458
|
+
if (dataUrl) {
|
|
459
|
+
insertImageAtCursor(dataUrl, file.name || 'pasted-image');
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
reader.readAsDataURL(file);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
// HTML/텍스트 붙여넣기 처리
|
|
468
|
+
e.preventDefault();
|
|
377
469
|
// HTML 데이터 가져오기
|
|
378
470
|
const html = clipboardData.getData('text/html');
|
|
379
471
|
const text = clipboardData.getData('text/plain');
|
|
@@ -2963,7 +3055,7 @@ const Editor = ({ value = '', width = '100%', height = '400px', minHeight, maxHe
|
|
|
2963
3055
|
minHeight: height === 'contents' ? 'auto' : 0,
|
|
2964
3056
|
height: height === 'contents' && savedEditorHeight ? `${savedEditorHeight}px` : undefined,
|
|
2965
3057
|
resize: 'none'
|
|
2966
|
-
}, placeholder: placeholder })) : (_jsx("div", { ref: editorRef, id: editorID, className: styles.editorContent, contentEditable: true, onInput: handleInput, onCompositionStart: handleCompositionStart, onCompositionEnd: handleCompositionEnd, onPaste: handlePaste, onClick: handleEditorClick, onContextMenu: handleEditorContextMenu, onKeyUp: () => {
|
|
3058
|
+
}, placeholder: placeholder })) : (_jsx("div", { ref: editorRef, id: editorID, className: styles.editorContent, contentEditable: true, onInput: handleInput, onCompositionStart: handleCompositionStart, onCompositionEnd: handleCompositionEnd, onPaste: handlePaste, onDragOver: handleDragOver, onDrop: handleDrop, onClick: handleEditorClick, onContextMenu: handleEditorContextMenu, onKeyUp: () => {
|
|
2967
3059
|
detectCurrentParagraphStyle();
|
|
2968
3060
|
detectCurrentAlign();
|
|
2969
3061
|
}, onKeyDown: handleKeyDown, style: {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte action to render element into a different part of the DOM
|
|
3
|
+
* Similar to React's createPortal
|
|
4
|
+
*
|
|
5
|
+
* @param node - The node to portal
|
|
6
|
+
* @param target - Target element or selector (default: 'body')
|
|
7
|
+
* @returns Svelte action object with update and destroy methods
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* <div use:portal={'body'}>
|
|
11
|
+
* This will be rendered in body
|
|
12
|
+
* </div>
|
|
13
|
+
*/
|
|
14
|
+
export declare function portal(node: HTMLElement, target?: HTMLElement | string): {
|
|
15
|
+
update: (newTarget: HTMLElement | string) => void;
|
|
16
|
+
destroy: () => void;
|
|
17
|
+
};
|
|
18
|
+
export default portal;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte action to render element into a different part of the DOM
|
|
3
|
+
* Similar to React's createPortal
|
|
4
|
+
*
|
|
5
|
+
* @param node - The node to portal
|
|
6
|
+
* @param target - Target element or selector (default: 'body')
|
|
7
|
+
* @returns Svelte action object with update and destroy methods
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* <div use:portal={'body'}>
|
|
11
|
+
* This will be rendered in body
|
|
12
|
+
* </div>
|
|
13
|
+
*/
|
|
14
|
+
export function portal(node, target) {
|
|
15
|
+
if (target === void 0) { target = 'body'; }
|
|
16
|
+
var targetEl;
|
|
17
|
+
function update(newTarget) {
|
|
18
|
+
target = newTarget;
|
|
19
|
+
if (typeof target === 'string') {
|
|
20
|
+
targetEl = document.querySelector(target);
|
|
21
|
+
if (!targetEl) {
|
|
22
|
+
throw new Error("No element found matching \"".concat(target, "\""));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
targetEl = target;
|
|
27
|
+
}
|
|
28
|
+
targetEl.appendChild(node);
|
|
29
|
+
node.hidden = false;
|
|
30
|
+
}
|
|
31
|
+
function destroy() {
|
|
32
|
+
if (node.parentNode) {
|
|
33
|
+
node.parentNode.removeChild(node);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
update(target);
|
|
37
|
+
return {
|
|
38
|
+
update: update,
|
|
39
|
+
destroy: destroy,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export default portal;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import styles from './avatar.module.scss';
|
|
3
|
+
|
|
4
|
+
type AvatarType = 'image' | 'icon' | 'text';
|
|
5
|
+
type AvatarSize = 16 | 20 | 24 | 28 | 32 | 36 | 40 | 48 | 56;
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
/**
|
|
9
|
+
* Avatar type
|
|
10
|
+
* - image: Display user uploaded image
|
|
11
|
+
* - icon: Display system provided icon with background
|
|
12
|
+
* - text: Display user name or initials with background
|
|
13
|
+
*/
|
|
14
|
+
type?: AvatarType;
|
|
15
|
+
/** Image source URL (for type='image') */
|
|
16
|
+
src?: string;
|
|
17
|
+
/** Icon class name (for type='icon') */
|
|
18
|
+
icon?: string;
|
|
19
|
+
/** Text content (for type='text') */
|
|
20
|
+
text?: string;
|
|
21
|
+
/** Avatar size in pixels */
|
|
22
|
+
size?: AvatarSize;
|
|
23
|
+
/** Show activity ring */
|
|
24
|
+
activityRing?: boolean;
|
|
25
|
+
/** Additional CSS class names */
|
|
26
|
+
class?: string;
|
|
27
|
+
/** Alt text for image */
|
|
28
|
+
alt?: string;
|
|
29
|
+
/** Click handler */
|
|
30
|
+
onclick?: () => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let {
|
|
34
|
+
type = 'icon',
|
|
35
|
+
src,
|
|
36
|
+
icon = 'icon-user',
|
|
37
|
+
text,
|
|
38
|
+
size = 56,
|
|
39
|
+
activityRing = false,
|
|
40
|
+
class: className = '',
|
|
41
|
+
alt = 'Avatar',
|
|
42
|
+
onclick,
|
|
43
|
+
...rest
|
|
44
|
+
}: Props & Record<string, unknown> = $props();
|
|
45
|
+
|
|
46
|
+
let wrapperClasses = $derived(
|
|
47
|
+
[styles.wrapper, activityRing && styles.activityRing, className]
|
|
48
|
+
.filter(Boolean)
|
|
49
|
+
.join(' ')
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
let avatarClasses = $derived(
|
|
53
|
+
[styles.avatar, styles[`size-${size}`], styles[`type-${type}`]]
|
|
54
|
+
.filter(Boolean)
|
|
55
|
+
.join(' ')
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
let wrapperSize = $derived(activityRing ? size + 10 : size);
|
|
59
|
+
let displayText = $derived(text ? text.slice(0, 2) : '');
|
|
60
|
+
|
|
61
|
+
function getFontSize(contentType: 'icon' | 'text') {
|
|
62
|
+
if (contentType === 'icon') {
|
|
63
|
+
const iconRatio = 0.785;
|
|
64
|
+
return Math.round(size * iconRatio);
|
|
65
|
+
} else {
|
|
66
|
+
const textRatio = 0.43;
|
|
67
|
+
return Math.round(size * textRatio);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<div
|
|
73
|
+
class={wrapperClasses}
|
|
74
|
+
style="width: {wrapperSize}px; height: {wrapperSize}px;"
|
|
75
|
+
onclick={onclick}
|
|
76
|
+
onkeydown={onclick ? (e) => e.key === 'Enter' && onclick() : undefined}
|
|
77
|
+
role={onclick ? 'button' : undefined}
|
|
78
|
+
tabindex={onclick ? 0 : undefined}
|
|
79
|
+
{...rest}
|
|
80
|
+
>
|
|
81
|
+
<div
|
|
82
|
+
class={avatarClasses}
|
|
83
|
+
style="width: {size}px; height: {size}px; font-size: {type === 'text'
|
|
84
|
+
? getFontSize('text')
|
|
85
|
+
: getFontSize('icon')}px;"
|
|
86
|
+
>
|
|
87
|
+
{#if type === 'image' && src}
|
|
88
|
+
<img {src} {alt} class={styles.image} />
|
|
89
|
+
{:else if type === 'icon'}
|
|
90
|
+
<i class={icon} style="font-size: {getFontSize('icon')}px;"></i>
|
|
91
|
+
{:else if type === 'text' && displayText}
|
|
92
|
+
<span style="font-size: {getFontSize('text')}px;">{displayText}</span>
|
|
93
|
+
{:else}
|
|
94
|
+
<i class={icon} style="font-size: {getFontSize('icon')}px;"></i>
|
|
95
|
+
{/if}
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
type AvatarType = 'image' | 'icon' | 'text';
|
|
2
|
+
type AvatarSize = 16 | 20 | 24 | 28 | 32 | 36 | 40 | 48 | 56;
|
|
3
|
+
interface Props {
|
|
4
|
+
/**
|
|
5
|
+
* Avatar type
|
|
6
|
+
* - image: Display user uploaded image
|
|
7
|
+
* - icon: Display system provided icon with background
|
|
8
|
+
* - text: Display user name or initials with background
|
|
9
|
+
*/
|
|
10
|
+
type?: AvatarType;
|
|
11
|
+
/** Image source URL (for type='image') */
|
|
12
|
+
src?: string;
|
|
13
|
+
/** Icon class name (for type='icon') */
|
|
14
|
+
icon?: string;
|
|
15
|
+
/** Text content (for type='text') */
|
|
16
|
+
text?: string;
|
|
17
|
+
/** Avatar size in pixels */
|
|
18
|
+
size?: AvatarSize;
|
|
19
|
+
/** Show activity ring */
|
|
20
|
+
activityRing?: boolean;
|
|
21
|
+
/** Additional CSS class names */
|
|
22
|
+
class?: string;
|
|
23
|
+
/** Alt text for image */
|
|
24
|
+
alt?: string;
|
|
25
|
+
/** Click handler */
|
|
26
|
+
onclick?: () => void;
|
|
27
|
+
}
|
|
28
|
+
type $$ComponentProps = Props & Record<string, unknown>;
|
|
29
|
+
declare const Avatar: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
30
|
+
type Avatar = ReturnType<typeof Avatar>;
|
|
31
|
+
export default Avatar;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
type ButtonTheme =
|
|
6
|
+
| 'default'
|
|
7
|
+
| 'primary'
|
|
8
|
+
| 'default-deep'
|
|
9
|
+
| 'info'
|
|
10
|
+
| 'link'
|
|
11
|
+
| 'success'
|
|
12
|
+
| 'warning'
|
|
13
|
+
| 'danger';
|
|
14
|
+
|
|
15
|
+
type ButtonVariant = 'solid' | 'fill' | 'border' | 'text';
|
|
16
|
+
|
|
17
|
+
type ButtonSize = 'xxs' | 'xs' | 'sm' | 'md' | 'lg';
|
|
18
|
+
|
|
19
|
+
interface Props extends HTMLButtonAttributes {
|
|
20
|
+
/** Theme color */
|
|
21
|
+
theme?: ButtonTheme;
|
|
22
|
+
/** Style variant */
|
|
23
|
+
variant?: ButtonVariant;
|
|
24
|
+
/** Size */
|
|
25
|
+
size?: ButtonSize;
|
|
26
|
+
/** Left icon class name */
|
|
27
|
+
icon?: string;
|
|
28
|
+
/** Right icon class name */
|
|
29
|
+
rightIcon?: string;
|
|
30
|
+
/** Loading state */
|
|
31
|
+
loading?: boolean;
|
|
32
|
+
/** Text alignment */
|
|
33
|
+
textAlign?: 'left' | 'center' | 'right';
|
|
34
|
+
/** Children content */
|
|
35
|
+
children?: Snippet;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let {
|
|
39
|
+
theme = 'default',
|
|
40
|
+
variant = 'solid',
|
|
41
|
+
size = 'sm',
|
|
42
|
+
icon,
|
|
43
|
+
rightIcon,
|
|
44
|
+
loading = false,
|
|
45
|
+
disabled = false,
|
|
46
|
+
textAlign,
|
|
47
|
+
class: className = '',
|
|
48
|
+
children,
|
|
49
|
+
...rest
|
|
50
|
+
}: Props = $props();
|
|
51
|
+
|
|
52
|
+
let buttonClass = $derived(
|
|
53
|
+
[
|
|
54
|
+
theme !== 'default' && theme,
|
|
55
|
+
variant !== 'solid' && variant,
|
|
56
|
+
size !== 'sm' && size,
|
|
57
|
+
textAlign === 'left' && 'text-left',
|
|
58
|
+
textAlign === 'right' && 'text-right',
|
|
59
|
+
className,
|
|
60
|
+
]
|
|
61
|
+
.filter(Boolean)
|
|
62
|
+
.join(' ')
|
|
63
|
+
);
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<button
|
|
67
|
+
class={buttonClass || undefined}
|
|
68
|
+
disabled={disabled || loading}
|
|
69
|
+
aria-busy={loading ? true : undefined}
|
|
70
|
+
aria-disabled={disabled ? true : undefined}
|
|
71
|
+
{...rest}
|
|
72
|
+
>
|
|
73
|
+
{#if loading}
|
|
74
|
+
<i class="icon-loading"></i>
|
|
75
|
+
{:else if icon}
|
|
76
|
+
<i class={icon}></i>
|
|
77
|
+
{/if}
|
|
78
|
+
|
|
79
|
+
{#if children}
|
|
80
|
+
{@render children()}
|
|
81
|
+
{/if}
|
|
82
|
+
|
|
83
|
+
{#if rightIcon && !loading}
|
|
84
|
+
<i class={rightIcon}></i>
|
|
85
|
+
{/if}
|
|
86
|
+
</button>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
3
|
+
type ButtonTheme = 'default' | 'primary' | 'default-deep' | 'info' | 'link' | 'success' | 'warning' | 'danger';
|
|
4
|
+
type ButtonVariant = 'solid' | 'fill' | 'border' | 'text';
|
|
5
|
+
type ButtonSize = 'xxs' | 'xs' | 'sm' | 'md' | 'lg';
|
|
6
|
+
interface Props extends HTMLButtonAttributes {
|
|
7
|
+
/** Theme color */
|
|
8
|
+
theme?: ButtonTheme;
|
|
9
|
+
/** Style variant */
|
|
10
|
+
variant?: ButtonVariant;
|
|
11
|
+
/** Size */
|
|
12
|
+
size?: ButtonSize;
|
|
13
|
+
/** Left icon class name */
|
|
14
|
+
icon?: string;
|
|
15
|
+
/** Right icon class name */
|
|
16
|
+
rightIcon?: string;
|
|
17
|
+
/** Loading state */
|
|
18
|
+
loading?: boolean;
|
|
19
|
+
/** Text alignment */
|
|
20
|
+
textAlign?: 'left' | 'center' | 'right';
|
|
21
|
+
/** Children content */
|
|
22
|
+
children?: Snippet;
|
|
23
|
+
}
|
|
24
|
+
declare const Button: import("svelte").Component<Props, {}, "">;
|
|
25
|
+
type Button = ReturnType<typeof Button>;
|
|
26
|
+
export default Button;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
+
|
|
4
|
+
interface Props extends Omit<HTMLInputAttributes, 'type'> {
|
|
5
|
+
/** Checked state */
|
|
6
|
+
checked?: boolean;
|
|
7
|
+
/** Indeterminate state (for select all pattern) */
|
|
8
|
+
indeterminate?: boolean;
|
|
9
|
+
/** Label text */
|
|
10
|
+
label?: string;
|
|
11
|
+
/** Disabled state */
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
/** Element reference for bind:this */
|
|
14
|
+
element?: HTMLInputElement;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
checked = $bindable(false),
|
|
19
|
+
indeterminate = false,
|
|
20
|
+
label,
|
|
21
|
+
disabled = false,
|
|
22
|
+
class: className,
|
|
23
|
+
element = $bindable(),
|
|
24
|
+
...rest
|
|
25
|
+
}: Props = $props();
|
|
26
|
+
|
|
27
|
+
// Handle indeterminate state (DOM property only, not HTML attribute)
|
|
28
|
+
$effect(() => {
|
|
29
|
+
if (element) {
|
|
30
|
+
element.indeterminate = indeterminate;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
{#if label}
|
|
36
|
+
<label>
|
|
37
|
+
<input
|
|
38
|
+
bind:this={element}
|
|
39
|
+
type="checkbox"
|
|
40
|
+
bind:checked
|
|
41
|
+
{disabled}
|
|
42
|
+
class={className}
|
|
43
|
+
{...rest}
|
|
44
|
+
/>
|
|
45
|
+
<span>{label}</span>
|
|
46
|
+
</label>
|
|
47
|
+
{:else}
|
|
48
|
+
<input
|
|
49
|
+
bind:this={element}
|
|
50
|
+
type="checkbox"
|
|
51
|
+
bind:checked
|
|
52
|
+
{disabled}
|
|
53
|
+
class={className}
|
|
54
|
+
{...rest}
|
|
55
|
+
/>
|
|
56
|
+
{/if}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props extends Omit<HTMLInputAttributes, 'type'> {
|
|
3
|
+
/** Checked state */
|
|
4
|
+
checked?: boolean;
|
|
5
|
+
/** Indeterminate state (for select all pattern) */
|
|
6
|
+
indeterminate?: boolean;
|
|
7
|
+
/** Label text */
|
|
8
|
+
label?: string;
|
|
9
|
+
/** Disabled state */
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
/** Element reference for bind:this */
|
|
12
|
+
element?: HTMLInputElement;
|
|
13
|
+
}
|
|
14
|
+
declare const Checkbox: import("svelte").Component<Props, {}, "element" | "checked">;
|
|
15
|
+
type Checkbox = ReturnType<typeof Checkbox>;
|
|
16
|
+
export default Checkbox;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
type ChipTheme = 'default' | 'blue' | 'green' | 'orange' | 'yellow' | 'red';
|
|
5
|
+
type ChipType = 'default' | 'fill' | 'border';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
/** Chip content */
|
|
9
|
+
children: Snippet;
|
|
10
|
+
/** Theme color */
|
|
11
|
+
theme?: ChipTheme;
|
|
12
|
+
/** Style type */
|
|
13
|
+
type?: ChipType;
|
|
14
|
+
/** Size */
|
|
15
|
+
size?: 'sm' | 'md';
|
|
16
|
+
/** Round corners */
|
|
17
|
+
round?: boolean;
|
|
18
|
+
/** Icon class name */
|
|
19
|
+
icon?: string;
|
|
20
|
+
/** Delete button handler */
|
|
21
|
+
ondelete?: () => void;
|
|
22
|
+
/** Additional class name */
|
|
23
|
+
class?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let {
|
|
27
|
+
children,
|
|
28
|
+
theme = 'default',
|
|
29
|
+
type = 'default',
|
|
30
|
+
size = 'md',
|
|
31
|
+
round = false,
|
|
32
|
+
icon,
|
|
33
|
+
ondelete,
|
|
34
|
+
class: className = '',
|
|
35
|
+
...rest
|
|
36
|
+
}: Props & Record<string, unknown> = $props();
|
|
37
|
+
|
|
38
|
+
let chipClasses = $derived(
|
|
39
|
+
[
|
|
40
|
+
'chip',
|
|
41
|
+
theme !== 'default' && theme,
|
|
42
|
+
type !== 'default' && type,
|
|
43
|
+
size !== 'md' && size,
|
|
44
|
+
round && 'round',
|
|
45
|
+
className,
|
|
46
|
+
]
|
|
47
|
+
.filter(Boolean)
|
|
48
|
+
.join(' ')
|
|
49
|
+
);
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<div class={chipClasses} {...rest}>
|
|
53
|
+
{#if icon}
|
|
54
|
+
<i class="icon {icon}"></i>
|
|
55
|
+
{/if}
|
|
56
|
+
{@render children()}
|
|
57
|
+
{#if ondelete}
|
|
58
|
+
<button aria-label="삭제" onclick={ondelete}></button>
|
|
59
|
+
{/if}
|
|
60
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type ChipTheme = 'default' | 'blue' | 'green' | 'orange' | 'yellow' | 'red';
|
|
3
|
+
type ChipType = 'default' | 'fill' | 'border';
|
|
4
|
+
interface Props {
|
|
5
|
+
/** Chip content */
|
|
6
|
+
children: Snippet;
|
|
7
|
+
/** Theme color */
|
|
8
|
+
theme?: ChipTheme;
|
|
9
|
+
/** Style type */
|
|
10
|
+
type?: ChipType;
|
|
11
|
+
/** Size */
|
|
12
|
+
size?: 'sm' | 'md';
|
|
13
|
+
/** Round corners */
|
|
14
|
+
round?: boolean;
|
|
15
|
+
/** Icon class name */
|
|
16
|
+
icon?: string;
|
|
17
|
+
/** Delete button handler */
|
|
18
|
+
ondelete?: () => void;
|
|
19
|
+
/** Additional class name */
|
|
20
|
+
class?: string;
|
|
21
|
+
}
|
|
22
|
+
type $$ComponentProps = Props & Record<string, unknown>;
|
|
23
|
+
declare const Chip: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
24
|
+
type Chip = ReturnType<typeof Chip>;
|
|
25
|
+
export default Chip;
|