@tabelhadev/tabelhawebui 0.28.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.
Files changed (175) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +222 -0
  3. package/README.pt-BR.md +217 -0
  4. package/dist/actions/click-outside.d.ts +3 -0
  5. package/dist/actions/click-outside.js +12 -0
  6. package/dist/actions/combobox-keyboard-nav.d.ts +9 -0
  7. package/dist/actions/combobox-keyboard-nav.js +28 -0
  8. package/dist/actions/focus-trap.d.ts +1 -0
  9. package/dist/actions/focus-trap.js +34 -0
  10. package/dist/actions/scroll-lock.d.ts +1 -0
  11. package/dist/actions/scroll-lock.js +17 -0
  12. package/dist/components/actions/Button.svelte +169 -0
  13. package/dist/components/actions/Button.svelte.d.ts +19 -0
  14. package/dist/components/appshell/AppShell.svelte +50 -0
  15. package/dist/components/appshell/AppShell.svelte.d.ts +19 -0
  16. package/dist/components/appshell/AppShellBottomNav.svelte +67 -0
  17. package/dist/components/appshell/AppShellBottomNav.svelte.d.ts +9 -0
  18. package/dist/components/appshell/AppShellContent.svelte +38 -0
  19. package/dist/components/appshell/AppShellContent.svelte.d.ts +10 -0
  20. package/dist/components/appshell/AppShellSidebar.svelte +196 -0
  21. package/dist/components/appshell/AppShellSidebar.svelte.d.ts +15 -0
  22. package/dist/components/appshell/types.d.ts +6 -0
  23. package/dist/components/appshell/types.js +1 -0
  24. package/dist/components/appshell/utils.d.ts +1 -0
  25. package/dist/components/appshell/utils.js +3 -0
  26. package/dist/components/card/Card.svelte +31 -0
  27. package/dist/components/card/Card.svelte.d.ts +9 -0
  28. package/dist/components/card/CardAction.svelte +57 -0
  29. package/dist/components/card/CardAction.svelte.d.ts +10 -0
  30. package/dist/components/card/CardContent.svelte +25 -0
  31. package/dist/components/card/CardContent.svelte.d.ts +8 -0
  32. package/dist/components/card/CardDescription.svelte +24 -0
  33. package/dist/components/card/CardDescription.svelte.d.ts +8 -0
  34. package/dist/components/card/CardFooter.svelte +25 -0
  35. package/dist/components/card/CardFooter.svelte.d.ts +8 -0
  36. package/dist/components/card/CardHeader.svelte +54 -0
  37. package/dist/components/card/CardHeader.svelte.d.ts +10 -0
  38. package/dist/components/card/CardTitle.svelte +25 -0
  39. package/dist/components/card/CardTitle.svelte.d.ts +8 -0
  40. package/dist/components/chat/ChatMessage.svelte +104 -0
  41. package/dist/components/chat/ChatMessage.svelte.d.ts +10 -0
  42. package/dist/components/choice/Listbox.svelte +183 -0
  43. package/dist/components/choice/Listbox.svelte.d.ts +15 -0
  44. package/dist/components/choice/MultiSelect.svelte +278 -0
  45. package/dist/components/choice/MultiSelect.svelte.d.ts +18 -0
  46. package/dist/components/choice/Rating.svelte +91 -0
  47. package/dist/components/choice/Rating.svelte.d.ts +12 -0
  48. package/dist/components/choice/Toggle.svelte +112 -0
  49. package/dist/components/choice/Toggle.svelte.d.ts +12 -0
  50. package/dist/components/feedback/Badge.svelte +66 -0
  51. package/dist/components/feedback/Badge.svelte.d.ts +9 -0
  52. package/dist/components/feedback/ErrorPage.svelte +99 -0
  53. package/dist/components/feedback/ErrorPage.svelte.d.ts +13 -0
  54. package/dist/components/feedback/Message.svelte +130 -0
  55. package/dist/components/feedback/Message.svelte.d.ts +14 -0
  56. package/dist/components/feedback/ProgressBar.svelte +69 -0
  57. package/dist/components/feedback/ProgressBar.svelte.d.ts +8 -0
  58. package/dist/components/feedback/Skeleton.svelte +47 -0
  59. package/dist/components/feedback/Skeleton.svelte.d.ts +9 -0
  60. package/dist/components/feedback/Status.svelte +75 -0
  61. package/dist/components/feedback/Status.svelte.d.ts +10 -0
  62. package/dist/components/feedback/StatusPill.svelte +94 -0
  63. package/dist/components/feedback/StatusPill.svelte.d.ts +13 -0
  64. package/dist/components/feedback/Toaster.svelte +122 -0
  65. package/dist/components/feedback/Toaster.svelte.d.ts +7 -0
  66. package/dist/components/feedback/Tooltip.svelte +126 -0
  67. package/dist/components/feedback/Tooltip.svelte.d.ts +10 -0
  68. package/dist/components/feedback/toast.svelte.d.ts +27 -0
  69. package/dist/components/feedback/toast.svelte.js +23 -0
  70. package/dist/components/forms/Checkbox.svelte +96 -0
  71. package/dist/components/forms/Checkbox.svelte.d.ts +11 -0
  72. package/dist/components/forms/DatePicker.svelte +372 -0
  73. package/dist/components/forms/DatePicker.svelte.d.ts +21 -0
  74. package/dist/components/forms/Field.svelte +47 -0
  75. package/dist/components/forms/Field.svelte.d.ts +11 -0
  76. package/dist/components/forms/FileUpload.svelte +504 -0
  77. package/dist/components/forms/FileUpload.svelte.d.ts +43 -0
  78. package/dist/components/forms/Input.svelte +93 -0
  79. package/dist/components/forms/Input.svelte.d.ts +9 -0
  80. package/dist/components/forms/Label.svelte +30 -0
  81. package/dist/components/forms/Label.svelte.d.ts +10 -0
  82. package/dist/components/forms/Radio.svelte +80 -0
  83. package/dist/components/forms/Radio.svelte.d.ts +11 -0
  84. package/dist/components/forms/Select.svelte +322 -0
  85. package/dist/components/forms/Select.svelte.d.ts +22 -0
  86. package/dist/components/forms/TagInput.svelte +336 -0
  87. package/dist/components/forms/TagInput.svelte.d.ts +16 -0
  88. package/dist/components/forms/Textarea.svelte +64 -0
  89. package/dist/components/forms/Textarea.svelte.d.ts +9 -0
  90. package/dist/components/landing/Landing.svelte +47 -0
  91. package/dist/components/landing/Landing.svelte.d.ts +8 -0
  92. package/dist/components/landing/LandingFeatures.svelte +92 -0
  93. package/dist/components/landing/LandingFeatures.svelte.d.ts +15 -0
  94. package/dist/components/landing/LandingHero.svelte +92 -0
  95. package/dist/components/landing/LandingHero.svelte.d.ts +13 -0
  96. package/dist/components/landing/LandingRoadmap.svelte +42 -0
  97. package/dist/components/landing/LandingRoadmap.svelte.d.ts +11 -0
  98. package/dist/components/landing/LandingSection.svelte +25 -0
  99. package/dist/components/landing/LandingSection.svelte.d.ts +9 -0
  100. package/dist/components/landing/LandingSectionHeading.svelte +19 -0
  101. package/dist/components/landing/LandingSectionHeading.svelte.d.ts +10 -0
  102. package/dist/components/landing/LandingSteps.svelte +100 -0
  103. package/dist/components/landing/LandingSteps.svelte.d.ts +13 -0
  104. package/dist/components/layout/BracketLink.svelte +41 -0
  105. package/dist/components/layout/BracketLink.svelte.d.ts +9 -0
  106. package/dist/components/layout/Divider.svelte +70 -0
  107. package/dist/components/layout/Divider.svelte.d.ts +9 -0
  108. package/dist/components/layout/Eyebrow.svelte +24 -0
  109. package/dist/components/layout/Eyebrow.svelte.d.ts +8 -0
  110. package/dist/components/layout/PageHeader.svelte +106 -0
  111. package/dist/components/layout/PageHeader.svelte.d.ts +15 -0
  112. package/dist/components/layout/PageSeo.svelte +34 -0
  113. package/dist/components/layout/PageSeo.svelte.d.ts +10 -0
  114. package/dist/components/layout/PageShell.svelte +25 -0
  115. package/dist/components/layout/PageShell.svelte.d.ts +8 -0
  116. package/dist/components/layout/Panel.svelte +105 -0
  117. package/dist/components/layout/Panel.svelte.d.ts +12 -0
  118. package/dist/components/layout/Prose.svelte +101 -0
  119. package/dist/components/layout/Prose.svelte.d.ts +7 -0
  120. package/dist/components/layout/RuleCard.svelte +63 -0
  121. package/dist/components/layout/RuleCard.svelte.d.ts +13 -0
  122. package/dist/components/layout/SectionHeading.svelte +57 -0
  123. package/dist/components/layout/SectionHeading.svelte.d.ts +10 -0
  124. package/dist/components/layout/StatTile.svelte +76 -0
  125. package/dist/components/layout/StatTile.svelte.d.ts +12 -0
  126. package/dist/components/layout/TerminalWindow.svelte +81 -0
  127. package/dist/components/layout/TerminalWindow.svelte.d.ts +9 -0
  128. package/dist/components/layout/Wordmark.svelte +26 -0
  129. package/dist/components/layout/Wordmark.svelte.d.ts +8 -0
  130. package/dist/components/list/List.svelte +40 -0
  131. package/dist/components/list/List.svelte.d.ts +13 -0
  132. package/dist/components/list/ListItem.svelte +81 -0
  133. package/dist/components/list/ListItem.svelte.d.ts +13 -0
  134. package/dist/components/marketing/MarketingShell.svelte +170 -0
  135. package/dist/components/marketing/MarketingShell.svelte.d.ts +23 -0
  136. package/dist/components/marketing/MarketingShellFooter.svelte +92 -0
  137. package/dist/components/marketing/MarketingShellFooter.svelte.d.ts +14 -0
  138. package/dist/components/navigation/Accordion.svelte +110 -0
  139. package/dist/components/navigation/Accordion.svelte.d.ts +15 -0
  140. package/dist/components/navigation/Carousel.svelte +115 -0
  141. package/dist/components/navigation/Carousel.svelte.d.ts +12 -0
  142. package/dist/components/navigation/InstructionStep.svelte +115 -0
  143. package/dist/components/navigation/InstructionStep.svelte.d.ts +10 -0
  144. package/dist/components/navigation/InstructionSteps.svelte +39 -0
  145. package/dist/components/navigation/InstructionSteps.svelte.d.ts +8 -0
  146. package/dist/components/navigation/Nav.svelte +102 -0
  147. package/dist/components/navigation/Nav.svelte.d.ts +14 -0
  148. package/dist/components/navigation/Stepper.svelte +112 -0
  149. package/dist/components/navigation/Stepper.svelte.d.ts +12 -0
  150. package/dist/components/navigation/TabCard.svelte +62 -0
  151. package/dist/components/navigation/TabCard.svelte.d.ts +10 -0
  152. package/dist/components/navigation/Tabs.svelte +83 -0
  153. package/dist/components/navigation/Tabs.svelte.d.ts +12 -0
  154. package/dist/components/navigation/ThemeToggle.svelte +118 -0
  155. package/dist/components/navigation/ThemeToggle.svelte.d.ts +20 -0
  156. package/dist/components/overlay/Dialog.svelte +200 -0
  157. package/dist/components/overlay/Dialog.svelte.d.ts +19 -0
  158. package/dist/components/overlay/DialogActions.svelte +50 -0
  159. package/dist/components/overlay/DialogActions.svelte.d.ts +9 -0
  160. package/dist/components/overlay/Dropdown.svelte +110 -0
  161. package/dist/components/overlay/Dropdown.svelte.d.ts +10 -0
  162. package/dist/components/overlay/FloatingActionPill.svelte +96 -0
  163. package/dist/components/overlay/FloatingActionPill.svelte.d.ts +14 -0
  164. package/dist/components/overlay/Sidebar.svelte +258 -0
  165. package/dist/components/overlay/Sidebar.svelte.d.ts +18 -0
  166. package/dist/components/table/Table.svelte +550 -0
  167. package/dist/components/table/Table.svelte.d.ts +42 -0
  168. package/dist/components/timeline/Timeline.svelte +25 -0
  169. package/dist/components/timeline/Timeline.svelte.d.ts +8 -0
  170. package/dist/components/timeline/TimelineItem.svelte +220 -0
  171. package/dist/components/timeline/TimelineItem.svelte.d.ts +18 -0
  172. package/dist/index.d.ts +156 -0
  173. package/dist/index.js +149 -0
  174. package/dist/theme/theme.css +348 -0
  175. package/package.json +52 -0
