@aspect-ops/exon-ui 0.0.1

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 (106) hide show
  1. package/dist/components/Badge/Badge.svelte +60 -0
  2. package/dist/components/Badge/Badge.svelte.d.ts +9 -0
  3. package/dist/components/Badge/index.d.ts +2 -0
  4. package/dist/components/Badge/index.js +1 -0
  5. package/dist/components/BottomNav/BottomNav.svelte +128 -0
  6. package/dist/components/BottomNav/BottomNav.svelte.d.ts +8 -0
  7. package/dist/components/BottomNav/BottomNavItem.svelte +133 -0
  8. package/dist/components/BottomNav/BottomNavItem.svelte.d.ts +8 -0
  9. package/dist/components/Breadcrumbs/BreadcrumbItem.svelte +65 -0
  10. package/dist/components/Breadcrumbs/BreadcrumbItem.svelte.d.ts +7 -0
  11. package/dist/components/Breadcrumbs/BreadcrumbSeparator.svelte +27 -0
  12. package/dist/components/Breadcrumbs/BreadcrumbSeparator.svelte.d.ts +7 -0
  13. package/dist/components/Breadcrumbs/Breadcrumbs.svelte +114 -0
  14. package/dist/components/Breadcrumbs/Breadcrumbs.svelte.d.ts +8 -0
  15. package/dist/components/Button/Button.svelte +209 -0
  16. package/dist/components/Button/Button.svelte.d.ts +14 -0
  17. package/dist/components/Button/index.d.ts +2 -0
  18. package/dist/components/Button/index.js +1 -0
  19. package/dist/components/Checkbox/Checkbox.svelte +166 -0
  20. package/dist/components/Checkbox/Checkbox.svelte.d.ts +16 -0
  21. package/dist/components/Checkbox/CheckboxGroup.svelte +55 -0
  22. package/dist/components/Checkbox/CheckboxGroup.svelte.d.ts +12 -0
  23. package/dist/components/Checkbox/index.d.ts +2 -0
  24. package/dist/components/Checkbox/index.js +2 -0
  25. package/dist/components/FormField/FormField.svelte +101 -0
  26. package/dist/components/FormField/FormField.svelte.d.ts +13 -0
  27. package/dist/components/FormField/index.d.ts +1 -0
  28. package/dist/components/FormField/index.js +1 -0
  29. package/dist/components/Icon/Icon.svelte +47 -0
  30. package/dist/components/Icon/Icon.svelte.d.ts +11 -0
  31. package/dist/components/Icon/index.d.ts +2 -0
  32. package/dist/components/Icon/index.js +1 -0
  33. package/dist/components/Link/Link.svelte +56 -0
  34. package/dist/components/Link/Link.svelte.d.ts +9 -0
  35. package/dist/components/Link/index.d.ts +2 -0
  36. package/dist/components/Link/index.js +1 -0
  37. package/dist/components/Menu/Menu.svelte +14 -0
  38. package/dist/components/Menu/Menu.svelte.d.ts +7 -0
  39. package/dist/components/Menu/MenuContent.svelte +71 -0
  40. package/dist/components/Menu/MenuContent.svelte.d.ts +10 -0
  41. package/dist/components/Menu/MenuItem.svelte +73 -0
  42. package/dist/components/Menu/MenuItem.svelte.d.ts +7 -0
  43. package/dist/components/Menu/MenuSeparator.svelte +19 -0
  44. package/dist/components/Menu/MenuSeparator.svelte.d.ts +6 -0
  45. package/dist/components/Menu/MenuSub.svelte +14 -0
  46. package/dist/components/Menu/MenuSub.svelte.d.ts +7 -0
  47. package/dist/components/Menu/MenuSubContent.svelte +60 -0
  48. package/dist/components/Menu/MenuSubContent.svelte.d.ts +7 -0
  49. package/dist/components/Menu/MenuSubTrigger.svelte +84 -0
  50. package/dist/components/Menu/MenuSubTrigger.svelte.d.ts +7 -0
  51. package/dist/components/Menu/MenuTrigger.svelte +32 -0
  52. package/dist/components/Menu/MenuTrigger.svelte.d.ts +7 -0
  53. package/dist/components/Navbar/NavItem.svelte +99 -0
  54. package/dist/components/Navbar/NavItem.svelte.d.ts +8 -0
  55. package/dist/components/Navbar/Navbar.svelte +243 -0
  56. package/dist/components/Navbar/Navbar.svelte.d.ts +10 -0
  57. package/dist/components/Radio/Radio.svelte +93 -0
  58. package/dist/components/Radio/Radio.svelte.d.ts +10 -0
  59. package/dist/components/Radio/RadioGroup.svelte +72 -0
  60. package/dist/components/Radio/RadioGroup.svelte.d.ts +14 -0
  61. package/dist/components/Radio/index.d.ts +2 -0
  62. package/dist/components/Radio/index.js +2 -0
  63. package/dist/components/Select/Select.svelte +251 -0
  64. package/dist/components/Select/Select.svelte.d.ts +17 -0
  65. package/dist/components/Select/index.d.ts +1 -0
  66. package/dist/components/Select/index.js +1 -0
  67. package/dist/components/Sidebar/Sidebar.svelte +48 -0
  68. package/dist/components/Sidebar/Sidebar.svelte.d.ts +7 -0
  69. package/dist/components/Sidebar/SidebarGroup.svelte +141 -0
  70. package/dist/components/Sidebar/SidebarGroup.svelte.d.ts +7 -0
  71. package/dist/components/Sidebar/SidebarItem.svelte +151 -0
  72. package/dist/components/Sidebar/SidebarItem.svelte.d.ts +8 -0
  73. package/dist/components/Switch/Switch.svelte +120 -0
  74. package/dist/components/Switch/Switch.svelte.d.ts +13 -0
  75. package/dist/components/Switch/index.d.ts +1 -0
  76. package/dist/components/Switch/index.js +1 -0
  77. package/dist/components/Tabs/TabContent.svelte +91 -0
  78. package/dist/components/Tabs/TabContent.svelte.d.ts +7 -0
  79. package/dist/components/Tabs/TabList.svelte +46 -0
  80. package/dist/components/Tabs/TabList.svelte.d.ts +7 -0
  81. package/dist/components/Tabs/TabTrigger.svelte +68 -0
  82. package/dist/components/Tabs/TabTrigger.svelte.d.ts +7 -0
  83. package/dist/components/Tabs/Tabs.svelte +69 -0
  84. package/dist/components/Tabs/Tabs.svelte.d.ts +7 -0
  85. package/dist/components/TextInput/TextInput.svelte +200 -0
  86. package/dist/components/TextInput/TextInput.svelte.d.ts +35 -0
  87. package/dist/components/TextInput/index.d.ts +1 -0
  88. package/dist/components/TextInput/index.js +1 -0
  89. package/dist/components/Textarea/Textarea.svelte +198 -0
  90. package/dist/components/Textarea/Textarea.svelte.d.ts +33 -0
  91. package/dist/components/Textarea/index.d.ts +1 -0
  92. package/dist/components/Textarea/index.js +1 -0
  93. package/dist/components/Typography/Typography.svelte +99 -0
  94. package/dist/components/Typography/Typography.svelte.d.ts +10 -0
  95. package/dist/components/Typography/index.d.ts +2 -0
  96. package/dist/components/Typography/index.js +1 -0
  97. package/dist/index.d.ts +37 -0
  98. package/dist/index.js +41 -0
  99. package/dist/styles/tokens.css +431 -0
  100. package/dist/test-setup.d.ts +1 -0
  101. package/dist/test-setup.js +1 -0
  102. package/dist/types/index.d.ts +129 -0
  103. package/dist/types/index.js +1 -0
  104. package/dist/types/navigation.d.ts +118 -0
  105. package/dist/types/navigation.js +1 -0
  106. package/package.json +130 -0
