@rimelight/ui 0.0.38 → 0.0.40
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/package.json +3 -3
- package/src/components/button/RLAButton.astro +2 -0
- package/src/components/button/button.theme.ts +12 -3
- package/src/components/button/button.ts +4 -0
- package/src/components/icon/icon.theme.ts +8 -1
- package/src/components/image/RLAImage.astro +1 -3
- package/src/components/page-section/RLAPageSection.astro +8 -0
- package/src/components/post/RLAPost.astro +70 -70
- package/src/components/tabs/RLATabs.astro +116 -32
- package/src/components/tabs/tabs.theme.ts +61 -23
- package/src/components/tabs/tabs.ts +47 -6
- package/src/components/timeline/RLATimeline.astro +194 -20
- package/src/components/timeline/timeline.theme.ts +307 -9
- package/src/components/timeline/timeline.ts +54 -4
- package/src/components/video/RLAVideo.astro +1 -1
- package/src/presets/index.ts +38 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rimelight/ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.40",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Rimelight Entertainment UI Library.",
|
|
6
6
|
"keywords": [
|
|
@@ -54,11 +54,11 @@
|
|
|
54
54
|
"@astrojs/solid-js": "7.0.1",
|
|
55
55
|
"@astrojs/ts-plugin": "1.10.10",
|
|
56
56
|
"@astrojs/vue": "7.0.1",
|
|
57
|
-
"astro": "7.1.
|
|
57
|
+
"astro": "7.1.3",
|
|
58
58
|
"solid-js": "1.9.14",
|
|
59
59
|
"typescript": "6.0.3",
|
|
60
60
|
"unocss": "66.7.5",
|
|
61
|
-
"vue": "3.5.
|
|
61
|
+
"vue": "3.5.40"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
64
|
"@astrojs/solid-js": ">=7.0.1",
|
|
@@ -28,6 +28,7 @@ const {
|
|
|
28
28
|
square = false,
|
|
29
29
|
rounded = false,
|
|
30
30
|
block = false,
|
|
31
|
+
justify = "center",
|
|
31
32
|
active = false,
|
|
32
33
|
theme,
|
|
33
34
|
activeColor,
|
|
@@ -60,6 +61,7 @@ const classes = resolveClasses(button, {
|
|
|
60
61
|
color: (active && activeColor ? activeColor : color) as "neutral",
|
|
61
62
|
size,
|
|
62
63
|
block,
|
|
64
|
+
justify,
|
|
63
65
|
square: square || (!label && !Astro.slots.has("default")),
|
|
64
66
|
rounded,
|
|
65
67
|
leading: isLeading,
|
|
@@ -11,7 +11,7 @@ export const createButtonTheme = (colors: readonly string[] = colorConfig.colors
|
|
|
11
11
|
slots: ["root", "label", "leadingIcon", "leadingAvatar", "leadingAvatarSize", "trailingIcon"],
|
|
12
12
|
base: {
|
|
13
13
|
root: [
|
|
14
|
-
"group inline-flex items-center
|
|
14
|
+
"group inline-flex items-center font-medium ws-nowrap no-underline disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75",
|
|
15
15
|
"transition-colors outline-none",
|
|
16
16
|
"aria-invalid:border-error-500 aria-invalid:ring-error-500/20"
|
|
17
17
|
],
|
|
@@ -63,8 +63,16 @@ export const createButtonTheme = (colors: readonly string[] = colorConfig.colors
|
|
|
63
63
|
trailingIcon: "size-7"
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
|
+
justify: {
|
|
67
|
+
start: { root: "justify-start" },
|
|
68
|
+
center: { root: "justify-center" },
|
|
69
|
+
end: { root: "justify-end" },
|
|
70
|
+
between: { root: "justify-between" },
|
|
71
|
+
around: { root: "justify-around" },
|
|
72
|
+
evenly: { root: "justify-evenly" }
|
|
73
|
+
},
|
|
66
74
|
block: {
|
|
67
|
-
true: { root: "w-full
|
|
75
|
+
true: { root: "w-full", trailingIcon: "ms-auto" }
|
|
68
76
|
},
|
|
69
77
|
square: {
|
|
70
78
|
true: {},
|
|
@@ -218,7 +226,8 @@ export const createButtonTheme = (colors: readonly string[] = colorConfig.colors
|
|
|
218
226
|
defaultVariants: {
|
|
219
227
|
variant: "solid",
|
|
220
228
|
color: "primary",
|
|
221
|
-
size: "md"
|
|
229
|
+
size: "md",
|
|
230
|
+
justify: "center"
|
|
222
231
|
}
|
|
223
232
|
})
|
|
224
233
|
}
|
|
@@ -46,6 +46,10 @@ export interface ButtonProps extends LinkProps {
|
|
|
46
46
|
* Makes the button stretch to full width
|
|
47
47
|
*/
|
|
48
48
|
block?: boolean
|
|
49
|
+
/**
|
|
50
|
+
* Horizontal alignment of content inside the button flex container
|
|
51
|
+
*/
|
|
52
|
+
justify?: "start" | "center" | "end" | "between" | "around" | "evenly"
|
|
49
53
|
/**
|
|
50
54
|
* Applies active state styling
|
|
51
55
|
*/
|
|
@@ -4,7 +4,14 @@ export const createIconTheme = () =>
|
|
|
4
4
|
defineTheme({
|
|
5
5
|
slots: ["root"],
|
|
6
6
|
base: {
|
|
7
|
-
root: [
|
|
7
|
+
root: [
|
|
8
|
+
"shrink-0",
|
|
9
|
+
"inline-flex",
|
|
10
|
+
"items-center",
|
|
11
|
+
"justify-center",
|
|
12
|
+
"align-middle",
|
|
13
|
+
"leading-none"
|
|
14
|
+
]
|
|
8
15
|
},
|
|
9
16
|
variants: {
|
|
10
17
|
size: {
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { getImage } from "astro:assets"
|
|
2
|
+
import { Image, Picture, getImage } from "astro:assets"
|
|
3
3
|
import type { ImageMetadata } from "astro"
|
|
4
|
-
import Image from "astro/components/Image.astro"
|
|
5
|
-
import Picture from "astro/components/Picture.astro"
|
|
6
4
|
import RLAIcon from "../icon/RLAIcon.astro"
|
|
7
5
|
import { scv } from "css-variants"
|
|
8
6
|
import { useUi } from "../../utils/useUi"
|
|
@@ -42,6 +42,7 @@ const classes = resolveClasses(pageSection, {
|
|
|
42
42
|
}, useUi("pageSection", uiProp), className);
|
|
43
43
|
|
|
44
44
|
const hasDefaultSlot = Astro.slots.has("default");
|
|
45
|
+
const hasContentSlot = Astro.slots.has("content");
|
|
45
46
|
|
|
46
47
|
const Tag = as;
|
|
47
48
|
const TitleTag = variant === "hero" ? "h1" : "h2";
|
|
@@ -152,7 +153,14 @@ const TitleTag = variant === "hero" ? "h1" : "h2";
|
|
|
152
153
|
) : orientation === "horizontal" ? (
|
|
153
154
|
<div class="hidden lg:block" />
|
|
154
155
|
) : null}
|
|
156
|
+
|
|
157
|
+
{hasContentSlot && (
|
|
158
|
+
<div class="col-span-full w-full">
|
|
159
|
+
<slot name="content" />
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
155
162
|
</div>
|
|
156
163
|
|
|
157
164
|
<slot name="bottom" />
|
|
158
165
|
</Tag>
|
|
166
|
+
|
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
---
|
|
2
|
-
import { scv } from "css-variants"
|
|
1
|
+
---
|
|
2
|
+
import { scv } from "css-variants"
|
|
3
3
|
import { useUi } from "../../utils/useUi"
|
|
4
|
-
import { resolveClasses } from "../../utils/resolveClasses"
|
|
5
|
-
import type { PostProps } from "./post"
|
|
6
|
-
import postTheme from "./post.theme"
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import RLABadge from "../badge/RLABadge.astro"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const post = scv(postTheme)
|
|
13
|
-
|
|
14
|
-
const {
|
|
15
|
-
title,
|
|
16
|
-
description,
|
|
17
|
-
date,
|
|
18
|
-
image,
|
|
19
|
-
to,
|
|
20
|
-
badge,
|
|
21
|
-
badgeColor = "primary",
|
|
22
|
-
variant = "soft",
|
|
23
|
-
orientation = "vertical",
|
|
24
|
-
ui: uiProp,
|
|
25
|
-
class: className,
|
|
26
|
-
...rest
|
|
27
|
-
} = Astro.props as PostProps
|
|
28
|
-
|
|
29
|
-
const classes = resolveClasses(post, { variant, orientation }, useUi("post", uiProp), className);
|
|
30
|
-
|
|
31
|
-
const Tag = to ? "a" : "div";
|
|
32
|
-
const formattedDate = date ? (date instanceof Date ? date.toLocaleDateString() : date) : null;
|
|
33
|
-
---
|
|
34
|
-
<Tag class={classes.root} data-slot="root" {...(to ? { href: to } : {})} {...rest}>
|
|
35
|
-
<slot name="image" {...{ ui: classes }}>
|
|
36
|
-
{image && (
|
|
37
|
-
<div class={classes.imageWrapper} data-slot="image-wrapper">
|
|
38
|
-
{typeof image === 'string' ? (
|
|
39
|
-
<img src={image} alt={title} class={classes.image} data-slot="image" />
|
|
40
|
-
) : (
|
|
41
|
-
<Image src={image as ImageMetadata} alt={title || ""} class={classes.image} data-slot="image" />
|
|
42
|
-
)}
|
|
43
|
-
</div>
|
|
44
|
-
)}
|
|
45
|
-
</slot>
|
|
46
|
-
|
|
47
|
-
<div class={classes.content} data-slot="content">
|
|
48
|
-
<slot name="title" {...{ ui: classes }}>
|
|
49
|
-
{title && <h3 class={classes.title} data-slot="title">{title}</h3>}
|
|
50
|
-
</slot>
|
|
51
|
-
|
|
52
|
-
<slot name="description" {...{ ui: classes }}>
|
|
53
|
-
{description && <p class={classes.description} data-slot="description">{description}</p>}
|
|
54
|
-
</slot>
|
|
55
|
-
|
|
56
|
-
<slot name="badge" {...{ ui: classes }}>
|
|
57
|
-
{badge && <RLABadge color={badgeColor as any} size="sm" shape="pill" class={classes.badge} data-slot="badge">{badge}</RLABadge>}
|
|
58
|
-
</slot>
|
|
59
|
-
|
|
60
|
-
<slot name="date" {...{ ui: classes }}>
|
|
61
|
-
{formattedDate && <time class={classes.date} data-slot="date">{formattedDate}</time>}
|
|
62
|
-
</slot>
|
|
63
|
-
|
|
64
|
-
<slot />
|
|
65
|
-
</div>
|
|
66
|
-
|
|
67
|
-
{Astro.slots.has("footer") && (
|
|
68
|
-
<div class={classes.footer} data-slot="footer">
|
|
69
|
-
<slot name="footer" {...{ ui: classes }} />
|
|
70
|
-
</div>
|
|
71
|
-
)}
|
|
4
|
+
import { resolveClasses } from "../../utils/resolveClasses"
|
|
5
|
+
import type { PostProps } from "./post"
|
|
6
|
+
import postTheme from "./post.theme"
|
|
7
|
+
import { Image } from "astro:assets"
|
|
8
|
+
import type { ImageMetadata } from "astro"
|
|
9
|
+
import RLABadge from "../badge/RLABadge.astro"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
const post = scv(postTheme)
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
title,
|
|
16
|
+
description,
|
|
17
|
+
date,
|
|
18
|
+
image,
|
|
19
|
+
to,
|
|
20
|
+
badge,
|
|
21
|
+
badgeColor = "primary",
|
|
22
|
+
variant = "soft",
|
|
23
|
+
orientation = "vertical",
|
|
24
|
+
ui: uiProp,
|
|
25
|
+
class: className,
|
|
26
|
+
...rest
|
|
27
|
+
} = Astro.props as PostProps
|
|
28
|
+
|
|
29
|
+
const classes = resolveClasses(post, { variant, orientation }, useUi("post", uiProp), className);
|
|
30
|
+
|
|
31
|
+
const Tag = to ? "a" : "div";
|
|
32
|
+
const formattedDate = date ? (date instanceof Date ? date.toLocaleDateString() : date) : null;
|
|
33
|
+
---
|
|
34
|
+
<Tag class={classes.root} data-slot="root" {...(to ? { href: to } : {})} {...rest}>
|
|
35
|
+
<slot name="image" {...{ ui: classes }}>
|
|
36
|
+
{image && (
|
|
37
|
+
<div class={classes.imageWrapper} data-slot="image-wrapper">
|
|
38
|
+
{typeof image === 'string' ? (
|
|
39
|
+
<img src={image} alt={title} class={classes.image} data-slot="image" />
|
|
40
|
+
) : (
|
|
41
|
+
<Image src={image as ImageMetadata} alt={title || ""} class={classes.image} data-slot="image" />
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
)}
|
|
45
|
+
</slot>
|
|
46
|
+
|
|
47
|
+
<div class={classes.content} data-slot="content">
|
|
48
|
+
<slot name="title" {...{ ui: classes }}>
|
|
49
|
+
{title && <h3 class={classes.title} data-slot="title">{title}</h3>}
|
|
50
|
+
</slot>
|
|
51
|
+
|
|
52
|
+
<slot name="description" {...{ ui: classes }}>
|
|
53
|
+
{description && <p class={classes.description} data-slot="description">{description}</p>}
|
|
54
|
+
</slot>
|
|
55
|
+
|
|
56
|
+
<slot name="badge" {...{ ui: classes }}>
|
|
57
|
+
{badge && <RLABadge color={badgeColor as any} size="sm" shape="pill" class={classes.badge} data-slot="badge">{badge}</RLABadge>}
|
|
58
|
+
</slot>
|
|
59
|
+
|
|
60
|
+
<slot name="date" {...{ ui: classes }}>
|
|
61
|
+
{formattedDate && <time class={classes.date} data-slot="date">{formattedDate}</time>}
|
|
62
|
+
</slot>
|
|
63
|
+
|
|
64
|
+
<slot />
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
{Astro.slots.has("footer") && (
|
|
68
|
+
<div class={classes.footer} data-slot="footer">
|
|
69
|
+
<slot name="footer" {...{ ui: classes }} />
|
|
70
|
+
</div>
|
|
71
|
+
)}
|
|
72
72
|
</Tag>
|
|
@@ -2,71 +2,96 @@
|
|
|
2
2
|
import { scv } from "css-variants"
|
|
3
3
|
import { useUi } from "../../utils/useUi"
|
|
4
4
|
import { resolveClasses } from "../../utils/resolveClasses"
|
|
5
|
-
import type { TabsProps } from "./tabs"
|
|
5
|
+
import type { TabsProps, TabItem } from "./tabs"
|
|
6
6
|
import tabsTheme from "./tabs.theme"
|
|
7
|
-
|
|
7
|
+
import RLABadge from "../badge/RLABadge.astro"
|
|
8
8
|
|
|
9
9
|
const tabs = scv(tabsTheme)
|
|
10
10
|
|
|
11
11
|
const {
|
|
12
12
|
items = [],
|
|
13
13
|
value = "",
|
|
14
|
-
|
|
14
|
+
defaultValue = "",
|
|
15
|
+
variant = "pill",
|
|
15
16
|
orientation = "horizontal",
|
|
16
17
|
size = "md",
|
|
17
18
|
color = "primary",
|
|
19
|
+
content = true,
|
|
20
|
+
unmountOnHide = true,
|
|
21
|
+
valueKey = "value",
|
|
22
|
+
labelKey = "label",
|
|
18
23
|
ui: uiProp,
|
|
19
24
|
class: className,
|
|
20
25
|
...rest
|
|
21
26
|
} = Astro.props as TabsProps
|
|
22
27
|
|
|
28
|
+
// Normalize variant names for backward compatibility (solid -> pill, pills -> pill, underline -> underline, link -> underline)
|
|
29
|
+
const normalizedVariant = (variant === "solid" || variant === "pills") ? "pill" : (variant === "link" ? "underline" : variant);
|
|
30
|
+
|
|
23
31
|
const classes = resolveClasses(tabs, {
|
|
24
|
-
variant,
|
|
32
|
+
variant: normalizedVariant,
|
|
25
33
|
orientation,
|
|
26
34
|
size
|
|
27
35
|
}, useUi("tabs", uiProp), className);
|
|
28
36
|
|
|
29
|
-
const initialValue = value || (items.length > 0 ? items[0]
|
|
37
|
+
const initialValue = value || defaultValue || (items.length > 0 ? (items[0][valueKey] ?? items[0].value ?? "0") : "")
|
|
30
38
|
---
|
|
31
|
-
<rla-tabs {...rest}>
|
|
39
|
+
<rla-tabs {...rest} data-orientation={orientation} data-variant={normalizedVariant}>
|
|
32
40
|
<div class={classes.root} data-slot="root" data-tabs-container data-value={initialValue}>
|
|
33
41
|
<div class={classes.list} data-slot="list" role="tablist">
|
|
34
|
-
{
|
|
35
|
-
|
|
42
|
+
<div class={classes.indicator} data-slot="indicator" style="display: none;"></div>
|
|
43
|
+
{items.map((item, index) => {
|
|
44
|
+
const itemVal = item[valueKey] ?? item.value ?? String(index)
|
|
45
|
+
const itemLabel = item[labelKey] ?? item.label
|
|
46
|
+
const active = itemVal === initialValue
|
|
36
47
|
return (
|
|
37
48
|
<button
|
|
38
49
|
type="button"
|
|
39
50
|
class={classes.trigger}
|
|
40
51
|
data-slot="trigger"
|
|
41
52
|
role="tab"
|
|
42
|
-
data-value={
|
|
53
|
+
data-value={itemVal}
|
|
43
54
|
data-state={active ? "active" : "inactive"}
|
|
44
55
|
disabled={item.disabled}
|
|
45
56
|
>
|
|
46
|
-
{item.icon && <span class={item.icon + "
|
|
47
|
-
{
|
|
57
|
+
{item.icon && <span class={item.icon + " " + classes.leadingIcon} data-slot="leadingIcon" />}
|
|
58
|
+
{itemLabel && <span class={classes.label} data-slot="label">{itemLabel}</span>}
|
|
59
|
+
{item.badge !== undefined && item.badge !== null && (
|
|
60
|
+
<RLABadge
|
|
61
|
+
size="sm"
|
|
62
|
+
variant="subtle"
|
|
63
|
+
color="neutral"
|
|
64
|
+
class={classes.trailingBadge}
|
|
65
|
+
data-slot="trailingBadge"
|
|
66
|
+
>
|
|
67
|
+
{typeof item.badge === "object" ? item.badge.label : item.badge}
|
|
68
|
+
</RLABadge>
|
|
69
|
+
)}
|
|
48
70
|
</button>
|
|
49
71
|
)
|
|
50
72
|
})}
|
|
51
73
|
</div>
|
|
52
|
-
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
{content && (
|
|
75
|
+
<div class="flex-1">
|
|
76
|
+
<slot>
|
|
77
|
+
{items.map((item, index) => {
|
|
78
|
+
const itemVal = item[valueKey] ?? item.value ?? String(index)
|
|
79
|
+
const active = itemVal === initialValue
|
|
80
|
+
return (
|
|
81
|
+
<div
|
|
82
|
+
class={classes.content}
|
|
83
|
+
data-slot="content"
|
|
84
|
+
role="tabpanel"
|
|
85
|
+
data-value={itemVal}
|
|
86
|
+
hidden={active ? undefined : "until-found"}
|
|
87
|
+
>
|
|
88
|
+
{item.content}
|
|
89
|
+
</div>
|
|
90
|
+
)
|
|
91
|
+
})}
|
|
92
|
+
</slot>
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
70
95
|
</div>
|
|
71
96
|
</rla-tabs>
|
|
72
97
|
|
|
@@ -74,19 +99,59 @@ const initialValue = value || (items.length > 0 ? items[0]?.value : "")
|
|
|
74
99
|
if (typeof HTMLElement !== 'undefined') {
|
|
75
100
|
class RLATabs extends HTMLElement {
|
|
76
101
|
private tabsCleanup: (() => void) | null = null;
|
|
102
|
+
private resizeObserver: ResizeObserver | null = null;
|
|
77
103
|
|
|
78
104
|
connectedCallback() {
|
|
79
105
|
this.style.display = 'contents';
|
|
80
106
|
const container = this.querySelector("[data-tabs-container]") as HTMLElement | null;
|
|
81
107
|
if (!container) return;
|
|
82
108
|
|
|
109
|
+
const list = container.querySelector("[data-slot=list]") as HTMLElement | null;
|
|
110
|
+
const indicator = container.querySelector("[data-slot=indicator]") as HTMLElement | null;
|
|
111
|
+
const orientation = this.dataset.orientation || "horizontal";
|
|
112
|
+
const isVertical = orientation === "vertical";
|
|
113
|
+
|
|
114
|
+
const updateIndicator = (activeTrigger: HTMLElement | null) => {
|
|
115
|
+
if (!indicator || !list || !activeTrigger) {
|
|
116
|
+
if (indicator) indicator.style.display = 'none';
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const listRect = list.getBoundingClientRect();
|
|
121
|
+
const triggerRect = activeTrigger.getBoundingClientRect();
|
|
122
|
+
|
|
123
|
+
if (triggerRect.width === 0 && triggerRect.height === 0) {
|
|
124
|
+
indicator.style.display = 'none';
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
indicator.style.display = 'block';
|
|
129
|
+
|
|
130
|
+
if (isVertical) {
|
|
131
|
+
const top = triggerRect.top - listRect.top + list.scrollTop;
|
|
132
|
+
const height = triggerRect.height;
|
|
133
|
+
indicator.style.transform = `translateY(${top}px)`;
|
|
134
|
+
indicator.style.height = `${height}px`;
|
|
135
|
+
} else {
|
|
136
|
+
const left = triggerRect.left - listRect.left + list.scrollLeft;
|
|
137
|
+
const width = triggerRect.width;
|
|
138
|
+
indicator.style.transform = `translateX(${left}px)`;
|
|
139
|
+
indicator.style.width = `${width}px`;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
83
143
|
const containerValue = container.dataset.value;
|
|
84
144
|
const triggers = container.querySelectorAll("[role=tab]");
|
|
85
145
|
const panels = container.querySelectorAll("[role=tabpanel]");
|
|
86
146
|
|
|
147
|
+
let activeTrigger: HTMLElement | null = null;
|
|
148
|
+
|
|
87
149
|
triggers.forEach(trigger => {
|
|
88
|
-
const
|
|
89
|
-
|
|
150
|
+
const el = trigger as HTMLElement;
|
|
151
|
+
const val = el.dataset.value;
|
|
152
|
+
const isActive = val === containerValue;
|
|
153
|
+
el.setAttribute("data-state", isActive ? "active" : "inactive");
|
|
154
|
+
if (isActive) activeTrigger = el;
|
|
90
155
|
});
|
|
91
156
|
|
|
92
157
|
panels.forEach(panel => {
|
|
@@ -98,6 +163,17 @@ const initialValue = value || (items.length > 0 ? items[0]?.value : "")
|
|
|
98
163
|
}
|
|
99
164
|
});
|
|
100
165
|
|
|
166
|
+
// Initialize indicator position
|
|
167
|
+
requestAnimationFrame(() => updateIndicator(activeTrigger));
|
|
168
|
+
|
|
169
|
+
if (window.ResizeObserver && list) {
|
|
170
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
171
|
+
const currentActive = list.querySelector("[role=tab][data-state=active]") as HTMLElement | null;
|
|
172
|
+
updateIndicator(currentActive);
|
|
173
|
+
});
|
|
174
|
+
this.resizeObserver.observe(list);
|
|
175
|
+
}
|
|
176
|
+
|
|
101
177
|
const clickCleanups: (() => void)[] = [];
|
|
102
178
|
const beforeMatchCleanups: (() => void)[] = [];
|
|
103
179
|
|
|
@@ -117,11 +193,14 @@ const initialValue = value || (items.length > 0 ? items[0]?.value : "")
|
|
|
117
193
|
triggers.forEach(triggerNode => {
|
|
118
194
|
const trigger = triggerNode as HTMLButtonElement;
|
|
119
195
|
const onClick = () => {
|
|
196
|
+
if (trigger.disabled) return;
|
|
120
197
|
const targetValue = trigger.dataset.value;
|
|
121
198
|
|
|
122
199
|
triggers.forEach(tNode => {
|
|
123
200
|
const t = tNode as HTMLButtonElement;
|
|
124
|
-
|
|
201
|
+
const isTarget = t.dataset.value === targetValue;
|
|
202
|
+
t.setAttribute("data-state", isTarget ? "active" : "inactive");
|
|
203
|
+
if (isTarget) updateIndicator(t);
|
|
125
204
|
});
|
|
126
205
|
|
|
127
206
|
panels.forEach(panel => {
|
|
@@ -139,6 +218,10 @@ const initialValue = value || (items.length > 0 ? items[0]?.value : "")
|
|
|
139
218
|
this.tabsCleanup = () => {
|
|
140
219
|
clickCleanups.forEach(fn => fn());
|
|
141
220
|
beforeMatchCleanups.forEach(fn => fn());
|
|
221
|
+
if (this.resizeObserver) {
|
|
222
|
+
this.resizeObserver.disconnect();
|
|
223
|
+
this.resizeObserver = null;
|
|
224
|
+
}
|
|
142
225
|
};
|
|
143
226
|
}
|
|
144
227
|
|
|
@@ -155,3 +238,4 @@ const initialValue = value || (items.length > 0 ? items[0]?.value : "")
|
|
|
155
238
|
}
|
|
156
239
|
}
|
|
157
240
|
</script>
|
|
241
|
+
|
|
@@ -2,48 +2,86 @@ import { defineTheme } from "../../utils/defineTheme"
|
|
|
2
2
|
|
|
3
3
|
export const createTabsTheme = () =>
|
|
4
4
|
defineTheme({
|
|
5
|
-
slots: [
|
|
5
|
+
slots: [
|
|
6
|
+
"root",
|
|
7
|
+
"list",
|
|
8
|
+
"indicator",
|
|
9
|
+
"trigger",
|
|
10
|
+
"leadingIcon",
|
|
11
|
+
"leadingAvatar",
|
|
12
|
+
"label",
|
|
13
|
+
"trailingBadge",
|
|
14
|
+
"content"
|
|
15
|
+
],
|
|
6
16
|
base: {
|
|
7
|
-
root: "
|
|
8
|
-
list: "
|
|
17
|
+
root: "flex gap-2 w-full",
|
|
18
|
+
list: "relative flex p-1 group items-center min-w-0",
|
|
19
|
+
indicator:
|
|
20
|
+
"absolute transition-[translate,width,height,top,left] duration-200 ease-out pointer-events-none",
|
|
9
21
|
trigger:
|
|
10
|
-
"inline-flex items-center justify-center
|
|
11
|
-
|
|
12
|
-
|
|
22
|
+
"group relative inline-flex items-center justify-center min-w-0 whitespace-nowrap font-medium transition-colors cursor-pointer disabled:cursor-not-allowed disabled:opacity-75 focus-visible:outline-none data-[state=inactive]:text-muted-foreground hover:data-[state=inactive]:text-foreground",
|
|
23
|
+
leadingIcon: "shrink-0",
|
|
24
|
+
leadingAvatar: "shrink-0",
|
|
25
|
+
label: "truncate",
|
|
26
|
+
trailingBadge: "shrink-0",
|
|
27
|
+
content: "w-full focus-visible:outline-none"
|
|
13
28
|
},
|
|
14
29
|
variants: {
|
|
15
30
|
variant: {
|
|
16
|
-
|
|
17
|
-
list: "bg-muted
|
|
18
|
-
trigger:
|
|
19
|
-
|
|
31
|
+
pill: {
|
|
32
|
+
list: "bg-muted/60 dark:bg-muted/40 rounded-lg p-1",
|
|
33
|
+
trigger: "grow rounded-md data-[state=active]:text-foreground z-10",
|
|
34
|
+
indicator: "bg-background rounded-md shadow-xs top-1 bottom-1 h-[calc(100%-8px)]"
|
|
20
35
|
},
|
|
21
36
|
underline: {
|
|
22
|
-
list: "bg-transparent border-b border-border
|
|
23
|
-
trigger:
|
|
24
|
-
|
|
37
|
+
list: "bg-transparent border-b border-border p-0 gap-2 -mb-px",
|
|
38
|
+
trigger: "rounded-md data-[state=active]:text-primary pb-2 pt-1 px-3 z-10",
|
|
39
|
+
indicator: "bg-primary rounded-full -bottom-px h-0.5 left-0"
|
|
25
40
|
},
|
|
26
|
-
|
|
27
|
-
list: "bg-transparent p-0 gap-2",
|
|
28
|
-
trigger:
|
|
29
|
-
|
|
41
|
+
link: {
|
|
42
|
+
list: "bg-transparent border-b border-border p-0 gap-2 -mb-px",
|
|
43
|
+
trigger: "rounded-md data-[state=active]:text-primary pb-2 pt-1 px-3 z-10",
|
|
44
|
+
indicator: "bg-primary rounded-full -bottom-px h-0.5 left-0"
|
|
30
45
|
}
|
|
31
46
|
},
|
|
32
47
|
orientation: {
|
|
33
|
-
horizontal: {
|
|
48
|
+
horizontal: {
|
|
49
|
+
root: "flex-col",
|
|
50
|
+
list: "w-full",
|
|
51
|
+
indicator: "",
|
|
52
|
+
trigger: "justify-center"
|
|
53
|
+
},
|
|
34
54
|
vertical: {
|
|
35
55
|
root: "flex-row gap-6",
|
|
36
|
-
list: "flex-col items-stretch
|
|
56
|
+
list: "flex-col items-stretch w-48 border-r border-border p-0",
|
|
57
|
+
trigger: "justify-start text-left"
|
|
37
58
|
}
|
|
38
59
|
},
|
|
39
60
|
size: {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
61
|
+
xs: {
|
|
62
|
+
trigger: "px-2 py-1 text-xs gap-1",
|
|
63
|
+
leadingIcon: "size-3.5"
|
|
64
|
+
},
|
|
65
|
+
sm: {
|
|
66
|
+
trigger: "px-2.5 py-1.5 text-xs gap-1.5",
|
|
67
|
+
leadingIcon: "size-4"
|
|
68
|
+
},
|
|
69
|
+
md: {
|
|
70
|
+
trigger: "px-3 py-1.5 text-sm gap-1.5",
|
|
71
|
+
leadingIcon: "size-4"
|
|
72
|
+
},
|
|
73
|
+
lg: {
|
|
74
|
+
trigger: "px-3.5 py-2 text-sm gap-2",
|
|
75
|
+
leadingIcon: "size-5"
|
|
76
|
+
},
|
|
77
|
+
xl: {
|
|
78
|
+
trigger: "px-4 py-2.5 text-base gap-2",
|
|
79
|
+
leadingIcon: "size-5"
|
|
80
|
+
}
|
|
43
81
|
}
|
|
44
82
|
},
|
|
45
83
|
defaultVariants: {
|
|
46
|
-
variant: "
|
|
84
|
+
variant: "pill",
|
|
47
85
|
orientation: "horizontal",
|
|
48
86
|
size: "md"
|
|
49
87
|
}
|