@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.
- package/LICENSE +661 -0
- package/README.md +222 -0
- package/README.pt-BR.md +217 -0
- package/dist/actions/click-outside.d.ts +3 -0
- package/dist/actions/click-outside.js +12 -0
- package/dist/actions/combobox-keyboard-nav.d.ts +9 -0
- package/dist/actions/combobox-keyboard-nav.js +28 -0
- package/dist/actions/focus-trap.d.ts +1 -0
- package/dist/actions/focus-trap.js +34 -0
- package/dist/actions/scroll-lock.d.ts +1 -0
- package/dist/actions/scroll-lock.js +17 -0
- package/dist/components/actions/Button.svelte +169 -0
- package/dist/components/actions/Button.svelte.d.ts +19 -0
- package/dist/components/appshell/AppShell.svelte +50 -0
- package/dist/components/appshell/AppShell.svelte.d.ts +19 -0
- package/dist/components/appshell/AppShellBottomNav.svelte +67 -0
- package/dist/components/appshell/AppShellBottomNav.svelte.d.ts +9 -0
- package/dist/components/appshell/AppShellContent.svelte +38 -0
- package/dist/components/appshell/AppShellContent.svelte.d.ts +10 -0
- package/dist/components/appshell/AppShellSidebar.svelte +196 -0
- package/dist/components/appshell/AppShellSidebar.svelte.d.ts +15 -0
- package/dist/components/appshell/types.d.ts +6 -0
- package/dist/components/appshell/types.js +1 -0
- package/dist/components/appshell/utils.d.ts +1 -0
- package/dist/components/appshell/utils.js +3 -0
- package/dist/components/card/Card.svelte +31 -0
- package/dist/components/card/Card.svelte.d.ts +9 -0
- package/dist/components/card/CardAction.svelte +57 -0
- package/dist/components/card/CardAction.svelte.d.ts +10 -0
- package/dist/components/card/CardContent.svelte +25 -0
- package/dist/components/card/CardContent.svelte.d.ts +8 -0
- package/dist/components/card/CardDescription.svelte +24 -0
- package/dist/components/card/CardDescription.svelte.d.ts +8 -0
- package/dist/components/card/CardFooter.svelte +25 -0
- package/dist/components/card/CardFooter.svelte.d.ts +8 -0
- package/dist/components/card/CardHeader.svelte +54 -0
- package/dist/components/card/CardHeader.svelte.d.ts +10 -0
- package/dist/components/card/CardTitle.svelte +25 -0
- package/dist/components/card/CardTitle.svelte.d.ts +8 -0
- package/dist/components/chat/ChatMessage.svelte +104 -0
- package/dist/components/chat/ChatMessage.svelte.d.ts +10 -0
- package/dist/components/choice/Listbox.svelte +183 -0
- package/dist/components/choice/Listbox.svelte.d.ts +15 -0
- package/dist/components/choice/MultiSelect.svelte +278 -0
- package/dist/components/choice/MultiSelect.svelte.d.ts +18 -0
- package/dist/components/choice/Rating.svelte +91 -0
- package/dist/components/choice/Rating.svelte.d.ts +12 -0
- package/dist/components/choice/Toggle.svelte +112 -0
- package/dist/components/choice/Toggle.svelte.d.ts +12 -0
- package/dist/components/feedback/Badge.svelte +66 -0
- package/dist/components/feedback/Badge.svelte.d.ts +9 -0
- package/dist/components/feedback/ErrorPage.svelte +99 -0
- package/dist/components/feedback/ErrorPage.svelte.d.ts +13 -0
- package/dist/components/feedback/Message.svelte +130 -0
- package/dist/components/feedback/Message.svelte.d.ts +14 -0
- package/dist/components/feedback/ProgressBar.svelte +69 -0
- package/dist/components/feedback/ProgressBar.svelte.d.ts +8 -0
- package/dist/components/feedback/Skeleton.svelte +47 -0
- package/dist/components/feedback/Skeleton.svelte.d.ts +9 -0
- package/dist/components/feedback/Status.svelte +75 -0
- package/dist/components/feedback/Status.svelte.d.ts +10 -0
- package/dist/components/feedback/StatusPill.svelte +94 -0
- package/dist/components/feedback/StatusPill.svelte.d.ts +13 -0
- package/dist/components/feedback/Toaster.svelte +122 -0
- package/dist/components/feedback/Toaster.svelte.d.ts +7 -0
- package/dist/components/feedback/Tooltip.svelte +126 -0
- package/dist/components/feedback/Tooltip.svelte.d.ts +10 -0
- package/dist/components/feedback/toast.svelte.d.ts +27 -0
- package/dist/components/feedback/toast.svelte.js +23 -0
- package/dist/components/forms/Checkbox.svelte +96 -0
- package/dist/components/forms/Checkbox.svelte.d.ts +11 -0
- package/dist/components/forms/DatePicker.svelte +372 -0
- package/dist/components/forms/DatePicker.svelte.d.ts +21 -0
- package/dist/components/forms/Field.svelte +47 -0
- package/dist/components/forms/Field.svelte.d.ts +11 -0
- package/dist/components/forms/FileUpload.svelte +504 -0
- package/dist/components/forms/FileUpload.svelte.d.ts +43 -0
- package/dist/components/forms/Input.svelte +93 -0
- package/dist/components/forms/Input.svelte.d.ts +9 -0
- package/dist/components/forms/Label.svelte +30 -0
- package/dist/components/forms/Label.svelte.d.ts +10 -0
- package/dist/components/forms/Radio.svelte +80 -0
- package/dist/components/forms/Radio.svelte.d.ts +11 -0
- package/dist/components/forms/Select.svelte +322 -0
- package/dist/components/forms/Select.svelte.d.ts +22 -0
- package/dist/components/forms/TagInput.svelte +336 -0
- package/dist/components/forms/TagInput.svelte.d.ts +16 -0
- package/dist/components/forms/Textarea.svelte +64 -0
- package/dist/components/forms/Textarea.svelte.d.ts +9 -0
- package/dist/components/landing/Landing.svelte +47 -0
- package/dist/components/landing/Landing.svelte.d.ts +8 -0
- package/dist/components/landing/LandingFeatures.svelte +92 -0
- package/dist/components/landing/LandingFeatures.svelte.d.ts +15 -0
- package/dist/components/landing/LandingHero.svelte +92 -0
- package/dist/components/landing/LandingHero.svelte.d.ts +13 -0
- package/dist/components/landing/LandingRoadmap.svelte +42 -0
- package/dist/components/landing/LandingRoadmap.svelte.d.ts +11 -0
- package/dist/components/landing/LandingSection.svelte +25 -0
- package/dist/components/landing/LandingSection.svelte.d.ts +9 -0
- package/dist/components/landing/LandingSectionHeading.svelte +19 -0
- package/dist/components/landing/LandingSectionHeading.svelte.d.ts +10 -0
- package/dist/components/landing/LandingSteps.svelte +100 -0
- package/dist/components/landing/LandingSteps.svelte.d.ts +13 -0
- package/dist/components/layout/BracketLink.svelte +41 -0
- package/dist/components/layout/BracketLink.svelte.d.ts +9 -0
- package/dist/components/layout/Divider.svelte +70 -0
- package/dist/components/layout/Divider.svelte.d.ts +9 -0
- package/dist/components/layout/Eyebrow.svelte +24 -0
- package/dist/components/layout/Eyebrow.svelte.d.ts +8 -0
- package/dist/components/layout/PageHeader.svelte +106 -0
- package/dist/components/layout/PageHeader.svelte.d.ts +15 -0
- package/dist/components/layout/PageSeo.svelte +34 -0
- package/dist/components/layout/PageSeo.svelte.d.ts +10 -0
- package/dist/components/layout/PageShell.svelte +25 -0
- package/dist/components/layout/PageShell.svelte.d.ts +8 -0
- package/dist/components/layout/Panel.svelte +105 -0
- package/dist/components/layout/Panel.svelte.d.ts +12 -0
- package/dist/components/layout/Prose.svelte +101 -0
- package/dist/components/layout/Prose.svelte.d.ts +7 -0
- package/dist/components/layout/RuleCard.svelte +63 -0
- package/dist/components/layout/RuleCard.svelte.d.ts +13 -0
- package/dist/components/layout/SectionHeading.svelte +57 -0
- package/dist/components/layout/SectionHeading.svelte.d.ts +10 -0
- package/dist/components/layout/StatTile.svelte +76 -0
- package/dist/components/layout/StatTile.svelte.d.ts +12 -0
- package/dist/components/layout/TerminalWindow.svelte +81 -0
- package/dist/components/layout/TerminalWindow.svelte.d.ts +9 -0
- package/dist/components/layout/Wordmark.svelte +26 -0
- package/dist/components/layout/Wordmark.svelte.d.ts +8 -0
- package/dist/components/list/List.svelte +40 -0
- package/dist/components/list/List.svelte.d.ts +13 -0
- package/dist/components/list/ListItem.svelte +81 -0
- package/dist/components/list/ListItem.svelte.d.ts +13 -0
- package/dist/components/marketing/MarketingShell.svelte +170 -0
- package/dist/components/marketing/MarketingShell.svelte.d.ts +23 -0
- package/dist/components/marketing/MarketingShellFooter.svelte +92 -0
- package/dist/components/marketing/MarketingShellFooter.svelte.d.ts +14 -0
- package/dist/components/navigation/Accordion.svelte +110 -0
- package/dist/components/navigation/Accordion.svelte.d.ts +15 -0
- package/dist/components/navigation/Carousel.svelte +115 -0
- package/dist/components/navigation/Carousel.svelte.d.ts +12 -0
- package/dist/components/navigation/InstructionStep.svelte +115 -0
- package/dist/components/navigation/InstructionStep.svelte.d.ts +10 -0
- package/dist/components/navigation/InstructionSteps.svelte +39 -0
- package/dist/components/navigation/InstructionSteps.svelte.d.ts +8 -0
- package/dist/components/navigation/Nav.svelte +102 -0
- package/dist/components/navigation/Nav.svelte.d.ts +14 -0
- package/dist/components/navigation/Stepper.svelte +112 -0
- package/dist/components/navigation/Stepper.svelte.d.ts +12 -0
- package/dist/components/navigation/TabCard.svelte +62 -0
- package/dist/components/navigation/TabCard.svelte.d.ts +10 -0
- package/dist/components/navigation/Tabs.svelte +83 -0
- package/dist/components/navigation/Tabs.svelte.d.ts +12 -0
- package/dist/components/navigation/ThemeToggle.svelte +118 -0
- package/dist/components/navigation/ThemeToggle.svelte.d.ts +20 -0
- package/dist/components/overlay/Dialog.svelte +200 -0
- package/dist/components/overlay/Dialog.svelte.d.ts +19 -0
- package/dist/components/overlay/DialogActions.svelte +50 -0
- package/dist/components/overlay/DialogActions.svelte.d.ts +9 -0
- package/dist/components/overlay/Dropdown.svelte +110 -0
- package/dist/components/overlay/Dropdown.svelte.d.ts +10 -0
- package/dist/components/overlay/FloatingActionPill.svelte +96 -0
- package/dist/components/overlay/FloatingActionPill.svelte.d.ts +14 -0
- package/dist/components/overlay/Sidebar.svelte +258 -0
- package/dist/components/overlay/Sidebar.svelte.d.ts +18 -0
- package/dist/components/table/Table.svelte +550 -0
- package/dist/components/table/Table.svelte.d.ts +42 -0
- package/dist/components/timeline/Timeline.svelte +25 -0
- package/dist/components/timeline/Timeline.svelte.d.ts +8 -0
- package/dist/components/timeline/TimelineItem.svelte +220 -0
- package/dist/components/timeline/TimelineItem.svelte.d.ts +18 -0
- package/dist/index.d.ts +156 -0
- package/dist/index.js +149 -0
- package/dist/theme/theme.css +348 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# TAbelhaWebUI
|
|
4
|
+
|
|
5
|
+
**Shared theme and chrome for ianptkcs's web apps — Catppuccin (Latte/Mocha)
|
|
6
|
+
with a "reading someone's source file" aesthetic: mono carries the structure,
|
|
7
|
+
sharp borders, configurable accent.**
|
|
8
|
+
|
|
9
|
+
**English** · [Português](README.pt-BR.md)
|
|
10
|
+
|
|
11
|
+
[](https://kit.svelte.dev)
|
|
12
|
+
[](https://workers.cloudflare.com)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
|
|
15
|
+
[](https://ko-fi.com/ianptkcs)
|
|
16
|
+
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What it is
|
|
22
|
+
|
|
23
|
+
The web counterpart of [`tabelhatuiui`](https://github.com/TAbelhaDev/tabelhatuiui)
|
|
24
|
+
(the Bubble Tea TUIs): each app keeps only its own business logic, and the
|
|
25
|
+
library takes care of the theme and of the components everyone was redrawing
|
|
26
|
+
from scratch.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
Requires Svelte 5.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bun add @tabelhadev/tabelhawebui
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
Import the theme once in the app and use the components:
|
|
39
|
+
|
|
40
|
+
```svelte
|
|
41
|
+
<script>
|
|
42
|
+
import { Card, Table, Badge, Button, Status } from '@tabelhadev/tabelhawebui';
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<Card>
|
|
46
|
+
<Card.Header>
|
|
47
|
+
<Card.Title>Resumo</Card.Title>
|
|
48
|
+
<Card.Description>mês atual</Card.Description>
|
|
49
|
+
</Card.Header>
|
|
50
|
+
<Card.Content>
|
|
51
|
+
<Status kind="success">configurado</Status>
|
|
52
|
+
<Button variant="primary">+ Nova transação</Button>
|
|
53
|
+
<Table columns={['Data', 'Descrição']} rows={[{ Data: '01/08', Descrição: 'Mercado' }]} />
|
|
54
|
+
</Card.Content>
|
|
55
|
+
</Card>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
In the app's CSS (once):
|
|
59
|
+
|
|
60
|
+
```css
|
|
61
|
+
@import "@tabelhadev/tabelhawebui/theme.css";
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Components
|
|
65
|
+
|
|
66
|
+
| Component | What it is |
|
|
67
|
+
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
68
|
+
| `Card` | panel with header and content; composed API `Card.Header`/`Title`/`Description`/`Content`/`Footer` |
|
|
69
|
+
| `TabCard` | "file tab" panel (page chrome) |
|
|
70
|
+
| `Table` | table with `columns`/`rows`, `cell` snippet, sort, global filter, selection, loading/skeleton, empty and pagination (with `pageSizeOptions`; `pageSize={0}` shows every row unpaginated) |
|
|
71
|
+
| `Badge` | small text tag — `default` / `secondary` / `outline` variants |
|
|
72
|
+
| `Button` | `default` / `primary` / `ghost` / `danger` / `outline` variants, `size` (default/sm/lg/icon-sm), `href`, `loading`; `buttonVariants()` |
|
|
73
|
+
| `Panel` | container with `focused` → accent border; `toggleable` collapsible |
|
|
74
|
+
| `Status` | semantic `success` / `warning` / `error` / `info`, with optional `icon` |
|
|
75
|
+
| `Input` | text field (`password`, `file` with `bind:files`, ...), `invalid` |
|
|
76
|
+
| `Textarea` | multi-line text area (`bind:value`, `rows`, `resize: vertical`, `invalid`) |
|
|
77
|
+
| `Label` | 13px mono label |
|
|
78
|
+
| `Field` | form wrapper: label + children (input/textarea/select) + optional error message |
|
|
79
|
+
| `Radio` | single choice: hidden input + circular indicator (accent when checked, focus via `:has()`) |
|
|
80
|
+
| `Checkbox` | multiple choice: hidden input + square with `✓` |
|
|
81
|
+
| `Toggle` | on/off switch (hidden checkbox + `switch` role, `invalid`) |
|
|
82
|
+
| `Select` | custom listbox with `options`, `bind:value`, `name`, keyboard navigation and `filter` |
|
|
83
|
+
| `MultiSelect` | multiple selection in a popover with checkboxes, `filter`, hidden input with `name` |
|
|
84
|
+
| `Listbox` | visible option list (single/multiple), `filter`, `checkmark` |
|
|
85
|
+
| `DatePicker` | date/month picker with a calendar popover, `bind:value`, `min`/`max`, `locale` |
|
|
86
|
+
| `FileUpload` | file picker with drag-and-drop (`basic`/`advanced`), `accept`/`maxFileSize`/`fileLimit` validation, per-file progress, `customUpload` + `uploadHandler` (no built-in network), snippets |
|
|
87
|
+
| `TagInput` | tag/etiqueta field: removable chips, autocomplete/filter of `options`, create-on-the-fly (Enter or create action), Backspace removes the last chip |
|
|
88
|
+
| `Toaster` | renders toasts + the `toast` store; configurable `position` |
|
|
89
|
+
| `Timeline` | timeline (`Timeline.Item`s with rail, dot, skills) |
|
|
90
|
+
| `RuleCard` | card with a top border, icon and serif prose |
|
|
91
|
+
| `Nav` | navbar: logo + breadcrumb links separated by `/` |
|
|
92
|
+
| `ThemeToggle` | toggles `data-theme`/`.dark` + `localStorage`; optional `showLabel` for a visible light/dark label |
|
|
93
|
+
| `AppShell` | authenticated app shell as one compound export: `AppShell.Sidebar` (desktop: brand + nav + theme/logout), `AppShell.BottomNav` (mobile), `AppShell.Content` (`max-w-6xl`) |
|
|
94
|
+
| `Wordmark` | `TAbelha` in text color + `suffix` in accent; inherits the context font/size |
|
|
95
|
+
| `Dropdown` | menu with a printed shadow; closes on outside click/Esc |
|
|
96
|
+
| `Divider` | horizontal/vertical line, `solid`/`dashed`, centered label |
|
|
97
|
+
| `Eyebrow` | uppercase mono section label |
|
|
98
|
+
| `BracketLink` | mono `[ label ]` link, brackets light up on hover |
|
|
99
|
+
| `Message` | inline `info`/`success`/`warn`/`error` alert, `closable`, `life` (auto-dismiss) |
|
|
100
|
+
| `ProgressBar` | determinate/indeterminate progress bar with a label |
|
|
101
|
+
| `ErrorPage` | error page display (`status` in mono/accent, `title`, `description`, `homeHref`/`homeLabel`, `actions` snippet) — used inside `+error.svelte` |
|
|
102
|
+
| `Skeleton` | shimmer placeholder (`width`/`height`/`rounded`) |
|
|
103
|
+
| `Tooltip` | CSS tooltip via `data-tooltip` (`top`/`bottom`/`left`/`right`) |
|
|
104
|
+
| `Dialog` | modal with overlay, `aria-modal`, Esc/outside to close, `footer` snippet, `animate` prop for enter/exit transitions |
|
|
105
|
+
| `Sidebar` | side/`top`/`bottom` panel; `mode` overlay (drawer) or `push` (static, desktop), `collapsible`+`collapsed`, `overlay` off, breakpoint 768 (becomes a full-width drawer on mobile), animated; focus trap + scroll-lock in overlay/mobile mode |
|
|
106
|
+
| `Tabs` | tab bar with bindable `value`, `disabled`, keyboard navigation |
|
|
107
|
+
| `Accordion` | collapsible panels (`multiple`, bindable `value`) |
|
|
108
|
+
| `Stepper` | step indicator (onboarding) with bindable `value` |
|
|
109
|
+
| `Carousel` | carousel with bindable `page`, dots and arrows |
|
|
110
|
+
| `Rating` | stars (`max`, `readonly`, `cancel`) |
|
|
111
|
+
| `StatusPill` | fixed toast-style pill (`position`, `closable`, bindable `visible`) |
|
|
112
|
+
| `FloatingActionPill` | clickable floating pill (`position`, `label`, `expanded` → `aria-expanded`, `animate` for scale+fade transition) — StatusPill's styling, but a `<button>` that opens a panel/dialog |
|
|
113
|
+
| `ChatMessage` | chat bubble (`role` user/assistant, `content`, `name`, `streaming` → pulsing ellipsis) — presentational render, no SSE logic |
|
|
114
|
+
| `Landing` | landing sections as one compound export: `Landing.Hero`, `Landing.Steps` (numbered cards), `Landing.Features`, `Landing.Roadmap`, `Landing.Footer` (`name` + optional `tagline`/`license`/repo link); `Hero`/`Steps`/`Features` take `justify` for justified serif prose |
|
|
115
|
+
| `SectionHeading` | section title: eyebrow + h2 + lead (optional `justify` for justified serif lead) |
|
|
116
|
+
| `TerminalWindow` | terminal window: dot bar + `title` + content (`children`) |
|
|
117
|
+
| `PageHeader` | page header: `title`, optional `subtitle` (with `//` prefix), optional `action` snippet on the right side |
|
|
118
|
+
| `StatTile` | stat card: `label`, `value`, optional `href` (label becomes link), `valueClass` for color, `footer` snippet for delta/sub-label |
|
|
119
|
+
|
|
120
|
+
## Tokens
|
|
121
|
+
|
|
122
|
+
The theme exposes `--twui-*` custom properties:
|
|
123
|
+
|
|
124
|
+
- Semantic: `--twui-paper`, `--twui-paper-raised`, `--twui-ink`,
|
|
125
|
+
`--twui-ink-soft`, `--twui-ink-faint`, `--twui-rule`, `--twui-accent`,
|
|
126
|
+
`--twui-accent-soft`, `--twui-signal`, `--twui-signal-soft`, `--twui-danger`
|
|
127
|
+
- Fonts: `--twui-font-mono`, `--twui-font-serif`
|
|
128
|
+
- Shadow: `--twui-shadow-offset` (hard 3px offset)
|
|
129
|
+
- Palette for the active flavour: `--twui-base`, `--twui-surface0`,
|
|
130
|
+
`--twui-text`, `--twui-red`, `--twui-green`, `--twui-mauve`, ... (via
|
|
131
|
+
`--twui-<color>`)
|
|
132
|
+
- Raw colours per flavour: `--twui-latte-*` and `--twui-mocha-*` (the full scale
|
|
133
|
+
— neutrals + Catppuccin's 14 accents)
|
|
134
|
+
|
|
135
|
+
Dark theme via `[data-theme="dark"]` **or** a `.dark` class on `<html>`. The
|
|
136
|
+
theme applies `body` defaults (background, colour, serif font) and respects
|
|
137
|
+
`prefers-reduced-motion`.
|
|
138
|
+
|
|
139
|
+
## Per-app custom accent
|
|
140
|
+
|
|
141
|
+
The default accent is maroon (light) / pink (dark). Each app can have its own by
|
|
142
|
+
**picking a name** through `data-accent` on `<html>` — the library resolves the
|
|
143
|
+
Latte/Mocha pair automatically:
|
|
144
|
+
|
|
145
|
+
```html
|
|
146
|
+
<html data-accent="teal"></html>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`--twui-accent-soft` derives on its own (via `color-mix`); the semantic
|
|
150
|
+
`--twui-signal`/`--twui-danger` and the neutrals do not change. Available
|
|
151
|
+
accents:
|
|
152
|
+
|
|
153
|
+
| Accent | Latte (light) | Mocha (dark) |
|
|
154
|
+
| ------------------ | ------------- | ------------ |
|
|
155
|
+
| `maroon` (default) | `#e64553` | `#eba0ac` |
|
|
156
|
+
| `mauve` | `#8839ef` | `#cba6f7` |
|
|
157
|
+
| `pink` | `#ea76cb` | `#f5c2e7` |
|
|
158
|
+
| `red` | `#d20f39` | `#f38ba8` |
|
|
159
|
+
| `peach` | `#fe640b` | `#fab387` |
|
|
160
|
+
| `yellow` | `#df8e1d` | `#f9e2af` |
|
|
161
|
+
| `green` | `#40a02b` | `#a6e3a1` |
|
|
162
|
+
| `teal` | `#179299` | `#94e2d5` |
|
|
163
|
+
| `lavender` | `#7287fd` | `#b4befe` |
|
|
164
|
+
| `rosewater` | `#dc8a78` | `#f5e0dc` |
|
|
165
|
+
| `flamingo` | `#dd7878` | `#f2cdcd` |
|
|
166
|
+
| `blue` | `#1e66f5` | `#89b4fa` |
|
|
167
|
+
| `sky` | `#04a5e5` | `#89dceb` |
|
|
168
|
+
| `sapphire` | `#209fb5` | `#74c7ec` |
|
|
169
|
+
|
|
170
|
+
(Escape hatch: anyone needing a colour outside the list can override
|
|
171
|
+
`--twui-accent` directly in `layout.css`, in both themes.)
|
|
172
|
+
|
|
173
|
+
## Development
|
|
174
|
+
|
|
175
|
+
Stack: Svelte 5 + SvelteKit, Bun as the package manager.
|
|
176
|
+
|
|
177
|
+
```sh
|
|
178
|
+
bun install
|
|
179
|
+
bun run check # typecheck (svelte-kit sync + svelte-check)
|
|
180
|
+
bun run lint # prettier
|
|
181
|
+
bun run build # svelte-package (dist)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The package is published to npm as `@tabelhadev/tabelhawebui`; versions follow
|
|
185
|
+
`CHANGELOG.md` and the convention in `CONTRIBUTING.md`.
|
|
186
|
+
|
|
187
|
+
## Tooling
|
|
188
|
+
|
|
189
|
+
- **MCP server** (`mcp/`): exposes components, tokens and accents as MCP tools
|
|
190
|
+
for coding agents (props with types, light/dark `--twui-*`, `data-accent`).
|
|
191
|
+
See `mcp/README.md` to install and run it.
|
|
192
|
+
|
|
193
|
+
## Requesting a component
|
|
194
|
+
|
|
195
|
+
Feature requests from other agents live in `requests/` as markdown files — see
|
|
196
|
+
`requests/README.md` for the flow, and `requests/_template.md` for what to fill
|
|
197
|
+
in. From outside this repo, open a feature request issue instead.
|
|
198
|
+
|
|
199
|
+
## Changelog
|
|
200
|
+
|
|
201
|
+
See [CHANGELOG.md](CHANGELOG.md) for the version history.
|
|
202
|
+
|
|
203
|
+
## Support the project
|
|
204
|
+
|
|
205
|
+
- **Global**: [ko-fi.com/ianptkcs](https://ko-fi.com/ianptkcs)
|
|
206
|
+
- **Brazil (Pix)**: scan the QR below or copy the code
|
|
207
|
+
|
|
208
|
+
<img src="pix-qr.png" alt="Pix QR" width="200" />
|
|
209
|
+
|
|
210
|
+
<details><summary>Pix code (copy)</summary>
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
00020126580014BR.GOV.BCB.PIX01365ad933b0-dcdc-4525-a736-0759902aeec65204000053039865802BR5925Ian Patrick da Costa Soar6009SAO PAULO62140510tQA85x6Dov63041FB6
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
</details>
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
[AGPL-3.0](LICENSE) — strong copyleft: you may use, modify and even host
|
|
221
|
+
TAbelhaWebUI commercially, but any modified version, including one running as a
|
|
222
|
+
network service (SaaS), has to stay open source under the same license.
|
package/README.pt-BR.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# TAbelhaWebUI
|
|
4
|
+
|
|
5
|
+
**Tema + chrome compartilhado dos apps web do ianptkcs — Catppuccin (Latte/Mocha)
|
|
6
|
+
com a estética "reading someone's source file": mono leva a estrutura, bordas
|
|
7
|
+
afiadas, acento configurável.**
|
|
8
|
+
|
|
9
|
+
[English](README.md) · **Português**
|
|
10
|
+
|
|
11
|
+
[](https://kit.svelte.dev)
|
|
12
|
+
[](https://workers.cloudflare.com)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
|
|
15
|
+
[](https://ko-fi.com/ianptkcs)
|
|
16
|
+
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## O que é
|
|
22
|
+
|
|
23
|
+
Equivalente web do [`tabelhatuiui`](https://github.com/TAbelhaDev/tabelhatuiui)
|
|
24
|
+
(os TUIs Bubble Tea): cada app mantém só o negócio dele; a lib cuida do tema e
|
|
25
|
+
dos componentes que todo mundo desenhava do zero.
|
|
26
|
+
|
|
27
|
+
## Instalação
|
|
28
|
+
|
|
29
|
+
Requer Svelte 5.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bun add @tabelhadev/tabelhawebui
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Uso
|
|
36
|
+
|
|
37
|
+
Importe o tema uma vez no app e use os componentes:
|
|
38
|
+
|
|
39
|
+
```svelte
|
|
40
|
+
<script>
|
|
41
|
+
import { Card, Table, Badge, Button, Status } from '@tabelhadev/tabelhawebui';
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<Card>
|
|
45
|
+
<Card.Header>
|
|
46
|
+
<Card.Title>Resumo</Card.Title>
|
|
47
|
+
<Card.Description>mês atual</Card.Description>
|
|
48
|
+
</Card.Header>
|
|
49
|
+
<Card.Content>
|
|
50
|
+
<Status kind="success">configurado</Status>
|
|
51
|
+
<Button variant="primary">+ Nova transação</Button>
|
|
52
|
+
<Table columns={['Data', 'Descrição']} rows={[{ Data: '01/08', Descrição: 'Mercado' }]} />
|
|
53
|
+
</Card.Content>
|
|
54
|
+
</Card>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
No CSS do app (uma vez):
|
|
58
|
+
|
|
59
|
+
```css
|
|
60
|
+
@import "@tabelhadev/tabelhawebui/theme.css";
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Componentes
|
|
64
|
+
|
|
65
|
+
| Componente | O que é |
|
|
66
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
67
|
+
| `Card` | painel com header e conteúdo; API composta `Card.Header`/`Title`/`Description`/`Content`/`Footer` |
|
|
68
|
+
| `TabCard` | painel "aba de arquivo" (chrome de página) |
|
|
69
|
+
| `Table` | tabelha com `columns`/`rows`, snippet `cell`, sort, filtro global, selection, loading/skeleton, empty e paginação (com `pageSizeOptions`; `pageSize={0}` mostra todas as linhas sem paginação) |
|
|
70
|
+
| `Badge` | tag de texto pequena — variantes `default` / `secondary` / `outline` |
|
|
71
|
+
| `Button` | variantes `default` / `primary` / `ghost` / `danger` / `outline`, `size` (default/sm/lg/icon-sm), `href`, `loading`; `buttonVariants()` |
|
|
72
|
+
| `Panel` | contêiner com `focused` → borda accent; `toggleable` colapsável |
|
|
73
|
+
| `Status` | semântico `success` / `warning` / `error` / `info`, com `icon` opcional |
|
|
74
|
+
| `Input` | campo de texto (`password`, `file` com `bind:files`, ...), `invalid` |
|
|
75
|
+
| `Textarea` | área de texto multi-linha (`bind:value`, `rows`, `resize: vertical`, `invalid`) |
|
|
76
|
+
| `Label` | rótulo mono 13px |
|
|
77
|
+
| `Field` | wrapper de form: label + children (input/textarea/select) + mensagem de erro opcional |
|
|
78
|
+
| `Radio` | opção única: input oculto + indicador circular (accent no checked, foco por `:has()`) |
|
|
79
|
+
| `Checkbox` | opção múltipla: input oculto + quadrado com `✓` |
|
|
80
|
+
| `Toggle` | switch on/off (hidden checkbox + role `switch`, `invalid`) |
|
|
81
|
+
| `Select` | listbox custom com `options`, `bind:value`, `name`, navegação por teclado e `filter` |
|
|
82
|
+
| `MultiSelect` | seleção múltipla em popover com checkboxes, `filter`, hidden input com `name` |
|
|
83
|
+
| `Listbox` | lista de opções visível (single/múltipla), `filter`, `checkmark` |
|
|
84
|
+
| `DatePicker` | seletor de data/mês com popover calendário, `bind:value`, `min`/`max`, `locale` |
|
|
85
|
+
| `FileUpload` | seletor de arquivos com drag-and-drop (`basic`/`advanced`), validação de `accept`/`maxFileSize`/`fileLimit`, progresso por arquivo, `customUpload` + `uploadHandler` (sem rede embutida), snippets |
|
|
86
|
+
| `Toaster` | renderiza toasts + store `toast`; `position` configurável |
|
|
87
|
+
| `Timeline` | linha do tempo (`Timeline.Item`s com trilho, dot, skills) |
|
|
88
|
+
| `RuleCard` | cartão com borda-superior, ícone e prosa serif |
|
|
89
|
+
| `Nav` | navbar: logo + links em breadcrumb com `/` |
|
|
90
|
+
| `ThemeToggle` | alterna `data-theme`/`.dark` + `localStorage`; `showLabel` opcional pra label visível claro/escuro |
|
|
91
|
+
| `AppShell` | shell do app autenticado num só export composto: `AppShell.Sidebar` (desktop: brand + nav + tema/logout), `AppShell.BottomNav` (mobile), `AppShell.Content` (`max-w-6xl`) |
|
|
92
|
+
| `Wordmark` | `TAbelha` na cor do texto + `suffix` em accent; herda a fonte/tamanho do contexto |
|
|
93
|
+
| `Dropdown` | menu com sombra impressa; fecha por fora/Esc |
|
|
94
|
+
| `Divider` | linha horizontal/vertical, `solid`/`dashed`, label central |
|
|
95
|
+
| `Eyebrow` | label de seção mono uppercase |
|
|
96
|
+
| `BracketLink` | link mono `[ label ]`, colchetes acendem no hover |
|
|
97
|
+
| `Message` | alerta inline `info`/`success`/`warn`/`error`, `closable`, `life` (auto-dismiss) |
|
|
98
|
+
| `ProgressBar` | barra de progresso determinada/indeterminada com label |
|
|
99
|
+
| `ErrorPage` | display de página de erro (`status` em mono/accent, `title`, `description`, `homeHref`/`homeLabel`, `actions` snippet) — usado dentro de `+error.svelte` |
|
|
100
|
+
| `Skeleton` | shimmer placeholder (`width`/`height`/`rounded`) |
|
|
101
|
+
| `Tooltip` | tooltip CSS via `data-tooltip` (`top`/`bottom`/`left`/`right`) |
|
|
102
|
+
| `Dialog` | modal com overlay, `aria-modal`, Esc/outside pra fechar, `footer` snippet |
|
|
103
|
+
| `Sidebar` | painel lateral/`top`/`bottom`; `mode` overlay (drawer) ou `push` (estático, desktop), `collapsible`+`collapsed`, `overlay` off, breakpoint 768 (mobile vira drawer full-width), animado |
|
|
104
|
+
| `Tabs` | barra de abas com `value` bindable, `disabled`, navegação por teclado |
|
|
105
|
+
| `Accordion` | painéis colapsáveis (`multiple`, `value` bindable) |
|
|
106
|
+
| `Stepper` | indicador de passos (onboarding) com `value` bindable |
|
|
107
|
+
| `Carousel` | carrossel com `page` bindable, dots e arrows |
|
|
108
|
+
| `Rating` | estrelas (`max`, `readonly`, `cancel`) |
|
|
109
|
+
| `StatusPill` | pill fixa estilo toast (`position`, `closable`, `visible` bindable) |
|
|
110
|
+
| `FloatingActionPill` | pill flutuante clicável (`position`, `label`, `expanded` → `aria-expanded`) — estilo do StatusPill, mas `<button>` que abre painel/dialog |
|
|
111
|
+
| `ChatMessage` | bubble de chat (`role` user/assistant, `content`, `name`, `streaming` → ellipsis pulsando) — render presentacional, sem lógica de SSE |
|
|
112
|
+
| `Landing` | seções de landing num só export composto: `Landing.Hero`, `Landing.Steps` (cards numerados, número em box colorido), `Landing.Features`, `Landing.Roadmap`, `Landing.Footer` (`name` + `tagline`/licença/link de repo opcionais) |
|
|
113
|
+
| `SectionHeading` | título de seção: eyebrow + h2 + lead |
|
|
114
|
+
| `TerminalWindow` | janela de terminal: barra de dots + `title` + conteúdo (`children`) |
|
|
115
|
+
|
|
116
|
+
## Tokens
|
|
117
|
+
|
|
118
|
+
O tema expõe custom properties `--twui-*`:
|
|
119
|
+
|
|
120
|
+
- Semânticos: `--twui-paper`, `--twui-paper-raised`, `--twui-ink`,
|
|
121
|
+
`--twui-ink-soft`, `--twui-ink-faint`, `--twui-rule`, `--twui-accent`,
|
|
122
|
+
`--twui-accent-soft`, `--twui-signal`, `--twui-signal-soft`, `--twui-danger`
|
|
123
|
+
- Fontes: `--twui-font-mono`, `--twui-font-serif`
|
|
124
|
+
- Sombra: `--twui-shadow-offset` (hard offset 3px)
|
|
125
|
+
- Paleta por flavor ativo: `--twui-base`, `--twui-surface0`, `--twui-text`,
|
|
126
|
+
`--twui-red`, `--twui-green`, `--twui-mauve`, ... (via `--twui-<color>`)
|
|
127
|
+
- Cores cruas por flavor: `--twui-latte-*` e `--twui-mocha-*` (escala completa
|
|
128
|
+
— neutros + 14 acentos do Catppuccin)
|
|
129
|
+
|
|
130
|
+
Tema escuro via `[data-theme="dark"]` **ou** classe `.dark` no `<html>`. O
|
|
131
|
+
tema aplica defaults de `body` (fundo, cor, fonte serif) e respeita
|
|
132
|
+
`prefers-reduced-motion`.
|
|
133
|
+
|
|
134
|
+
## Accent custom por app
|
|
135
|
+
|
|
136
|
+
O accent padrão é o marrom (claro) / pink (escuro). Cada app pode ter o seu
|
|
137
|
+
**escolhendo um nome** via `data-accent` no `<html>` — a lib resolve o par
|
|
138
|
+
Latte/Mocha automaticamente:
|
|
139
|
+
|
|
140
|
+
```html
|
|
141
|
+
<html data-accent="teal"></html>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
O `--twui-accent-soft` deriva sozinho (via `color-mix`); os semânticos
|
|
145
|
+
`--twui-signal`/`--twui-danger` e os neutros não mudam. Acentos disponíveis:
|
|
146
|
+
|
|
147
|
+
| Accent | Latte (claro) | Mocha (escuro) |
|
|
148
|
+
| ------------------ | ------------- | -------------- |
|
|
149
|
+
| `maroon` (default) | `#e64553` | `#eba0ac` |
|
|
150
|
+
| `mauve` | `#8839ef` | `#cba6f7` |
|
|
151
|
+
| `pink` | `#ea76cb` | `#f5c2e7` |
|
|
152
|
+
| `red` | `#d20f39` | `#f38ba8` |
|
|
153
|
+
| `peach` | `#fe640b` | `#fab387` |
|
|
154
|
+
| `yellow` | `#df8e1d` | `#f9e2af` |
|
|
155
|
+
| `green` | `#40a02b` | `#a6e3a1` |
|
|
156
|
+
| `teal` | `#179299` | `#94e2d5` |
|
|
157
|
+
| `lavender` | `#7287fd` | `#b4befe` |
|
|
158
|
+
| `rosewater` | `#dc8a78` | `#f5e0dc` |
|
|
159
|
+
| `flamingo` | `#dd7878` | `#f2cdcd` |
|
|
160
|
+
| `blue` | `#1e66f5` | `#89b4fa` |
|
|
161
|
+
| `sky` | `#04a5e5` | `#89dceb` |
|
|
162
|
+
| `sapphire` | `#209fb5` | `#74c7ec` |
|
|
163
|
+
|
|
164
|
+
(Escape hatch: quem precisar de uma cor fora da lista pode sobrescrever
|
|
165
|
+
`--twui-accent` direto no `layout.css`, nos dois temas.)
|
|
166
|
+
|
|
167
|
+
## Desenvolvimento
|
|
168
|
+
|
|
169
|
+
Stack: Svelte 5 + SvelteKit, Bun como package manager.
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
bun install
|
|
173
|
+
bun run check # typecheck (svelte-kit sync + svelte-check)
|
|
174
|
+
bun run lint # prettier
|
|
175
|
+
bun run build # svelte-package (dist)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
O pacote é publicado no npm como `@tabelhadev/tabelhawebui`; as versões seguem o
|
|
179
|
+
`CHANGELOG.md` e a convenção do `CONTRIBUTING.md`.
|
|
180
|
+
|
|
181
|
+
## Ferramentas
|
|
182
|
+
|
|
183
|
+
- **MCP server** (`mcp/`): expõe componentes, tokens e accents como tools
|
|
184
|
+
MCP pra coding agents (props com tipos, `--twui-*` light/dark,
|
|
185
|
+
`data-accent`). Veja `mcp/README.md` pra instalar/rodar.
|
|
186
|
+
|
|
187
|
+
## Pedindo um componente
|
|
188
|
+
|
|
189
|
+
Requests de feature feitos por outros agents vivem em `requests/` como arquivos
|
|
190
|
+
markdown — veja `requests/README.md` pro fluxo, e `requests/_template.md` pro
|
|
191
|
+
que preencher. De fora deste repo, abra uma issue de feature request.
|
|
192
|
+
|
|
193
|
+
## Changelog
|
|
194
|
+
|
|
195
|
+
Veja [CHANGELOG.md](CHANGELOG.md) para o histórico de versões.
|
|
196
|
+
|
|
197
|
+
## Apoie o projeto
|
|
198
|
+
|
|
199
|
+
- **Global**: [ko-fi.com/ianptkcs](https://ko-fi.com/ianptkcs)
|
|
200
|
+
- **Brasil (Pix)**: escaneie o QR abaixo ou copie o código
|
|
201
|
+
|
|
202
|
+
<img src="pix-qr.png" alt="Pix QR" width="200" />
|
|
203
|
+
|
|
204
|
+
<details><summary>Código Pix (copiar)</summary>
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
00020126580014BR.GOV.BCB.PIX01365ad933b0-dcdc-4525-a736-0759902aeec65204000053039865802BR5925Ian Patrick da Costa Soar6009SAO PAULO62140510tQA85x6Dov63041FB6
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
</details>
|
|
211
|
+
|
|
212
|
+
## Licença
|
|
213
|
+
|
|
214
|
+
[AGPL-3.0](LICENSE) — copyleft forte: você pode usar, modificar e até
|
|
215
|
+
hospedar o TAbelhaWebUI comercialmente, mas qualquer versão modificada,
|
|
216
|
+
inclusive rodando como serviço via rede (SaaS), precisa continuar open source
|
|
217
|
+
sob a mesma licença.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Shared Svelte action: fires `callback` on pointerdown outside the node.
|
|
2
|
+
// Used by Dropdown, Select, TagInput, DatePicker, MultiSelect.
|
|
3
|
+
export function clickOutside(node, callback) {
|
|
4
|
+
function handler(e) {
|
|
5
|
+
if (!node.contains(e.target))
|
|
6
|
+
callback();
|
|
7
|
+
}
|
|
8
|
+
document.addEventListener("pointerdown", handler);
|
|
9
|
+
return {
|
|
10
|
+
destroy: () => document.removeEventListener("pointerdown", handler),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function comboboxKeydown(params: {
|
|
2
|
+
open: boolean;
|
|
3
|
+
activeIndex: number;
|
|
4
|
+
itemCount: number;
|
|
5
|
+
onActiveIndexChange: (i: number) => void;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
onConfirm: (index: number) => void;
|
|
8
|
+
onOpen?: () => void;
|
|
9
|
+
}): (e: KeyboardEvent) => void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Shared combobox keyboard navigation: ArrowUp/Down move an active index,
|
|
2
|
+
// Enter confirms the active item, Escape closes. Used by Select and TagInput.
|
|
3
|
+
export function comboboxKeydown(params) {
|
|
4
|
+
return (e) => {
|
|
5
|
+
if (e.key === "Escape") {
|
|
6
|
+
params.onClose();
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (e.key === "ArrowDown") {
|
|
10
|
+
e.preventDefault();
|
|
11
|
+
if (!params.open && params.onOpen) {
|
|
12
|
+
params.onOpen();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
params.onActiveIndexChange(Math.min(params.activeIndex + 1, params.itemCount - 1));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (e.key === "ArrowUp") {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
params.onActiveIndexChange(Math.max(params.activeIndex - 1, 0));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (e.key === "Enter" && params.open && params.activeIndex >= 0) {
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
params.onConfirm(params.activeIndex);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function trapFocus(panelEl: HTMLElement, onClose: () => void): () => void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Shared focus-trap: cycles Tab/Shift+Tab inside `panelEl`, matching the
|
|
2
|
+
// behaviour already in Dialog.svelte (WCAG 2.4.3). Used by Dialog and Sidebar
|
|
3
|
+
// (when in overlay / mobile modal mode).
|
|
4
|
+
import { tick } from "svelte";
|
|
5
|
+
const FOCUSABLE = 'a[href], button:not([disabled]), input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
|
6
|
+
export function trapFocus(panelEl, onClose) {
|
|
7
|
+
const onKey = (e) => {
|
|
8
|
+
if (e.key === "Escape") {
|
|
9
|
+
onClose();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (e.key !== "Tab")
|
|
13
|
+
return;
|
|
14
|
+
const focusable = Array.from(panelEl.querySelectorAll(FOCUSABLE));
|
|
15
|
+
if (focusable.length === 0)
|
|
16
|
+
return;
|
|
17
|
+
const first = focusable[0];
|
|
18
|
+
const last = focusable[focusable.length - 1];
|
|
19
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
last.focus();
|
|
22
|
+
}
|
|
23
|
+
else if (!e.shiftKey && document.activeElement === last) {
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
first.focus();
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
document.addEventListener("keydown", onKey);
|
|
29
|
+
tick().then(() => {
|
|
30
|
+
const first = panelEl?.querySelector(FOCUSABLE);
|
|
31
|
+
(first ?? panelEl)?.focus();
|
|
32
|
+
});
|
|
33
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function lockScroll(): () => void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Shared scroll-lock counter: document.body.style.overflow is locked while ANY
|
|
2
|
+
// consumer (Dialog, Sidebar overlay, …) is open, and only restored when the
|
|
3
|
+
// last one closes. This prevents the bug where closing one modal unlocks scroll
|
|
4
|
+
// while another is still visible.
|
|
5
|
+
let openCount = 0;
|
|
6
|
+
export function lockScroll() {
|
|
7
|
+
if (openCount === 0) {
|
|
8
|
+
document.body.style.overflow = "hidden";
|
|
9
|
+
}
|
|
10
|
+
openCount += 1;
|
|
11
|
+
return () => {
|
|
12
|
+
openCount -= 1;
|
|
13
|
+
if (openCount === 0) {
|
|
14
|
+
document.body.style.overflow = "";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|