@webjsdev/ui 0.3.7 → 0.3.9
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/README.md +14 -5
- package/package.json +2 -1
- package/packages/registry/components/accordion.ts +20 -15
- package/packages/registry/components/alert-dialog.ts +21 -18
- package/packages/registry/components/alert.ts +20 -18
- package/packages/registry/components/aspect-ratio.ts +11 -9
- package/packages/registry/components/avatar.ts +20 -12
- package/packages/registry/components/badge.ts +7 -5
- package/packages/registry/components/breadcrumb.ts +17 -15
- package/packages/registry/components/button.ts +8 -6
- package/packages/registry/components/card.ts +18 -14
- package/packages/registry/components/checkbox.ts +6 -4
- package/packages/registry/components/collapsible.ts +14 -12
- package/packages/registry/components/dialog.ts +32 -25
- package/packages/registry/components/dropdown-menu.ts +24 -21
- package/packages/registry/components/hover-card.ts +20 -11
- package/packages/registry/components/input.ts +6 -4
- package/packages/registry/components/kbd.ts +11 -9
- package/packages/registry/components/label.ts +6 -4
- package/packages/registry/components/native-select.ts +12 -10
- package/packages/registry/components/pagination.ts +15 -11
- package/packages/registry/components/popover.ts +24 -16
- package/packages/registry/components/progress.ts +8 -6
- package/packages/registry/components/radio-group.ts +14 -12
- package/packages/registry/components/separator.ts +8 -6
- package/packages/registry/components/skeleton.ts +6 -4
- package/packages/registry/components/sonner.ts +17 -13
- package/packages/registry/components/switch.ts +13 -11
- package/packages/registry/components/table.ts +21 -19
- package/packages/registry/components/tabs.ts +16 -10
- package/packages/registry/components/textarea.ts +6 -4
- package/packages/registry/components/toggle-group.ts +15 -13
- package/packages/registry/components/toggle.ts +17 -14
- package/packages/registry/components/tooltip.ts +12 -9
- package/packages/registry/lib/dom.ts +18 -0
- package/packages/registry/lib/utils.ts +6 -105
- package/packages/registry/registry.json +25 -5
- package/src/commands/add.js +78 -14
- package/src/commands/diff.js +20 -3
- package/src/commands/init.js +41 -25
- package/src/commands/list.js +2 -2
- package/src/commands/view.js +18 -3
- package/src/registry/example.js +120 -0
- package/src/registry/extract.js +151 -0
- package/src/registry/fetcher.js +60 -2
- package/src/registry/local.js +207 -0
- package/src/registry/resolver.js +2 -2
- package/src/utils/detect-project.js +3 -1
- package/src/utils/theme.js +68 -0
|
@@ -9,16 +9,6 @@
|
|
|
9
9
|
* HoverCardTrigger → <ui-hover-card-trigger>
|
|
10
10
|
* HoverCardContent → <ui-hover-card-content side align side-offset align-offset>
|
|
11
11
|
*
|
|
12
|
-
* Usage:
|
|
13
|
-
* <ui-hover-card open-delay="700" close-delay="300">
|
|
14
|
-
* <ui-hover-card-trigger>
|
|
15
|
-
* <a href="/user/vivek">@vivek</a>
|
|
16
|
-
* </ui-hover-card-trigger>
|
|
17
|
-
* <ui-hover-card-content>
|
|
18
|
-
* <div class="flex gap-3">…</div>
|
|
19
|
-
* </ui-hover-card-content>
|
|
20
|
-
* </ui-hover-card>
|
|
21
|
-
*
|
|
22
12
|
* Attributes on <ui-hover-card>:
|
|
23
13
|
* `open`: boolean (reflected). Open state.
|
|
24
14
|
* `open-delay`: ms, default 700. Hover delay before opening.
|
|
@@ -37,9 +27,28 @@
|
|
|
37
27
|
* Programmatic API on <ui-hover-card>: `.show()` · `.hide()`.
|
|
38
28
|
*
|
|
39
29
|
* Design tokens used: --popover, --popover-foreground, --border.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```html
|
|
33
|
+
* <ui-hover-card open-delay="700" close-delay="300">
|
|
34
|
+
* <ui-hover-card-trigger>
|
|
35
|
+
* <a href="/user/vivek">@vivek</a>
|
|
36
|
+
* </ui-hover-card-trigger>
|
|
37
|
+
* <ui-hover-card-content>
|
|
38
|
+
* <div class="flex gap-3">
|
|
39
|
+
* <img class="size-10 rounded-full" src="/avatars/vivek.jpg" alt="Vivek Khandelwal">
|
|
40
|
+
* <div>
|
|
41
|
+
* <div class="text-sm font-semibold">Vivek Khandelwal</div>
|
|
42
|
+
* <p class="text-sm text-muted-foreground">Builds the platform, not against it.</p>
|
|
43
|
+
* </div>
|
|
44
|
+
* </div>
|
|
45
|
+
* </ui-hover-card-content>
|
|
46
|
+
* </ui-hover-card>
|
|
47
|
+
* ```
|
|
40
48
|
*/
|
|
41
49
|
import { WebComponent, html, prop } from '@webjsdev/core';
|
|
42
|
-
import { ensureId
|
|
50
|
+
import { ensureId } from '../lib/utils.ts';
|
|
51
|
+
import { onBeforeCache } from '../lib/dom.ts';
|
|
43
52
|
import { positionFloating, type PopoverSide, type PopoverAlign } from './popover.ts';
|
|
44
53
|
|
|
45
54
|
// `fixed m-0` opts out of the UA `[popover]` auto-centering margin so
|
|
@@ -8,16 +8,18 @@
|
|
|
8
8
|
* shadcn parity:
|
|
9
9
|
* Input → inputClass()
|
|
10
10
|
*
|
|
11
|
-
* Usage:
|
|
12
|
-
* <input class=${inputClass()} type="email" name="email" id="email" required
|
|
13
|
-
* aria-describedby="email-hint">
|
|
14
|
-
*
|
|
15
11
|
* Pair with `<label class=${labelClass()} for="...">` and a hint paragraph
|
|
16
12
|
* (`<p class=${hintClass()} id="...-hint">`). Wrap all three in
|
|
17
13
|
* `<div class=${fieldClass()}>` for the canonical field rhythm.
|
|
18
14
|
*
|
|
19
15
|
* Design tokens used: --input, --background, --primary, --primary-foreground,
|
|
20
16
|
* --muted-foreground, --foreground, --ring, --destructive.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```html
|
|
20
|
+
* <input class=${inputClass()} type="email" name="email" id="email" required
|
|
21
|
+
* aria-describedby="email-hint">
|
|
22
|
+
* ```
|
|
21
23
|
*/
|
|
22
24
|
import { cn } from '../lib/utils.ts';
|
|
23
25
|
|
|
@@ -6,17 +6,19 @@
|
|
|
6
6
|
* Kbd → kbdClass()
|
|
7
7
|
* KbdGroup → kbdGroupClass()
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* <kbd class=${kbdClass()}>⌘</kbd>
|
|
11
|
-
* <kbd class=${kbdClass()}>K</kbd>
|
|
9
|
+
* Design tokens used: --muted, --muted-foreground, --background.
|
|
12
10
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* </div>
|
|
11
|
+
* @example
|
|
12
|
+
* ```html
|
|
13
|
+
* <kbd class=${kbdClass()}>⌘</kbd>
|
|
14
|
+
* <kbd class=${kbdClass()}>K</kbd>
|
|
18
15
|
*
|
|
19
|
-
*
|
|
16
|
+
* <div class=${kbdGroupClass()}>
|
|
17
|
+
* <kbd class=${kbdClass()}>⌘</kbd>
|
|
18
|
+
* <kbd class=${kbdClass()}>Shift</kbd>
|
|
19
|
+
* <kbd class=${kbdClass()}>P</kbd>
|
|
20
|
+
* </div>
|
|
21
|
+
* ```
|
|
20
22
|
*/
|
|
21
23
|
|
|
22
24
|
export const kbdClass = (): string =>
|
|
@@ -6,15 +6,17 @@
|
|
|
6
6
|
* shadcn parity:
|
|
7
7
|
* Label → labelClass()
|
|
8
8
|
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* <label class=${labelClass()} for="email">Email</label>
|
|
11
|
-
* <input class=${inputClass()} id="email" name="email" type="email">
|
|
12
|
-
*
|
|
13
9
|
* Disabled-state inheritance: when the label is inside a container with
|
|
14
10
|
* `data-disabled="true"` (the "field" pattern), or next to a peer-disabled
|
|
15
11
|
* control, it dims automatically.
|
|
16
12
|
*
|
|
17
13
|
* Design tokens used: none (typography only).
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```html
|
|
17
|
+
* <label class=${labelClass()} for="email">Email</label>
|
|
18
|
+
* <input class=${inputClass()} id="email" name="email" type="email">
|
|
19
|
+
* ```
|
|
18
20
|
*/
|
|
19
21
|
|
|
20
22
|
/** Compose Tailwind classes for a native `<label>`. */
|
|
@@ -13,16 +13,6 @@
|
|
|
13
13
|
* stylesheet sets Canvas / CanvasText on
|
|
14
14
|
* every <option> automatically)
|
|
15
15
|
*
|
|
16
|
-
* Usage:
|
|
17
|
-
* <div class=${nativeSelectWrapperClass()}>
|
|
18
|
-
* <select class=${nativeSelectClass()} name="plan">
|
|
19
|
-
* <option>Basic</option>
|
|
20
|
-
* <option>Pro</option>
|
|
21
|
-
* </select>
|
|
22
|
-
* <!-- chevron icon, decorative -->
|
|
23
|
-
* <svg class="${nativeSelectIconClass()}" aria-hidden="true">…</svg>
|
|
24
|
-
* </div>
|
|
25
|
-
*
|
|
26
16
|
* Importing this module installs a stylesheet that forces Canvas /
|
|
27
17
|
* CanvasText on every <option> inside the wrapper so the dropdown reads
|
|
28
18
|
* in both light and dark themes regardless of OS preference; advanced
|
|
@@ -30,6 +20,18 @@
|
|
|
30
20
|
*
|
|
31
21
|
* Design tokens used: --input, --background, --primary, --primary-foreground,
|
|
32
22
|
* --muted-foreground, --ring, --destructive.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```html
|
|
26
|
+
* <div class=${nativeSelectWrapperClass()}>
|
|
27
|
+
* <select class=${nativeSelectClass()} name="plan">
|
|
28
|
+
* <option>Basic</option>
|
|
29
|
+
* <option>Pro</option>
|
|
30
|
+
* </select>
|
|
31
|
+
* <!-- chevron icon, decorative -->
|
|
32
|
+
* <svg class="${nativeSelectIconClass()}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
|
33
|
+
* </div>
|
|
34
|
+
* ```
|
|
33
35
|
*/
|
|
34
36
|
import { cn } from '../lib/utils.ts';
|
|
35
37
|
|
|
@@ -12,23 +12,27 @@
|
|
|
12
12
|
* PaginationNext → paginationNextClass()
|
|
13
13
|
* PaginationEllipsis → paginationEllipsisClass()
|
|
14
14
|
*
|
|
15
|
-
* Usage:
|
|
16
|
-
* <nav role="navigation" aria-label="pagination" class=${paginationClass()}>
|
|
17
|
-
* <ul class=${paginationContentClass()}>
|
|
18
|
-
* <li><a class=${paginationPreviousClass()} href="?page=1">‹ Previous</a></li>
|
|
19
|
-
* <li><a class=${paginationLinkClass({ isActive: false })} href="?page=2">2</a></li>
|
|
20
|
-
* <li><a class=${paginationLinkClass({ isActive: true })} aria-current="page">3</a></li>
|
|
21
|
-
* <li class=${paginationEllipsisClass()} aria-hidden="true">…</li>
|
|
22
|
-
* <li><a class=${paginationNextClass()} href="?page=4">Next ›</a></li>
|
|
23
|
-
* </ul>
|
|
24
|
-
* </nav>
|
|
25
|
-
*
|
|
26
15
|
* A11y (required for accessible output): wrap the list in <nav
|
|
27
16
|
* aria-label="pagination">, set aria-current="page" on the active page
|
|
28
17
|
* link, give an icon-only Previous / Next control an aria-label, and mark
|
|
29
18
|
* the ellipsis aria-hidden="true". The class helpers emit none of these.
|
|
30
19
|
*
|
|
31
20
|
* Design tokens used: inherited from buttonClass.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```html
|
|
24
|
+
* <nav role="navigation" aria-label="pagination" class=${paginationClass()}>
|
|
25
|
+
* <ul class=${paginationContentClass()}>
|
|
26
|
+
* <li><a class=${paginationPreviousClass()} href="?page=1">‹ Previous</a></li>
|
|
27
|
+
* <li><a class=${paginationLinkClass({ isActive: false })} href="?page=2">2</a></li>
|
|
28
|
+
* <li><a class=${paginationLinkClass({ isActive: true })} aria-current="page">3</a></li>
|
|
29
|
+
* <li class=${paginationEllipsisClass()} aria-hidden="true">
|
|
30
|
+
* <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg>
|
|
31
|
+
* </li>
|
|
32
|
+
* <li><a class=${paginationNextClass()} href="?page=4">Next ›</a></li>
|
|
33
|
+
* </ul>
|
|
34
|
+
* </nav>
|
|
35
|
+
* ```
|
|
32
36
|
*/
|
|
33
37
|
import { cn } from '../lib/utils.ts';
|
|
34
38
|
import { buttonClass, type ButtonSize } from './button.ts';
|
|
@@ -18,28 +18,36 @@
|
|
|
18
18
|
* PopoverTitle → popoverTitleClass()
|
|
19
19
|
* PopoverDescription → popoverDescriptionClass()
|
|
20
20
|
*
|
|
21
|
-
* Usage (single invoker, implicit anchor, zero inline style):
|
|
22
|
-
* <button popovertarget="filter" class=${buttonClass({ variant: 'outline' })}>Filter</button>
|
|
23
|
-
* <div id="filter" popover
|
|
24
|
-
* class=${popoverContentClass({ side: 'bottom', align: 'start', sideOffset: 4 })}>
|
|
25
|
-
* <div class=${popoverHeaderClass()}>
|
|
26
|
-
* <h3 class=${popoverTitleClass()}>Filter posts</h3>
|
|
27
|
-
* <p class=${popoverDescriptionClass()}>By tag and status.</p>
|
|
28
|
-
* </div>
|
|
29
|
-
* </div>
|
|
30
|
-
*
|
|
31
|
-
* <!-- Explicit anchor (multiple invokers / anchoring to a different element): -->
|
|
32
|
-
* <span style="anchor-name: --picker">@vivek</span>
|
|
33
|
-
* <button popovertarget="profile">Show</button>
|
|
34
|
-
* <div id="profile" popover style="position-anchor: --picker"
|
|
35
|
-
* class=${popoverContentClass({ side: 'bottom' })}>…</div>
|
|
36
|
-
*
|
|
37
21
|
* The `positionFloating` helper is also exported (used by the Tier-2
|
|
38
22
|
* tooltip / hover-card / dropdown-menu components for imperative
|
|
39
23
|
* positioning where CSS Anchor Positioning isn't enough). Migrated from
|
|
40
24
|
* the prior <ui-popover> custom element set.
|
|
41
25
|
*
|
|
42
26
|
* Design tokens used: --popover, --popover-foreground, --border.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```html
|
|
30
|
+
* <!-- Single invoker, implicit anchor, zero inline style. -->
|
|
31
|
+
* <button popovertarget="filter" class=${buttonClass({ variant: 'outline' })}>Filter</button>
|
|
32
|
+
* <div id="filter" popover
|
|
33
|
+
* class=${popoverContentClass({ side: 'bottom', align: 'start', sideOffset: 4 })}>
|
|
34
|
+
* <div class=${popoverHeaderClass()}>
|
|
35
|
+
* <h3 class=${popoverTitleClass()}>Filter posts</h3>
|
|
36
|
+
* <p class=${popoverDescriptionClass()}>By tag and status.</p>
|
|
37
|
+
* </div>
|
|
38
|
+
* </div>
|
|
39
|
+
*
|
|
40
|
+
* <!-- Explicit anchor, for multiple invokers or anchoring to a different element. -->
|
|
41
|
+
* <span style="anchor-name: --picker">@vivek</span>
|
|
42
|
+
* <button popovertarget="profile">Show</button>
|
|
43
|
+
* <div id="profile" popover style="position-anchor: --picker"
|
|
44
|
+
* class=${popoverContentClass({ side: 'bottom' })}>
|
|
45
|
+
* <div class=${popoverHeaderClass()}>
|
|
46
|
+
* <h3 class=${popoverTitleClass()}>Vivek Khandelwal</h3>
|
|
47
|
+
* <p class=${popoverDescriptionClass()}>@vivek</p>
|
|
48
|
+
* </div>
|
|
49
|
+
* </div>
|
|
50
|
+
* ```
|
|
43
51
|
*/
|
|
44
52
|
|
|
45
53
|
// --------------------------------------------------------------------------
|
|
@@ -10,18 +10,20 @@
|
|
|
10
10
|
* `::-webkit-progress-value`, and
|
|
11
11
|
* `::-moz-progress-bar`)
|
|
12
12
|
*
|
|
13
|
-
* Usage:
|
|
14
|
-
* <progress value="42" max="100" class=${progressClass()}></progress>
|
|
15
|
-
*
|
|
16
|
-
* <!-- Indeterminate (no `value` attribute): -->
|
|
17
|
-
* <progress class=${progressClass()}></progress>
|
|
18
|
-
*
|
|
19
13
|
* A11y (required for accessible output): give the <progress> an accessible
|
|
20
14
|
* name with aria-label (or a <label for>), e.g. aria-label="Upload
|
|
21
15
|
* progress". The native element supplies the role and value; only the name
|
|
22
16
|
* is the author's responsibility.
|
|
23
17
|
*
|
|
24
18
|
* Design tokens used: --primary.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```html
|
|
22
|
+
* <progress value="42" max="100" class=${progressClass()}></progress>
|
|
23
|
+
*
|
|
24
|
+
* <!-- Indeterminate state, with no value attribute. -->
|
|
25
|
+
* <progress class=${progressClass()}></progress>
|
|
26
|
+
* ```
|
|
25
27
|
*/
|
|
26
28
|
|
|
27
29
|
/**
|
|
@@ -9,19 +9,21 @@
|
|
|
9
9
|
* → radioGroupClass({ orientation }) on a <div role="radiogroup">
|
|
10
10
|
* RadioGroupItem → radioClass() on a native <input type="radio">
|
|
11
11
|
*
|
|
12
|
-
* Usage:
|
|
13
|
-
* <div role="radiogroup" class=${radioGroupClass()}>
|
|
14
|
-
* <div class="flex items-center gap-2">
|
|
15
|
-
* <input type="radio" name="plan" value="basic" id="plan-basic" class=${radioClass()}>
|
|
16
|
-
* <label class=${labelClass()} for="plan-basic">Basic</label>
|
|
17
|
-
* </div>
|
|
18
|
-
* <div class="flex items-center gap-2">
|
|
19
|
-
* <input type="radio" name="plan" value="pro" id="plan-pro" class=${radioClass()}>
|
|
20
|
-
* <label class=${labelClass()} for="plan-pro">Pro</label>
|
|
21
|
-
* </div>
|
|
22
|
-
* </div>
|
|
23
|
-
*
|
|
24
12
|
* Design tokens used: --input, --primary, --ring, --destructive.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```html
|
|
16
|
+
* <div role="radiogroup" class=${radioGroupClass()}>
|
|
17
|
+
* <div class="flex items-center gap-2">
|
|
18
|
+
* <input type="radio" name="plan" value="basic" id="plan-basic" class=${radioClass()}>
|
|
19
|
+
* <label class=${labelClass()} for="plan-basic">Basic</label>
|
|
20
|
+
* </div>
|
|
21
|
+
* <div class="flex items-center gap-2">
|
|
22
|
+
* <input type="radio" name="plan" value="pro" id="plan-pro" class=${radioClass()}>
|
|
23
|
+
* <label class=${labelClass()} for="plan-pro">Pro</label>
|
|
24
|
+
* </div>
|
|
25
|
+
* </div>
|
|
26
|
+
* ```
|
|
25
27
|
*/
|
|
26
28
|
import { cn } from '../lib/utils.ts';
|
|
27
29
|
|
|
@@ -7,18 +7,20 @@
|
|
|
7
7
|
* Separator (orientation: horizontal | vertical, decorative: bool)
|
|
8
8
|
* → separatorClass({ orientation }) + role + data-orientation
|
|
9
9
|
*
|
|
10
|
-
* Usage:
|
|
11
|
-
* <div role="none" class=${separatorClass()} data-orientation="horizontal"></div>
|
|
12
|
-
* <div role="separator" aria-orientation="vertical"
|
|
13
|
-
* class=${separatorClass({ orientation: 'vertical' })}
|
|
14
|
-
* data-orientation="vertical"></div>
|
|
15
|
-
*
|
|
16
10
|
* A11y (required for accessible output): a meaningful divider needs
|
|
17
11
|
* role="separator" plus aria-orientation. A purely decorative one needs
|
|
18
12
|
* role="none" (or aria-hidden="true") so assistive tech does not announce
|
|
19
13
|
* an empty separator.
|
|
20
14
|
*
|
|
21
15
|
* Design tokens used: --border.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```html
|
|
19
|
+
* <div role="none" class=${separatorClass()} data-orientation="horizontal"></div>
|
|
20
|
+
* <div role="separator" aria-orientation="vertical"
|
|
21
|
+
* class=${separatorClass({ orientation: 'vertical' })}
|
|
22
|
+
* data-orientation="vertical"></div>
|
|
23
|
+
* ```
|
|
22
24
|
*/
|
|
23
25
|
import { cn } from '../lib/utils.ts';
|
|
24
26
|
|
|
@@ -6,16 +6,18 @@
|
|
|
6
6
|
* shadcn parity:
|
|
7
7
|
* Skeleton → skeletonClass() (visual: animated rounded muted block)
|
|
8
8
|
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* <div class=${cn(skeletonClass(), 'h-4 w-32')}></div>
|
|
11
|
-
* <div class=${cn(skeletonClass(), 'h-12 w-12 rounded-full')}></div>
|
|
12
|
-
*
|
|
13
9
|
* A11y (required for accessible output): a skeleton is a decorative
|
|
14
10
|
* placeholder, so hide it from assistive tech with aria-hidden="true" (or
|
|
15
11
|
* mark the loading region aria-busy="true"). Announce the real content
|
|
16
12
|
* once it replaces the skeleton.
|
|
17
13
|
*
|
|
18
14
|
* Design tokens used: --accent.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```html
|
|
18
|
+
* <div class=${cn(skeletonClass(), 'h-4 w-32')}></div>
|
|
19
|
+
* <div class=${cn(skeletonClass(), 'h-12 w-12 rounded-full')}></div>
|
|
20
|
+
* ```
|
|
19
21
|
*/
|
|
20
22
|
|
|
21
23
|
export const skeletonClass = (): string => 'animate-pulse rounded-md bg-accent';
|
|
@@ -12,18 +12,6 @@
|
|
|
12
12
|
* → toast() (plus toast.success / .error / .info / .warning /
|
|
13
13
|
* .loading / .promise / .dismiss)
|
|
14
14
|
*
|
|
15
|
-
* Usage:
|
|
16
|
-
* <!-- Mount once at the root of your app (typically in layout.ts): -->
|
|
17
|
-
* <ui-sonner position="bottom-right"></ui-sonner>
|
|
18
|
-
*
|
|
19
|
-
* // Then from anywhere (server or client component):
|
|
20
|
-
* import { toast } from '@/components/ui/sonner.ts';
|
|
21
|
-
* toast('Saved!');
|
|
22
|
-
* toast.success('Account created');
|
|
23
|
-
* toast.error('Failed to save', { description: 'Try again' });
|
|
24
|
-
* toast.promise(savePost(), { loading: 'Saving…', success: 'Saved', error: 'Failed' });
|
|
25
|
-
* toast.dismiss(id);
|
|
26
|
-
*
|
|
27
15
|
* Attributes on <ui-sonner>:
|
|
28
16
|
* `position`: "top-left" | "top-center" | "top-right" |
|
|
29
17
|
* "bottom-left" | "bottom-center" | "bottom-right" (default).
|
|
@@ -42,9 +30,25 @@
|
|
|
42
30
|
* uses); typically only needed when mounting multiple viewports.
|
|
43
31
|
*
|
|
44
32
|
* Design tokens used: --popover, --popover-foreground, --border, --radius.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```html
|
|
36
|
+
* <!-- Mount once at the root of your app, typically in layout.ts. -->
|
|
37
|
+
* <ui-sonner position="bottom-right"></ui-sonner>
|
|
38
|
+
*
|
|
39
|
+
* <!-- Then from anywhere, in a server or client component, call the API. -->
|
|
40
|
+
* <script type="module">
|
|
41
|
+
* import { toast } from '@/components/ui/sonner.ts';
|
|
42
|
+
* toast('Saved!');
|
|
43
|
+
* toast.success('Account created');
|
|
44
|
+
* toast.error('Failed to save', { description: 'Try again' });
|
|
45
|
+
* toast.promise(savePost(), { loading: 'Saving', success: 'Saved', error: 'Failed' });
|
|
46
|
+
* toast.dismiss(id);
|
|
47
|
+
* </script>
|
|
48
|
+
* ```
|
|
45
49
|
*/
|
|
46
50
|
import { WebComponent, html, repeat, unsafeHTML, signal, prop } from '@webjsdev/core';
|
|
47
|
-
import { onBeforeCache } from '../lib/
|
|
51
|
+
import { onBeforeCache } from '../lib/dom.ts';
|
|
48
52
|
|
|
49
53
|
type ToastType = 'default' | 'success' | 'error' | 'info' | 'warning' | 'loading';
|
|
50
54
|
|
|
@@ -8,19 +8,21 @@
|
|
|
8
8
|
* Switch (size: default | sm) → switchInputClass() on a hidden <input> +
|
|
9
9
|
* switchTrackClass({ size }) on a sibling <span>
|
|
10
10
|
*
|
|
11
|
-
* Usage:
|
|
12
|
-
* <label class="inline-flex items-center gap-2">
|
|
13
|
-
* <input type="checkbox" role="switch" name="notify" class=${switchInputClass()}>
|
|
14
|
-
* <span class=${switchTrackClass()}></span>
|
|
15
|
-
* <span class=${labelClass()}>Notifications</span>
|
|
16
|
-
* </label>
|
|
17
|
-
*
|
|
18
|
-
* <!-- Small size: -->
|
|
19
|
-
* <input type="checkbox" role="switch" name="x" class=${cn(switchInputClass(), 'peer/sm')}>
|
|
20
|
-
* <span class=${switchTrackClass({ size: 'sm' })}></span>
|
|
21
|
-
*
|
|
22
11
|
* Design tokens used: --primary, --input, --background, --foreground, --ring,
|
|
23
12
|
* --primary-foreground.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```html
|
|
16
|
+
* <label class="inline-flex items-center gap-2">
|
|
17
|
+
* <input type="checkbox" role="switch" name="notify" class=${switchInputClass()}>
|
|
18
|
+
* <span class=${switchTrackClass()}></span>
|
|
19
|
+
* <span class=${labelClass()}>Notifications</span>
|
|
20
|
+
* </label>
|
|
21
|
+
*
|
|
22
|
+
* <!-- Small size. -->
|
|
23
|
+
* <input type="checkbox" role="switch" name="x" class=${cn(switchInputClass(), 'peer/sm')}>
|
|
24
|
+
* <span class=${switchTrackClass({ size: 'sm' })}></span>
|
|
25
|
+
* ```
|
|
24
26
|
*/
|
|
25
27
|
import { cn } from '../lib/utils.ts';
|
|
26
28
|
|
|
@@ -13,31 +13,33 @@
|
|
|
13
13
|
* TableHead / TableCell / TableCaption
|
|
14
14
|
* → tableHeadClass() / tableCellClass() / tableCaptionClass()
|
|
15
15
|
*
|
|
16
|
-
* Usage:
|
|
17
|
-
* <div class=${tableContainerClass()}>
|
|
18
|
-
* <table class=${tableClass()}>
|
|
19
|
-
* <thead class=${tableHeaderClass()}>
|
|
20
|
-
* <tr class=${tableRowClass()}>
|
|
21
|
-
* <th scope="col" class=${tableHeadClass()}>Name</th>
|
|
22
|
-
* <th scope="col" class=${tableHeadClass()}>Status</th>
|
|
23
|
-
* </tr>
|
|
24
|
-
* </thead>
|
|
25
|
-
* <tbody class=${tableBodyClass()}>
|
|
26
|
-
* <tr class=${tableRowClass()}>
|
|
27
|
-
* <td class=${tableCellClass()}>Vivek</td>
|
|
28
|
-
* <td class=${tableCellClass()}>Active</td>
|
|
29
|
-
* </tr>
|
|
30
|
-
* </tbody>
|
|
31
|
-
* <caption class=${tableCaptionClass()}>Users</caption>
|
|
32
|
-
* </table>
|
|
33
|
-
* </div>
|
|
34
|
-
*
|
|
35
16
|
* A11y (required for accessible output): every header cell needs a scope
|
|
36
17
|
* (scope="col" on a column header, scope="row" on a row header) so screen
|
|
37
18
|
* readers map cells to their headers. Add a <caption> naming the table's
|
|
38
19
|
* purpose (it can be visually hidden if a heading already names it).
|
|
39
20
|
*
|
|
40
21
|
* Design tokens used: --muted, --muted-foreground, --foreground.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```html
|
|
25
|
+
* <div class=${tableContainerClass()}>
|
|
26
|
+
* <table class=${tableClass()}>
|
|
27
|
+
* <thead class=${tableHeaderClass()}>
|
|
28
|
+
* <tr class=${tableRowClass()}>
|
|
29
|
+
* <th scope="col" class=${tableHeadClass()}>Name</th>
|
|
30
|
+
* <th scope="col" class=${tableHeadClass()}>Status</th>
|
|
31
|
+
* </tr>
|
|
32
|
+
* </thead>
|
|
33
|
+
* <tbody class=${tableBodyClass()}>
|
|
34
|
+
* <tr class=${tableRowClass()}>
|
|
35
|
+
* <td class=${tableCellClass()}>Vivek</td>
|
|
36
|
+
* <td class=${tableCellClass()}>Active</td>
|
|
37
|
+
* </tr>
|
|
38
|
+
* </tbody>
|
|
39
|
+
* <caption class=${tableCaptionClass()}>Users</caption>
|
|
40
|
+
* </table>
|
|
41
|
+
* </div>
|
|
42
|
+
* ```
|
|
41
43
|
*/
|
|
42
44
|
|
|
43
45
|
export const tableContainerClass = (): string => 'relative w-full overflow-x-auto';
|
|
@@ -11,16 +11,6 @@
|
|
|
11
11
|
* TabsTrigger → <ui-tabs-trigger value>
|
|
12
12
|
* TabsContent → <ui-tabs-content value>
|
|
13
13
|
*
|
|
14
|
-
* Usage:
|
|
15
|
-
* <ui-tabs value="account">
|
|
16
|
-
* <ui-tabs-list>
|
|
17
|
-
* <ui-tabs-trigger value="account">Account</ui-tabs-trigger>
|
|
18
|
-
* <ui-tabs-trigger value="password">Password</ui-tabs-trigger>
|
|
19
|
-
* </ui-tabs-list>
|
|
20
|
-
* <ui-tabs-content value="account">…</ui-tabs-content>
|
|
21
|
-
* <ui-tabs-content value="password">…</ui-tabs-content>
|
|
22
|
-
* </ui-tabs>
|
|
23
|
-
*
|
|
24
14
|
* Attributes on <ui-tabs>:
|
|
25
15
|
* `value`: string (reflected, controlled). Active tab value.
|
|
26
16
|
* `orientation`: "horizontal" (default) | "vertical".
|
|
@@ -42,6 +32,22 @@
|
|
|
42
32
|
*
|
|
43
33
|
* Design tokens used: --muted, --muted-foreground, --foreground, --background,
|
|
44
34
|
* --input, --ring.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```html
|
|
38
|
+
* <ui-tabs value="account">
|
|
39
|
+
* <ui-tabs-list>
|
|
40
|
+
* <ui-tabs-trigger value="account">Account</ui-tabs-trigger>
|
|
41
|
+
* <ui-tabs-trigger value="password">Password</ui-tabs-trigger>
|
|
42
|
+
* </ui-tabs-list>
|
|
43
|
+
* <ui-tabs-content value="account">
|
|
44
|
+
* <p class="text-sm">Update your name and email here.</p>
|
|
45
|
+
* </ui-tabs-content>
|
|
46
|
+
* <ui-tabs-content value="password">
|
|
47
|
+
* <p class="text-sm">Change your password here.</p>
|
|
48
|
+
* </ui-tabs-content>
|
|
49
|
+
* </ui-tabs>
|
|
50
|
+
* ```
|
|
45
51
|
*/
|
|
46
52
|
import { WebComponent, html, prop } from '@webjsdev/core';
|
|
47
53
|
import { cn, ensureId } from '../lib/utils.ts';
|
|
@@ -6,15 +6,17 @@
|
|
|
6
6
|
* shadcn parity:
|
|
7
7
|
* Textarea → textareaClass()
|
|
8
8
|
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* <textarea class=${textareaClass()} name="message" rows="4" placeholder="Your message…">
|
|
11
|
-
* </textarea>
|
|
12
|
-
*
|
|
13
9
|
* Pair with `<label class=${labelClass()} for="...">` and wrap in
|
|
14
10
|
* `<div class=${fieldClass()}>` for the canonical field rhythm.
|
|
15
11
|
*
|
|
16
12
|
* Design tokens used: --input, --background, --muted-foreground, --ring,
|
|
17
13
|
* --destructive.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```html
|
|
17
|
+
* <textarea class=${textareaClass()} name="message" rows="4" placeholder="Your message">
|
|
18
|
+
* </textarea>
|
|
19
|
+
* ```
|
|
18
20
|
*/
|
|
19
21
|
import { cn } from '../lib/utils.ts';
|
|
20
22
|
|
|
@@ -12,19 +12,6 @@
|
|
|
12
12
|
* → <ui-toggle-group type variant size value>
|
|
13
13
|
* ToggleGroupItem → <ui-toggle-group-item value>
|
|
14
14
|
*
|
|
15
|
-
* Usage:
|
|
16
|
-
* <ui-toggle-group type="single" value="bold">
|
|
17
|
-
* <ui-toggle-group-item value="bold" aria-label="Bold"><b>B</b></ui-toggle-group-item>
|
|
18
|
-
* <ui-toggle-group-item value="italic" aria-label="Italic"><i>I</i></ui-toggle-group-item>
|
|
19
|
-
* <ui-toggle-group-item value="underline" aria-label="Underline"><u>U</u></ui-toggle-group-item>
|
|
20
|
-
* </ui-toggle-group>
|
|
21
|
-
*
|
|
22
|
-
* <!-- Multiple selection (comma-separated value): -->
|
|
23
|
-
* <ui-toggle-group type="multiple" value="bold,italic">
|
|
24
|
-
* <ui-toggle-group-item value="bold">B</ui-toggle-group-item>
|
|
25
|
-
* <ui-toggle-group-item value="italic">I</ui-toggle-group-item>
|
|
26
|
-
* </ui-toggle-group>
|
|
27
|
-
*
|
|
28
15
|
* Attributes on <ui-toggle-group>:
|
|
29
16
|
* `type`: "single" (default) | "multiple".
|
|
30
17
|
* `value`: string. Selected value(s). Single: a single value;
|
|
@@ -47,6 +34,21 @@
|
|
|
47
34
|
*
|
|
48
35
|
* Design tokens used: inherited from toggleClass (--muted, --accent, --ring,
|
|
49
36
|
* --input, --destructive).
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```html
|
|
40
|
+
* <ui-toggle-group type="single" value="bold">
|
|
41
|
+
* <ui-toggle-group-item value="bold" aria-label="Bold"><b>B</b></ui-toggle-group-item>
|
|
42
|
+
* <ui-toggle-group-item value="italic" aria-label="Italic"><i>I</i></ui-toggle-group-item>
|
|
43
|
+
* <ui-toggle-group-item value="underline" aria-label="Underline"><u>U</u></ui-toggle-group-item>
|
|
44
|
+
* </ui-toggle-group>
|
|
45
|
+
*
|
|
46
|
+
* <!-- Multiple selection, with a comma-separated value. -->
|
|
47
|
+
* <ui-toggle-group type="multiple" value="bold,italic">
|
|
48
|
+
* <ui-toggle-group-item value="bold">B</ui-toggle-group-item>
|
|
49
|
+
* <ui-toggle-group-item value="italic">I</ui-toggle-group-item>
|
|
50
|
+
* </ui-toggle-group>
|
|
51
|
+
* ```
|
|
50
52
|
*/
|
|
51
53
|
import { WebComponent, html, prop } from '@webjsdev/core';
|
|
52
54
|
import { cn } from '../lib/utils.ts';
|
|
@@ -10,20 +10,6 @@
|
|
|
10
10
|
* → toggleClass({ variant, size }) (class helper)
|
|
11
11
|
* → <ui-toggle pressed variant size> (custom element)
|
|
12
12
|
*
|
|
13
|
-
* Usage (Tier-1 class helper, caller owns state):
|
|
14
|
-
* <button class=${toggleClass()} data-state="off" aria-pressed="false"
|
|
15
|
-
* onclick="this.dataset.state = this.dataset.state==='on'?'off':'on'">
|
|
16
|
-
* <svg>…</svg>
|
|
17
|
-
* </button>
|
|
18
|
-
*
|
|
19
|
-
* Usage (Tier-2 custom element, state managed):
|
|
20
|
-
* <ui-toggle aria-label="Toggle bold">
|
|
21
|
-
* <svg>…</svg>
|
|
22
|
-
* </ui-toggle>
|
|
23
|
-
*
|
|
24
|
-
* <!-- Controlled / initial: -->
|
|
25
|
-
* <ui-toggle variant="outline" size="sm" pressed>B</ui-toggle>
|
|
26
|
-
*
|
|
27
13
|
* Attributes on <ui-toggle>:
|
|
28
14
|
* `pressed`: boolean (reflected). Active state.
|
|
29
15
|
* `variant`: "default" (default) | "outline".
|
|
@@ -37,6 +23,23 @@
|
|
|
37
23
|
*
|
|
38
24
|
* Design tokens used: --muted, --muted-foreground, --accent, --accent-foreground,
|
|
39
25
|
* --input, --background, --ring, --destructive.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```html
|
|
29
|
+
* <!-- Tier-1 class helper, caller owns state. -->
|
|
30
|
+
* <button class=${toggleClass()} data-state="off" aria-pressed="false"
|
|
31
|
+
* onclick="this.dataset.state = this.dataset.state==='on'?'off':'on'">
|
|
32
|
+
* <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M14 12a4 4 0 0 0 0-8H6v8" /><path d="M15 20a4 4 0 0 0 0-8H6v8Z" /></svg>
|
|
33
|
+
* </button>
|
|
34
|
+
*
|
|
35
|
+
* <!-- Tier-2 custom element, state managed. -->
|
|
36
|
+
* <ui-toggle aria-label="Toggle bold">
|
|
37
|
+
* <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M14 12a4 4 0 0 0 0-8H6v8" /><path d="M15 20a4 4 0 0 0 0-8H6v8Z" /></svg>
|
|
38
|
+
* </ui-toggle>
|
|
39
|
+
*
|
|
40
|
+
* <!-- Controlled or initial state. -->
|
|
41
|
+
* <ui-toggle variant="outline" size="sm" pressed>B</ui-toggle>
|
|
42
|
+
* ```
|
|
40
43
|
*/
|
|
41
44
|
import { WebComponent, html, prop } from '@webjsdev/core';
|
|
42
45
|
import { cn } from '../lib/utils.ts';
|