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.
Files changed (74) hide show
  1. package/cdn/podo-datepicker.css +1 -1
  2. package/cdn/podo-datepicker.js +1 -1
  3. package/cdn/podo-datepicker.min.css +1 -1
  4. package/cdn/podo-datepicker.min.js +1 -1
  5. package/cdn/podo-ui.css +4 -1
  6. package/cdn/podo-ui.min.css +1 -1
  7. package/dist/react/atom/editor.d.ts.map +1 -1
  8. package/dist/react/atom/editor.js +94 -2
  9. package/dist/svelte/actions/portal.d.ts +18 -0
  10. package/dist/svelte/actions/portal.js +42 -0
  11. package/dist/svelte/atom/Avatar.svelte +97 -0
  12. package/dist/svelte/atom/Avatar.svelte.d.ts +31 -0
  13. package/dist/svelte/atom/Button.svelte +86 -0
  14. package/dist/svelte/atom/Button.svelte.d.ts +26 -0
  15. package/dist/svelte/atom/Checkbox.svelte +56 -0
  16. package/dist/svelte/atom/Checkbox.svelte.d.ts +16 -0
  17. package/dist/svelte/atom/Chip.svelte +60 -0
  18. package/dist/svelte/atom/Chip.svelte.d.ts +25 -0
  19. package/dist/svelte/atom/Editor.svelte +1314 -0
  20. package/dist/svelte/atom/Editor.svelte.d.ts +30 -0
  21. package/dist/svelte/atom/EditorView.svelte +16 -0
  22. package/dist/svelte/atom/EditorView.svelte.d.ts +9 -0
  23. package/dist/svelte/atom/File.svelte +33 -0
  24. package/dist/svelte/atom/File.svelte.d.ts +14 -0
  25. package/dist/svelte/atom/Input.svelte +80 -0
  26. package/dist/svelte/atom/Input.svelte.d.ts +19 -0
  27. package/dist/svelte/atom/Label.svelte +43 -0
  28. package/dist/svelte/atom/Label.svelte.d.ts +19 -0
  29. package/dist/svelte/atom/Radio.svelte +69 -0
  30. package/dist/svelte/atom/Radio.svelte.d.ts +26 -0
  31. package/dist/svelte/atom/RadioGroup.svelte +46 -0
  32. package/dist/svelte/atom/RadioGroup.svelte.d.ts +16 -0
  33. package/dist/svelte/atom/Select.svelte +65 -0
  34. package/dist/svelte/atom/Select.svelte.d.ts +26 -0
  35. package/dist/svelte/atom/Textarea.svelte +53 -0
  36. package/dist/svelte/atom/Textarea.svelte.d.ts +13 -0
  37. package/dist/svelte/atom/Toggle.svelte +48 -0
  38. package/dist/svelte/atom/Toggle.svelte.d.ts +14 -0
  39. package/dist/svelte/atom/Tooltip.svelte +78 -0
  40. package/dist/svelte/atom/Tooltip.svelte.d.ts +23 -0
  41. package/dist/svelte/atom/avatar.module.scss +82 -0
  42. package/dist/svelte/atom/editor-view.module.scss +251 -0
  43. package/dist/svelte/atom/input.module.scss +98 -0
  44. package/dist/svelte/atom/textarea.module.scss +17 -0
  45. package/dist/svelte/atom/tooltip.module.scss +227 -0
  46. package/dist/svelte/index.d.ts +26 -0
  47. package/dist/svelte/index.js +30 -0
  48. package/dist/svelte/molecule/DatePicker.svelte +986 -0
  49. package/dist/svelte/molecule/DatePicker.svelte.d.ts +71 -0
  50. package/dist/svelte/molecule/Field.svelte +81 -0
  51. package/dist/svelte/molecule/Field.svelte.d.ts +26 -0
  52. package/dist/svelte/molecule/Pagination.svelte +95 -0
  53. package/dist/svelte/molecule/Pagination.svelte.d.ts +14 -0
  54. package/dist/svelte/molecule/Tab.svelte +69 -0
  55. package/dist/svelte/molecule/Tab.svelte.d.ts +26 -0
  56. package/dist/svelte/molecule/TabPanel.svelte +24 -0
  57. package/dist/svelte/molecule/TabPanel.svelte.d.ts +14 -0
  58. package/dist/svelte/molecule/Table.svelte +109 -0
  59. package/dist/svelte/molecule/Table.svelte.d.ts +54 -0
  60. package/dist/svelte/molecule/Toast.svelte +111 -0
  61. package/dist/svelte/molecule/Toast.svelte.d.ts +25 -0
  62. package/dist/svelte/molecule/ToastProvider.svelte +74 -0
  63. package/dist/svelte/molecule/ToastProvider.svelte.d.ts +8 -0
  64. package/dist/svelte/molecule/field.module.scss +22 -0
  65. package/dist/svelte/molecule/pagination.module.scss +61 -0
  66. package/dist/svelte/molecule/toast-container.module.scss +70 -0
  67. package/dist/svelte/molecule/toast.module.scss +12 -0
  68. package/dist/svelte/stores/toast.d.ts +45 -0
  69. package/dist/svelte/stores/toast.js +55 -0
  70. package/dist/svelte/stores/validation.d.ts +15 -0
  71. package/dist/svelte/stores/validation.js +38 -0
  72. package/global.scss +1 -0
  73. package/package.json +32 -5
  74. 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;