@@ -0,0 +1,126 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ let {
5
+ label,
6
+ position = 'top',
7
+ children,
8
+ class: className = ''
9
+ }: {
10
+ label: string;
11
+ position?: 'top' | 'bottom' | 'left' | 'right';
12
+ children: Snippet;
13
+ class?: string;
14
+ } = $props();
15
+
16
+ // The bubble used to be a ::after pseudo-element fed by attr(data-tooltip).
17
+ // Pseudo-elements are not in the accessibility tree, so the label was
18
+ // invisible to screen readers. It is now a real element referenced by
19
+ // aria-describedby.
20
+ const id = $props.id();
21
+
22
+ // WCAG 1.4.13 (Content on Hover or Focus) requires the bubble to be
23
+ // dismissible without moving the pointer or focus. Escape hides it until
24
+ // the trigger is left and entered again. The listener is on the window
25
+ // rather than the wrapper: a keydown handler on a static span would make it
26
+ // an interactive element without a role.
27
+ let active = $state(false);
28
+ let dismissed = $state(false);
29
+ const visible = $derived(active && !dismissed);
30
+
31
+ function enter() {
32
+ active = true;
33
+ dismissed = false;
34
+ }
35
+
36
+ function leave() {
37
+ active = false;
38
+ dismissed = false;
39
+ }
40
+
41
+ function onWindowKeydown(event: KeyboardEvent) {
42
+ if (event.key === 'Escape' && active) dismissed = true;
43
+ }
44
+ </script>
45
+
46
+ <svelte:window onkeydown={onWindowKeydown} />
47
+
48
+ <!--
49
+ The wrapper is not the interactive element — whatever the caller renders
50
+ inside it is (a button, a link, an icon with tabindex). It only observes
51
+ hover and focus to toggle a bubble that is already wired up through
52
+ aria-describedby, so it needs no role of its own; giving it one would
53
+ announce a control that does not exist.
54
+ -->
55
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
56
+ <span
57
+ class="twui-tooltip twui-tooltip-{position} {className}"
58
+ class:twui-tooltip-visible={visible}
59
+ aria-describedby={id}
60
+ onpointerenter={enter}
61
+ onpointerleave={leave}
62
+ onfocusin={enter}
63
+ onfocusout={leave}
64
+ >
65
+ {@render children()}
66
+ <span class="twui-tooltip-bubble" role="tooltip" {id}>{label}</span>
67
+ </span>
68
+
69
+ <style>
70
+ .twui-tooltip {
71
+ position: relative;
72
+ display: inline-flex;
73
+ }
74
+
75
+ .twui-tooltip-bubble {
76
+ position: absolute;
77
+ z-index: 50;
78
+ padding: 4px 8px;
79
+ border: 1px solid var(--twui-rule);
80
+ background: var(--twui-paper-raised);
81
+ font-family: var(--twui-font-mono);
82
+ font-size: 11px;
83
+ /* A long label used to run off the viewport under white-space: nowrap. */
84
+ max-width: min(16rem, 60vw);
85
+ width: max-content;
86
+ color: var(--twui-ink);
87
+ box-shadow: var(--twui-shadow-offset, 3px 3px 0 0 var(--twui-rule));
88
+ opacity: 0;
89
+ pointer-events: none;
90
+ transition: opacity 0.12s ease;
91
+ }
92
+
93
+ .twui-tooltip-visible .twui-tooltip-bubble {
94
+ opacity: 1;
95
+ }
96
+
97
+ @media (prefers-reduced-motion: reduce) {
98
+ .twui-tooltip-bubble {
99
+ transition: none;
100
+ }
101
+ }
102
+
103
+ .twui-tooltip-top .twui-tooltip-bubble {
104
+ bottom: calc(100% + 6px);
105
+ left: 50%;
106
+ transform: translateX(-50%);
107
+ }
108
+
109
+ .twui-tooltip-bottom .twui-tooltip-bubble {
110
+ top: calc(100% + 6px);
111
+ left: 50%;
112
+ transform: translateX(-50%);
113
+ }
114
+
115
+ .twui-tooltip-left .twui-tooltip-bubble {
116
+ right: calc(100% + 6px);
117
+ top: 50%;
118
+ transform: translateY(-50%);
119
+ }
120
+
121
+ .twui-tooltip-right .twui-tooltip-bubble {
122
+ left: calc(100% + 6px);
123
+ top: 50%;
124
+ transform: translateY(-50%);
125
+ }
126
+ </style>
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ label: string;
4
+ position?: 'top' | 'bottom' | 'left' | 'right';
5
+ children: Snippet;
6
+ class?: string;
7
+ };
8
+ declare const Tooltip: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type Tooltip = ReturnType<typeof Tooltip>;
10
+ export default Tooltip;
@@ -0,0 +1,27 @@
1
+ export type ToastKind = "success" | "error" | "info" | "warning";
2
+ export interface Toast {
3
+ id: number;
4
+ kind: ToastKind;
5
+ message: string;
6
+ action?: {
7
+ label: string;
8
+ onClick: () => void;
9
+ };
10
+ }
11
+ export declare const toasts: {
12
+ current: Toast[];
13
+ };
14
+ export declare function dismiss(id: number): void;
15
+ type ToastOptions = {
16
+ action?: Toast["action"];
17
+ duration?: number;
18
+ };
19
+ export interface ToastApi {
20
+ (message: string, opts?: ToastOptions): void;
21
+ success(message: string, opts?: ToastOptions): void;
22
+ error(message: string): void;
23
+ info(message: string): void;
24
+ warning(message: string): void;
25
+ }
26
+ export declare const toast: ToastApi;
27
+ export {};
@@ -0,0 +1,23 @@
1
+ // Simple, self-contained toast — replaces svelte-sonner. The store is a
2
+ // Svelte 5 module (`$state` in .svelte.js) importable from anywhere; the
3
+ // <Toaster> from tabelhawebui is the only one that renders.
4
+ let nextId = 1;
5
+ export const toasts = $state({ current: [] });
6
+ export function dismiss(id) {
7
+ toasts.current = toasts.current.filter((t) => t.id !== id);
8
+ }
9
+ function push(kind, message, opts) {
10
+ const id = nextId++;
11
+ toasts.current.push({ id, kind, message, action: opts?.action });
12
+ // Auto-dismiss (except toasts with action, like "new version available").
13
+ if (!opts?.action) {
14
+ setTimeout(() => dismiss(id), 4000);
15
+ }
16
+ }
17
+ // Callable (`toast('msg', opts)`) and with methods (`toast.success('msg')`).
18
+ export const toast = Object.assign((message, opts) => push("info", message, opts), {
19
+ success: (message, opts) => push("success", message, opts),
20
+ error: (message) => push("error", message),
21
+ info: (message) => push("info", message),
22
+ warning: (message) => push("warning", message),
23
+ });
@@ -0,0 +1,96 @@
1
+ <script lang="ts">
2
+ import type { HTMLInputAttributes } from 'svelte/elements';
3
+
4
+ let {
5
+ class: className = '',
6
+ checked = $bindable(false),
7
+ name,
8
+ value,
9
+ disabled = false,
10
+ label,
11
+ onchange,
12
+ ...rest
13
+ }: HTMLInputAttributes & {
14
+ checked?: boolean;
15
+ name?: string;
16
+ value?: string;
17
+ disabled?: boolean;
18
+ label?: string;
19
+ } = $props();
20
+ </script>
21
+
22
+ <label class="twui-choice {disabled ? 'twui-choice-disabled' : ''} {className}">
23
+ <input
24
+ {...rest}
25
+ type="checkbox"
26
+ {name}
27
+ {value}
28
+ {disabled}
29
+ checked={checked}
30
+ onchange={(e) => {
31
+ checked = e.currentTarget.checked;
32
+ onchange?.(e);
33
+ }}
34
+ class="twui-choice-input"
35
+ />
36
+ <span class="twui-choice-box" aria-hidden="true"></span>
37
+ {#if label}
38
+ <span class="twui-choice-label">{label}</span>
39
+ {/if}
40
+ </label>
41
+
42
+ <style>
43
+ .twui-choice {
44
+ display: inline-flex;
45
+ align-items: center;
46
+ gap: 8px;
47
+ cursor: pointer;
48
+ font-family: var(--twui-font-mono);
49
+ font-size: 14px;
50
+ color: var(--twui-ink);
51
+ }
52
+
53
+ .twui-choice-input {
54
+ position: absolute;
55
+ opacity: 0;
56
+ pointer-events: none;
57
+ }
58
+
59
+ .twui-choice-box {
60
+ position: relative;
61
+ width: 14px;
62
+ height: 14px;
63
+ border: 1px solid var(--twui-rule);
64
+ background: var(--twui-paper);
65
+ border-radius: 2px;
66
+ transition:
67
+ border-color 0.15s ease,
68
+ background 0.15s ease;
69
+ }
70
+
71
+ .twui-choice-input:checked + .twui-choice-box {
72
+ border-color: var(--twui-accent);
73
+ background: var(--twui-accent);
74
+ }
75
+
76
+ .twui-choice-input:checked + .twui-choice-box::after {
77
+ content: '✓';
78
+ position: absolute;
79
+ inset: 0;
80
+ display: flex;
81
+ align-items: center;
82
+ justify-content: center;
83
+ font-size: 10px;
84
+ line-height: 1;
85
+ color: var(--twui-paper);
86
+ }
87
+
88
+ .twui-choice:has(.twui-choice-input:focus-visible) .twui-choice-box {
89
+ box-shadow: 0 0 0 2px var(--twui-accent-soft);
90
+ }
91
+
92
+ .twui-choice-disabled {
93
+ opacity: 0.5;
94
+ cursor: not-allowed;
95
+ }
96
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { HTMLInputAttributes } from 'svelte/elements';
2
+ type $$ComponentProps = HTMLInputAttributes & {
3
+ checked?: boolean;
4
+ name?: string;
5
+ value?: string;
6
+ disabled?: boolean;
7
+ label?: string;
8
+ };
9
+ declare const Checkbox: import("svelte").Component<$$ComponentProps, {}, "checked">;
10
+ type Checkbox = ReturnType<typeof Checkbox>;
11
+ export default Checkbox;
@@ -0,0 +1,372 @@
1
+ <script lang="ts">
2
+ import { fade } from 'svelte/transition';
3
+ import { clickOutside } from '../../actions/click-outside';
4
+
5
+ let {
6
+ value = $bindable(''),
7
+ label,
8
+ name,
9
+ mode = 'date',
10
+ min,
11
+ max,
12
+ disabled = false,
13
+ required = false,
14
+ placeholder = 'Selecione uma data',
15
+ locale = 'pt-BR',
16
+ prevLabel = 'Anterior',
17
+ nextLabel = 'Próximo',
18
+ class: className = '',
19
+ ...rest
20
+ }: {
21
+ value?: string;
22
+ label?: string;
23
+ name?: string;
24
+ mode?: 'date' | 'month';
25
+ min?: string;
26
+ max?: string;
27
+ disabled?: boolean;
28
+ required?: boolean;
29
+ placeholder?: string;
30
+ locale?: string;
31
+ prevLabel?: string;
32
+ nextLabel?: string;
33
+ class?: string;
34
+ } & { 'aria-label'?: string; id?: string } = $props();
35
+
36
+ let open = $state(false);
37
+ let rootEl = $state<HTMLDivElement | undefined>();
38
+ const today = new Date();
39
+ let viewYear = $state(today.getFullYear());
40
+ let viewMonth = $state(today.getMonth()); // 0-11
41
+
42
+ function toggle() {
43
+ if (disabled) return;
44
+ open = !open;
45
+ if (open) {
46
+ const ref = value
47
+ ? new Date(`${value}T00:00:00`)
48
+ : null;
49
+ viewYear = ref ? ref.getFullYear() : today.getFullYear();
50
+ viewMonth = ref ? ref.getMonth() : today.getMonth();
51
+ }
52
+ }
53
+
54
+ function pick(iso: string) {
55
+ // In month mode the value is YYYY-MM: the clicked day only indicates the month.
56
+ value = mode === 'month' ? iso.slice(0, 7) : iso;
57
+ open = false;
58
+ }
59
+
60
+ // Navigation always month by month (in both modes).
61
+ function prev() {
62
+ viewMonth -= 1;
63
+ if (viewMonth < 0) {
64
+ viewMonth = 11;
65
+ viewYear -= 1;
66
+ }
67
+ }
68
+
69
+ function next() {
70
+ viewMonth += 1;
71
+ if (viewMonth > 11) {
72
+ viewMonth = 0;
73
+ viewYear += 1;
74
+ }
75
+ }
76
+
77
+ const monthLabel = $derived(
78
+ new Date(viewYear, viewMonth, 1).toLocaleDateString(locale, {
79
+ month: 'long',
80
+ year: 'numeric'
81
+ })
82
+ );
83
+
84
+ // Grid headers aligned to getDay() (0=Sunday): Jan 7, 2024 is a Sunday.
85
+ const weekdays = $derived(
86
+ Array.from({ length: 7 }, (_, i) =>
87
+ new Intl.DateTimeFormat(locale, { weekday: 'narrow' }).format(new Date(2024, 0, 7 + i))
88
+ )
89
+ );
90
+
91
+ const days = $derived.by(() => {
92
+ const first = new Date(viewYear, viewMonth, 1);
93
+ const startDow = first.getDay(); // 0=Sunday
94
+ const count = new Date(viewYear, viewMonth + 1, 0).getDate();
95
+ const cells: Array<{ iso: string; day: number; inMonth: boolean }> = [];
96
+ for (let i = 0; i < startDow; i++) {
97
+ const d = new Date(viewYear, viewMonth, i - startDow + 1);
98
+ cells.push({ iso: isoOf(d), day: d.getDate(), inMonth: false });
99
+ }
100
+ for (let day = 1; day <= count; day++) {
101
+ const d = new Date(viewYear, viewMonth, day);
102
+ cells.push({ iso: isoOf(d), day, inMonth: true });
103
+ }
104
+ while (cells.length % 7 !== 0) {
105
+ const last = cells[cells.length - 1];
106
+ const d = new Date(`${last.iso}T00:00:00`);
107
+ d.setDate(d.getDate() + 1);
108
+ cells.push({ iso: isoOf(d), day: d.getDate(), inMonth: false });
109
+ }
110
+ return cells;
111
+ });
112
+
113
+ function isoOf(d: Date): string {
114
+ const y = d.getFullYear();
115
+ const m = String(d.getMonth() + 1).padStart(2, '0');
116
+ const day = String(d.getDate()).padStart(2, '0');
117
+ return `${y}-${m}-${day}`;
118
+ }
119
+
120
+ function isToday(iso: string): boolean {
121
+ return iso === isoOf(today);
122
+ }
123
+
124
+ // Seleção: date compara o dia; month compara o mês (destaca o mês inteiro).
125
+ function isSelected(iso: string): boolean {
126
+ if (!value) return false;
127
+ return mode === 'month' ? iso.slice(0, 7) === value : iso === value;
128
+ }
129
+
130
+ function inRange(iso: string): boolean {
131
+ if (min && iso.slice(0, 7) < min.slice(0, 7)) return false;
132
+ if (max && iso.slice(0, 7) > max.slice(0, 7)) return false;
133
+ return true;
134
+ }
135
+
136
+ function displayValue(): string {
137
+ if (!value) return '';
138
+ const [y, m] = value.split('-').map(Number);
139
+ if (mode === 'month') {
140
+ return new Date(y, m - 1, 1).toLocaleDateString(locale, { month: 'long', year: 'numeric' });
141
+ }
142
+ return new Date(y, m - 1, 1).toLocaleDateString(locale, {
143
+ day: '2-digit',
144
+ month: 'short',
145
+ year: 'numeric'
146
+ });
147
+ }
148
+ </script>
149
+
150
+ <div
151
+ class="twui-date-wrap {className}"
152
+ bind:this={rootEl}
153
+ use:clickOutside={() => (open = false)}
154
+ >
155
+ <button
156
+ type="button"
157
+ class="twui-date-trigger"
158
+ id={rest.id}
159
+ {...rest}
160
+ disabled={disabled}
161
+ aria-haspopup="dialog"
162
+ aria-expanded={open}
163
+ onclick={toggle}
164
+ >
165
+ <svg
166
+ class="twui-date-calendar-icon"
167
+ width="16"
168
+ height="16"
169
+ viewBox="0 0 24 24"
170
+ fill="none"
171
+ stroke="currentColor"
172
+ stroke-width="2"
173
+ stroke-linecap="round"
174
+ stroke-linejoin="round"
175
+ aria-hidden="true"
176
+ ><rect x="3" y="4" width="18" height="18" rx="2" /><path d="M16 2v4M8 2v4M3 10h18" /></svg>
177
+ <span class="twui-date-value {value ? '' : 'twui-date-placeholder'}">
178
+ {value ? displayValue() : placeholder}
179
+ </span>
180
+ </button>
181
+
182
+ {#if name}
183
+ <input type="hidden" {name} value={value} {required} />
184
+ {/if}
185
+
186
+ {#if open}
187
+ <div
188
+ class="twui-date-popover"
189
+ role="dialog"
190
+ transition:fade={{ duration: 100 }}
191
+ >
192
+ <div class="twui-date-nav">
193
+ <button type="button" class="twui-date-nav-btn" onclick={prev} aria-label={prevLabel}>
194
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6" /></svg>
195
+ </button>
196
+ <span class="twui-date-nav-label">{monthLabel}</span>
197
+ <button type="button" class="twui-date-nav-btn" onclick={next} aria-label={nextLabel}>
198
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6" /></svg>
199
+ </button>
200
+ </div>
201
+
202
+ <div class="twui-date-weekdays">
203
+ {#each weekdays as w, i (i)}<span>{w}</span>{/each}
204
+ </div>
205
+ <div class="twui-date-grid">
206
+ {#each days as cell, i (i)}
207
+ <button
208
+ type="button"
209
+ class="twui-date-cell {cell.inMonth ? '' : 'twui-date-cell-out'} {isToday(cell.iso)
210
+ ? 'twui-date-cell-today'
211
+ : ''} {isSelected(cell.iso) ? 'twui-date-cell-selected' : ''}"
212
+ disabled={!inRange(cell.iso)}
213
+ onclick={() => pick(cell.iso)}
214
+ >
215
+ {cell.day}
216
+ </button>
217
+ {/each}
218
+ </div>
219
+ </div>
220
+ {/if}
221
+ </div>
222
+
223
+ <style>
224
+ .twui-date-wrap {
225
+ position: relative;
226
+ display: block;
227
+ width: 100%;
228
+ }
229
+
230
+ .twui-date-trigger {
231
+ display: flex;
232
+ align-items: center;
233
+ gap: 8px;
234
+ width: 100%;
235
+ padding: 8px 12px;
236
+ border: 1px solid var(--twui-rule);
237
+ background: var(--twui-paper);
238
+ font-family: var(--twui-font-mono);
239
+ font-size: 14px;
240
+ text-align: left;
241
+ color: var(--twui-ink);
242
+ cursor: pointer;
243
+ transition:
244
+ border-color 0.15s ease,
245
+ box-shadow 0.15s ease;
246
+ }
247
+
248
+ .twui-date-trigger:focus-visible {
249
+ border-color: var(--twui-accent);
250
+ }
251
+
252
+ .twui-date-trigger:disabled {
253
+ opacity: 0.5;
254
+ cursor: not-allowed;
255
+ }
256
+
257
+ .twui-date-calendar-icon {
258
+ flex-shrink: 0;
259
+ color: var(--twui-ink-soft);
260
+ }
261
+
262
+ .twui-date-value {
263
+ overflow: hidden;
264
+ text-overflow: ellipsis;
265
+ white-space: nowrap;
266
+ }
267
+
268
+ .twui-date-placeholder {
269
+ color: var(--twui-ink-faint);
270
+ }
271
+
272
+ .twui-date-popover {
273
+ position: absolute;
274
+ top: calc(100% + 4px);
275
+ left: 0;
276
+ z-index: 50;
277
+ width: 264px;
278
+ padding: 8px;
279
+ border: 1px solid var(--twui-rule);
280
+ background: var(--twui-paper-raised);
281
+ box-shadow: var(--twui-shadow-offset, 3px 3px 0 0 var(--twui-rule));
282
+ }
283
+
284
+ .twui-date-nav {
285
+ display: flex;
286
+ align-items: center;
287
+ justify-content: space-between;
288
+ margin-bottom: 8px;
289
+ }
290
+
291
+ .twui-date-nav-label {
292
+ font-family: var(--twui-font-mono);
293
+ font-size: 13px;
294
+ font-weight: 500;
295
+ color: var(--twui-ink);
296
+ text-transform: capitalize;
297
+ }
298
+
299
+ .twui-date-nav-btn {
300
+ display: inline-flex;
301
+ align-items: center;
302
+ justify-content: center;
303
+ width: 26px;
304
+ height: 26px;
305
+ border: 1px solid var(--twui-rule);
306
+ background: transparent;
307
+ color: var(--twui-ink-soft);
308
+ cursor: pointer;
309
+ }
310
+
311
+ .twui-date-nav-btn:hover {
312
+ border-color: var(--twui-accent);
313
+ color: var(--twui-accent);
314
+ }
315
+
316
+ .twui-date-weekdays {
317
+ display: grid;
318
+ grid-template-columns: repeat(7, 1fr);
319
+ margin-bottom: 4px;
320
+ }
321
+
322
+ .twui-date-weekdays span {
323
+ font-family: var(--twui-font-mono);
324
+ font-size: 11px;
325
+ text-align: center;
326
+ color: var(--twui-ink-faint);
327
+ }
328
+
329
+ .twui-date-grid {
330
+ display: grid;
331
+ grid-template-columns: repeat(7, 1fr);
332
+ gap: 2px;
333
+ }
334
+
335
+ .twui-date-cell {
336
+ display: flex;
337
+ align-items: center;
338
+ justify-content: center;
339
+ height: 30px;
340
+ border: 1px solid transparent;
341
+ background: transparent;
342
+ font-family: var(--twui-font-mono);
343
+ font-size: 12px;
344
+ color: var(--twui-ink);
345
+ cursor: pointer;
346
+ }
347
+
348
+ .twui-date-cell:hover:not(:disabled):not(.twui-date-cell-selected) {
349
+ border-color: var(--twui-rule);
350
+ background: var(--twui-paper);
351
+ color: var(--twui-accent);
352
+ }
353
+
354
+ .twui-date-cell:disabled {
355
+ opacity: 0.3;
356
+ cursor: not-allowed;
357
+ }
358
+
359
+ .twui-date-cell-out {
360
+ color: var(--twui-ink-faint);
361
+ }
362
+
363
+ .twui-date-cell-today {
364
+ border-color: var(--twui-accent);
365
+ }
366
+
367
+ .twui-date-cell-selected {
368
+ background: var(--twui-accent);
369
+ color: var(--twui-paper);
370
+ font-weight: 500;
371
+ }
372
+ </style>
@@ -0,0 +1,21 @@
1
+ type $$ComponentProps = {
2
+ value?: string;
3
+ label?: string;
4
+ name?: string;
5
+ mode?: 'date' | 'month';
6
+ min?: string;
7
+ max?: string;
8
+ disabled?: boolean;
9
+ required?: boolean;
10
+ placeholder?: string;
11
+ locale?: string;
12
+ prevLabel?: string;
13
+ nextLabel?: string;
14
+ class?: string;
15
+ } & {
16
+ 'aria-label'?: string;
17
+ id?: string;
18
+ };
19
+ declare const DatePicker: import("svelte").Component<$$ComponentProps, {}, "value">;
20
+ type DatePicker = ReturnType<typeof DatePicker>;
21
+ export default DatePicker;