@@ -0,0 +1,68 @@
1
+ <script lang="ts">
2
+ import { Tabs as TabsPrimitive } from 'bits-ui';
3
+ import type { TabTriggerProps } from '../../types/index.js';
4
+
5
+ interface Props extends TabTriggerProps {
6
+ children?: import('svelte').Snippet;
7
+ }
8
+
9
+ let { value, disabled = false, class: className = '', children }: Props = $props();
10
+ </script>
11
+
12
+ <TabsPrimitive.Trigger {value} {disabled} class="tab-trigger {className}">
13
+ {#if children}
14
+ {@render children()}
15
+ {/if}
16
+ </TabsPrimitive.Trigger>
17
+
18
+ <style>
19
+ :global(.tab-trigger) {
20
+ position: relative;
21
+ min-height: var(--touch-target-min, 44px);
22
+ padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
23
+ font-family: inherit;
24
+ font-size: var(--text-sm, 0.875rem);
25
+ font-weight: 500;
26
+ color: var(--color-text-muted, #6b7280);
27
+ background: transparent;
28
+ border: none;
29
+ border-bottom: 2px solid transparent;
30
+ cursor: pointer;
31
+ transition: all var(--transition-fast, 150ms ease);
32
+ -webkit-tap-highlight-color: transparent;
33
+ }
34
+
35
+ :global(.tab-trigger:hover:not(:disabled)) {
36
+ color: var(--color-text, #1f2937);
37
+ background: var(--color-bg-muted, #f3f4f6);
38
+ }
39
+
40
+ :global(.tab-trigger:focus-visible) {
41
+ outline: 2px solid var(--color-primary, #3b82f6);
42
+ outline-offset: 2px;
43
+ z-index: 1;
44
+ }
45
+
46
+ :global(.tab-trigger:disabled) {
47
+ opacity: 0.5;
48
+ cursor: not-allowed;
49
+ pointer-events: none;
50
+ }
51
+
52
+ /* Active state - horizontal */
53
+ :global(.tab-trigger[data-state='active']) {
54
+ color: var(--color-primary, #3b82f6);
55
+ border-bottom-color: var(--color-primary, #3b82f6);
56
+ }
57
+
58
+ /* Vertical orientation */
59
+ :global(.tab-trigger[data-orientation='vertical']) {
60
+ border-bottom: none;
61
+ border-left: 2px solid transparent;
62
+ }
63
+
64
+ :global(.tab-trigger[data-orientation='vertical'][data-state='active']) {
65
+ border-left-color: var(--color-primary, #3b82f6);
66
+ border-bottom-color: transparent;
67
+ }
68
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { TabTriggerProps } from '../../types/index.js';
2
+ interface Props extends TabTriggerProps {
3
+ children?: import('svelte').Snippet;
4
+ }
5
+ declare const TabTrigger: import("svelte").Component<Props, {}, "">;
6
+ type TabTrigger = ReturnType<typeof TabTrigger>;
7
+ export default TabTrigger;
@@ -0,0 +1,69 @@
1
+ <script lang="ts">
2
+ import { Tabs as TabsPrimitive } from 'bits-ui';
3
+ import type { TabsProps } from '../../types/index.js';
4
+
5
+ interface Props extends TabsProps {
6
+ children?: import('svelte').Snippet;
7
+ }
8
+
9
+ let {
10
+ value = $bindable(undefined),
11
+ orientation = 'horizontal',
12
+ class: className = '',
13
+ children
14
+ }: Props = $props();
15
+
16
+ // Track internal value for uncontrolled mode
17
+ let internalValue = $state(value);
18
+
19
+ // Sync internal value when external value changes
20
+ $effect(() => {
21
+ if (value !== undefined) {
22
+ internalValue = value;
23
+ }
24
+ });
25
+
26
+ // Update external value when internal changes
27
+ function handleValueChange(newValue: string) {
28
+ internalValue = newValue;
29
+ if (value !== undefined) {
30
+ value = newValue;
31
+ }
32
+ }
33
+ </script>
34
+
35
+ {#if value !== undefined}
36
+ <TabsPrimitive.Root bind:value {orientation} class="tabs tabs--{orientation} {className}">
37
+ {#if children}
38
+ {@render children()}
39
+ {/if}
40
+ </TabsPrimitive.Root>
41
+ {:else}
42
+ <TabsPrimitive.Root
43
+ value={internalValue}
44
+ onValueChange={handleValueChange}
45
+ {orientation}
46
+ class="tabs tabs--{orientation} {className}"
47
+ >
48
+ {#if children}
49
+ {@render children()}
50
+ {/if}
51
+ </TabsPrimitive.Root>
52
+ {/if}
53
+
54
+ <style>
55
+ :global(.tabs) {
56
+ font-family: inherit;
57
+ display: flex;
58
+ flex-direction: column;
59
+ gap: var(--space-md, 1rem);
60
+ }
61
+
62
+ :global(.tabs--vertical) {
63
+ flex-direction: row;
64
+ }
65
+
66
+ :global(.tabs--horizontal) {
67
+ flex-direction: column;
68
+ }
69
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { TabsProps } from '../../types/index.js';
2
+ interface Props extends TabsProps {
3
+ children?: import('svelte').Snippet;
4
+ }
5
+ declare const Tabs: import("svelte").Component<Props, {}, "value">;
6
+ type Tabs = ReturnType<typeof Tabs>;
7
+ export default Tabs;
@@ -0,0 +1,200 @@
1
+ <script lang="ts">
2
+ import type { TextInputType, InputSize } from '../../types/index.js';
3
+
4
+ interface Props {
5
+ type?: TextInputType;
6
+ value?: string;
7
+ placeholder?: string;
8
+ name?: string;
9
+ id?: string;
10
+ size?: InputSize;
11
+ disabled?: boolean;
12
+ readonly?: boolean;
13
+ required?: boolean;
14
+ error?: boolean;
15
+ maxlength?: number;
16
+ minlength?: number;
17
+ pattern?: string;
18
+ autocomplete?: HTMLInputElement['autocomplete'];
19
+ leadingIcon?: import('svelte').Snippet;
20
+ trailingIcon?: import('svelte').Snippet;
21
+ class?: string;
22
+ oninput?: (e: Event & { currentTarget: HTMLInputElement }) => void;
23
+ onchange?: (e: Event & { currentTarget: HTMLInputElement }) => void;
24
+ onblur?: (e: FocusEvent & { currentTarget: HTMLInputElement }) => void;
25
+ onfocus?: (e: FocusEvent & { currentTarget: HTMLInputElement }) => void;
26
+ }
27
+
28
+ let {
29
+ type = 'text',
30
+ value = $bindable(''),
31
+ placeholder,
32
+ name,
33
+ id,
34
+ size = 'md',
35
+ disabled = false,
36
+ readonly = false,
37
+ required = false,
38
+ error = false,
39
+ maxlength,
40
+ minlength,
41
+ pattern,
42
+ autocomplete,
43
+ leadingIcon,
44
+ trailingIcon,
45
+ class: className = '',
46
+ oninput,
47
+ onchange,
48
+ onblur,
49
+ onfocus
50
+ }: Props = $props();
51
+
52
+ const hasLeadingIcon = $derived(!!leadingIcon);
53
+ const hasTrailingIcon = $derived(!!trailingIcon);
54
+ </script>
55
+
56
+ <div
57
+ class="text-input text-input--{size} {error ? 'text-input--error' : ''} {disabled
58
+ ? 'text-input--disabled'
59
+ : ''} {className}"
60
+ >
61
+ {#if hasLeadingIcon}
62
+ <span class="text-input__icon text-input__icon--leading" aria-hidden="true">
63
+ {@render leadingIcon?.()}
64
+ </span>
65
+ {/if}
66
+
67
+ <input
68
+ class="text-input__field"
69
+ class:text-input__field--has-leading={hasLeadingIcon}
70
+ class:text-input__field--has-trailing={hasTrailingIcon}
71
+ {type}
72
+ bind:value
73
+ {placeholder}
74
+ {name}
75
+ {id}
76
+ {disabled}
77
+ {readonly}
78
+ {required}
79
+ {maxlength}
80
+ {minlength}
81
+ {pattern}
82
+ {autocomplete}
83
+ aria-invalid={error}
84
+ aria-required={required}
85
+ {oninput}
86
+ {onchange}
87
+ {onblur}
88
+ {onfocus}
89
+ />
90
+
91
+ {#if hasTrailingIcon}
92
+ <span class="text-input__icon text-input__icon--trailing" aria-hidden="true">
93
+ {@render trailingIcon?.()}
94
+ </span>
95
+ {/if}
96
+ </div>
97
+
98
+ <style>
99
+ .text-input {
100
+ position: relative;
101
+ display: flex;
102
+ align-items: center;
103
+ width: 100%;
104
+ }
105
+
106
+ .text-input--disabled {
107
+ opacity: 0.5;
108
+ }
109
+
110
+ .text-input__field {
111
+ width: 100%;
112
+ min-height: var(--touch-target-min, 44px);
113
+ padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
114
+ border: 1px solid var(--color-border, #e5e7eb);
115
+ border-radius: var(--radius-md, 0.375rem);
116
+ background: var(--color-bg, #ffffff);
117
+ color: var(--color-text, #1f2937);
118
+ font-family: inherit;
119
+ font-size: var(--text-sm, 0.875rem);
120
+ line-height: 1.5;
121
+ transition:
122
+ border-color var(--transition-fast, 150ms ease),
123
+ box-shadow var(--transition-fast, 150ms ease);
124
+ -webkit-tap-highlight-color: transparent;
125
+ }
126
+
127
+ .text-input__field::placeholder {
128
+ color: var(--color-text-muted, #9ca3af);
129
+ }
130
+
131
+ .text-input__field:focus {
132
+ outline: none;
133
+ border-color: var(--color-primary, #3b82f6);
134
+ box-shadow: 0 0 0 3px var(--color-primary-alpha, rgba(59, 130, 246, 0.1));
135
+ }
136
+
137
+ .text-input__field:disabled {
138
+ background: var(--color-bg-muted, #f3f4f6);
139
+ cursor: not-allowed;
140
+ }
141
+
142
+ .text-input__field:read-only {
143
+ background: var(--color-bg-muted, #f3f4f6);
144
+ }
145
+
146
+ /* Sizes */
147
+ .text-input--sm .text-input__field {
148
+ min-height: var(--touch-target-min, 44px);
149
+ padding: var(--space-xs, 0.25rem) var(--space-sm, 0.5rem);
150
+ font-size: var(--text-xs, 0.75rem);
151
+ }
152
+
153
+ .text-input--md .text-input__field {
154
+ min-height: var(--touch-target-min, 44px);
155
+ padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
156
+ font-size: var(--text-sm, 0.875rem);
157
+ }
158
+
159
+ .text-input--lg .text-input__field {
160
+ min-height: 3.25rem;
161
+ padding: var(--space-md, 1rem) var(--space-lg, 1.5rem);
162
+ font-size: var(--text-base, 1rem);
163
+ }
164
+
165
+ /* Error state */
166
+ .text-input--error .text-input__field {
167
+ border-color: var(--color-error, #ef4444);
168
+ }
169
+
170
+ .text-input--error .text-input__field:focus {
171
+ border-color: var(--color-error, #ef4444);
172
+ box-shadow: 0 0 0 3px var(--color-error-alpha, rgba(239, 68, 68, 0.1));
173
+ }
174
+
175
+ /* Icons */
176
+ .text-input__icon {
177
+ position: absolute;
178
+ display: flex;
179
+ align-items: center;
180
+ justify-content: center;
181
+ color: var(--color-text-muted, #6b7280);
182
+ pointer-events: none;
183
+ }
184
+
185
+ .text-input__icon--leading {
186
+ left: var(--space-md, 1rem);
187
+ }
188
+
189
+ .text-input__icon--trailing {
190
+ right: var(--space-md, 1rem);
191
+ }
192
+
193
+ .text-input__field--has-leading {
194
+ padding-left: calc(var(--space-md, 1rem) * 2 + 1.25rem);
195
+ }
196
+
197
+ .text-input__field--has-trailing {
198
+ padding-right: calc(var(--space-md, 1rem) * 2 + 1.25rem);
199
+ }
200
+ </style>
@@ -0,0 +1,35 @@
1
+ import type { TextInputType, InputSize } from '../../types/index.js';
2
+ interface Props {
3
+ type?: TextInputType;
4
+ value?: string;
5
+ placeholder?: string;
6
+ name?: string;
7
+ id?: string;
8
+ size?: InputSize;
9
+ disabled?: boolean;
10
+ readonly?: boolean;
11
+ required?: boolean;
12
+ error?: boolean;
13
+ maxlength?: number;
14
+ minlength?: number;
15
+ pattern?: string;
16
+ autocomplete?: HTMLInputElement['autocomplete'];
17
+ leadingIcon?: import('svelte').Snippet;
18
+ trailingIcon?: import('svelte').Snippet;
19
+ class?: string;
20
+ oninput?: (e: Event & {
21
+ currentTarget: HTMLInputElement;
22
+ }) => void;
23
+ onchange?: (e: Event & {
24
+ currentTarget: HTMLInputElement;
25
+ }) => void;
26
+ onblur?: (e: FocusEvent & {
27
+ currentTarget: HTMLInputElement;
28
+ }) => void;
29
+ onfocus?: (e: FocusEvent & {
30
+ currentTarget: HTMLInputElement;
31
+ }) => void;
32
+ }
33
+ declare const TextInput: import("svelte").Component<Props, {}, "value">;
34
+ type TextInput = ReturnType<typeof TextInput>;
35
+ export default TextInput;
@@ -0,0 +1 @@
1
+ export { default as TextInput } from './TextInput.svelte';
@@ -0,0 +1 @@
1
+ export { default as TextInput } from './TextInput.svelte';
@@ -0,0 +1,198 @@
1
+ <script lang="ts">
2
+ import type { InputSize } from '../../types/index.js';
3
+
4
+ interface Props {
5
+ value?: string;
6
+ placeholder?: string;
7
+ name?: string;
8
+ id?: string;
9
+ size?: InputSize;
10
+ disabled?: boolean;
11
+ readonly?: boolean;
12
+ required?: boolean;
13
+ error?: boolean;
14
+ maxlength?: number;
15
+ minlength?: number;
16
+ rows?: number;
17
+ autoResize?: boolean;
18
+ showCharacterCount?: boolean;
19
+ class?: string;
20
+ oninput?: (e: Event & { currentTarget: HTMLTextAreaElement }) => void;
21
+ onchange?: (e: Event & { currentTarget: HTMLTextAreaElement }) => void;
22
+ onblur?: (e: FocusEvent & { currentTarget: HTMLTextAreaElement }) => void;
23
+ onfocus?: (e: FocusEvent & { currentTarget: HTMLTextAreaElement }) => void;
24
+ }
25
+
26
+ let {
27
+ value = $bindable(''),
28
+ placeholder,
29
+ name,
30
+ id,
31
+ size = 'md',
32
+ disabled = false,
33
+ readonly = false,
34
+ required = false,
35
+ error = false,
36
+ maxlength,
37
+ minlength,
38
+ rows = 3,
39
+ autoResize = false,
40
+ showCharacterCount = false,
41
+ class: className = '',
42
+ oninput,
43
+ onchange,
44
+ onblur,
45
+ onfocus
46
+ }: Props = $props();
47
+
48
+ let textareaElement: HTMLTextAreaElement | undefined = $state();
49
+
50
+ const characterCount = $derived(value?.length ?? 0);
51
+
52
+ function handleInput(e: Event & { currentTarget: HTMLTextAreaElement }) {
53
+ if (autoResize && textareaElement) {
54
+ textareaElement.style.height = 'auto';
55
+ textareaElement.style.height = `${textareaElement.scrollHeight}px`;
56
+ }
57
+ oninput?.(e);
58
+ }
59
+
60
+ $effect(() => {
61
+ if (autoResize && textareaElement && value) {
62
+ textareaElement.style.height = 'auto';
63
+ textareaElement.style.height = `${textareaElement.scrollHeight}px`;
64
+ }
65
+ });
66
+ </script>
67
+
68
+ <div
69
+ class="textarea textarea--{size} {error ? 'textarea--error' : ''} {disabled
70
+ ? 'textarea--disabled'
71
+ : ''} {className}"
72
+ >
73
+ <textarea
74
+ bind:this={textareaElement}
75
+ class="textarea__field"
76
+ bind:value
77
+ {placeholder}
78
+ {name}
79
+ {id}
80
+ {disabled}
81
+ {readonly}
82
+ {required}
83
+ {maxlength}
84
+ {minlength}
85
+ {rows}
86
+ aria-invalid={error}
87
+ aria-required={required}
88
+ oninput={handleInput}
89
+ {onchange}
90
+ {onblur}
91
+ {onfocus}
92
+ ></textarea>
93
+
94
+ {#if showCharacterCount}
95
+ <div class="textarea__footer">
96
+ <span
97
+ class="textarea__count"
98
+ class:textarea__count--error={maxlength && characterCount > maxlength}
99
+ >
100
+ {characterCount}{#if maxlength}/{maxlength}{/if}
101
+ </span>
102
+ </div>
103
+ {/if}
104
+ </div>
105
+
106
+ <style>
107
+ .textarea {
108
+ display: flex;
109
+ flex-direction: column;
110
+ width: 100%;
111
+ }
112
+
113
+ .textarea--disabled {
114
+ opacity: 0.5;
115
+ }
116
+
117
+ .textarea__field {
118
+ width: 100%;
119
+ min-height: var(--touch-target-min, 44px);
120
+ padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
121
+ border: 1px solid var(--color-border, #e5e7eb);
122
+ border-radius: var(--radius-md, 0.375rem);
123
+ background: var(--color-bg, #ffffff);
124
+ color: var(--color-text, #1f2937);
125
+ font-family: inherit;
126
+ font-size: var(--text-sm, 0.875rem);
127
+ line-height: 1.5;
128
+ resize: vertical;
129
+ transition:
130
+ border-color var(--transition-fast, 150ms ease),
131
+ box-shadow var(--transition-fast, 150ms ease);
132
+ -webkit-tap-highlight-color: transparent;
133
+ }
134
+
135
+ .textarea__field::placeholder {
136
+ color: var(--color-text-muted, #9ca3af);
137
+ }
138
+
139
+ .textarea__field:focus {
140
+ outline: none;
141
+ border-color: var(--color-primary, #3b82f6);
142
+ box-shadow: 0 0 0 3px var(--color-primary-alpha, rgba(59, 130, 246, 0.1));
143
+ }
144
+
145
+ .textarea__field:disabled {
146
+ background: var(--color-bg-muted, #f3f4f6);
147
+ cursor: not-allowed;
148
+ }
149
+
150
+ .textarea__field:read-only {
151
+ background: var(--color-bg-muted, #f3f4f6);
152
+ }
153
+
154
+ /* Sizes */
155
+ .textarea--sm .textarea__field {
156
+ min-height: var(--touch-target-min, 44px);
157
+ padding: var(--space-xs, 0.25rem) var(--space-sm, 0.5rem);
158
+ font-size: var(--text-xs, 0.75rem);
159
+ }
160
+
161
+ .textarea--md .textarea__field {
162
+ min-height: var(--touch-target-min, 44px);
163
+ padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
164
+ font-size: var(--text-sm, 0.875rem);
165
+ }
166
+
167
+ .textarea--lg .textarea__field {
168
+ min-height: 3.25rem;
169
+ padding: var(--space-md, 1rem) var(--space-lg, 1.5rem);
170
+ font-size: var(--text-base, 1rem);
171
+ }
172
+
173
+ /* Error state */
174
+ .textarea--error .textarea__field {
175
+ border-color: var(--color-error, #ef4444);
176
+ }
177
+
178
+ .textarea--error .textarea__field:focus {
179
+ border-color: var(--color-error, #ef4444);
180
+ box-shadow: 0 0 0 3px var(--color-error-alpha, rgba(239, 68, 68, 0.1));
181
+ }
182
+
183
+ /* Footer with character count */
184
+ .textarea__footer {
185
+ display: flex;
186
+ justify-content: flex-end;
187
+ margin-top: var(--space-xs, 0.25rem);
188
+ }
189
+
190
+ .textarea__count {
191
+ font-size: var(--text-xs, 0.75rem);
192
+ color: var(--color-text-muted, #6b7280);
193
+ }
194
+
195
+ .textarea__count--error {
196
+ color: var(--color-error, #ef4444);
197
+ }
198
+ </style>
@@ -0,0 +1,33 @@
1
+ import type { InputSize } from '../../types/index.js';
2
+ interface Props {
3
+ value?: string;
4
+ placeholder?: string;
5
+ name?: string;
6
+ id?: string;
7
+ size?: InputSize;
8
+ disabled?: boolean;
9
+ readonly?: boolean;
10
+ required?: boolean;
11
+ error?: boolean;
12
+ maxlength?: number;
13
+ minlength?: number;
14
+ rows?: number;
15
+ autoResize?: boolean;
16
+ showCharacterCount?: boolean;
17
+ class?: string;
18
+ oninput?: (e: Event & {
19
+ currentTarget: HTMLTextAreaElement;
20
+ }) => void;
21
+ onchange?: (e: Event & {
22
+ currentTarget: HTMLTextAreaElement;
23
+ }) => void;
24
+ onblur?: (e: FocusEvent & {
25
+ currentTarget: HTMLTextAreaElement;
26
+ }) => void;
27
+ onfocus?: (e: FocusEvent & {
28
+ currentTarget: HTMLTextAreaElement;
29
+ }) => void;
30
+ }
31
+ declare const Textarea: import("svelte").Component<Props, {}, "value">;
32
+ type Textarea = ReturnType<typeof Textarea>;
33
+ export default Textarea;
@@ -0,0 +1 @@
1
+ export { default as Textarea } from './Textarea.svelte';
@@ -0,0 +1 @@
1
+ export { default as Textarea } from './Textarea.svelte';