@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.
@@ -1,37 +1,78 @@
1
1
  import type { ThemeColor } from "../../types/component"
2
+
2
3
  export interface TabItem {
3
- label: string
4
- value: string
4
+ label?: string
5
+ value?: string
5
6
  icon?: string
7
+ badge?: string | number | { label?: string | number; color?: string; variant?: string }
6
8
  content?: string
7
9
  disabled?: boolean
10
+ slot?: string
11
+ class?: any
12
+ ui?: any
13
+ [key: string]: any
8
14
  }
9
15
 
10
- export interface TabsProps {
16
+ export interface TabsProps<T extends TabItem = TabItem> {
11
17
  /**
12
18
  * The tab items.
13
19
  */
14
- items?: TabItem[]
20
+ items?: T[]
15
21
  /**
16
22
  * Active tab value.
17
23
  */
18
24
  value?: string
25
+ /**
26
+ * Default active tab value when uncontrolled.
27
+ */
28
+ defaultValue?: string
19
29
  /**
20
30
  * Tab variant.
31
+ *
32
+ * @defaultValue "pill"
21
33
  */
22
- variant?: "solid" | "underline" | "pills"
34
+ variant?: "pill" | "underline" | "link" | "solid" | "pills"
23
35
  /**
24
36
  * Layout orientation.
37
+ *
38
+ * @defaultValue "horizontal"
25
39
  */
26
40
  orientation?: "horizontal" | "vertical"
27
41
  /**
28
42
  * Size scale.
43
+ *
44
+ * @defaultValue "md"
29
45
  */
30
- size?: "sm" | "md" | "lg"
46
+ size?: "xs" | "sm" | "md" | "lg" | "xl"
31
47
  /**
32
48
  * Color theme.
33
49
  */
34
50
  color?: ThemeColor
51
+ /**
52
+ * Whether to render content panels automatically. Set to false for tab bar only.
53
+ *
54
+ * @defaultValue true
55
+ */
56
+ content?: boolean
57
+ /**
58
+ * Unmount hidden tab panels from DOM. Defaults to true. If false, panel is hidden via
59
+ * CSS/attribute.
60
+ *
61
+ * @defaultValue true
62
+ */
63
+ unmountOnHide?: boolean
64
+ /**
65
+ * Property key for value on item object.
66
+ *
67
+ * @defaultValue "value"
68
+ */
69
+ valueKey?: string
70
+ /**
71
+ * Property key for label on item object.
72
+ *
73
+ * @defaultValue "label"
74
+ */
75
+ labelKey?: string
35
76
  /**
36
77
  * Slot-specific CSS class overrides.
37
78
  */
@@ -1,21 +1,195 @@
1
- ---
2
- import { scv } from "css-variants"
1
+ ---
2
+ import { scv, cx } from "css-variants"
3
3
  import { useUi } from "../../utils/useUi"
4
- import { resolveClasses } from "../../utils/resolveClasses"
5
- import type { TimelineProps } from "./timeline"
6
- import timelineTheme from "./timeline.theme"
7
-
8
-
9
- const timeline = scv(timelineTheme)
10
-
11
- const {
12
- ui: uiProp,
13
- class: className,
14
- ...rest
15
- } = Astro.props as TimelineProps
16
-
17
- const classes = resolveClasses(timeline, {}, useUi("timeline", uiProp), className);
18
- ---
19
- <div class={classes.root} data-slot="root" {...rest}>
20
- <slot />
21
- </div>
4
+ import { resolveClasses } from "../../utils/resolveClasses"
5
+ import type { TimelineProps, TimelineItem } from "./timeline"
6
+ import { resolveTheme } from "../../utils/resolveTheme"
7
+ import timelineThemeDefault, { createTimelineTheme } from "./timeline.theme"
8
+ const timelineTheme = resolveTheme(timelineThemeDefault, createTimelineTheme)
9
+ import RLAAvatar from "../avatar/RLAAvatar.astro"
10
+ import RLAIcon from "../icon/RLAIcon.astro"
11
+
12
+ const timeline = scv(timelineTheme)
13
+
14
+ const {
15
+ as: Component = "div",
16
+ items,
17
+ size = "md",
18
+ color = "primary",
19
+ orientation = "vertical",
20
+ valueKey = "value",
21
+ defaultValue,
22
+ modelValue,
23
+ reverse = false,
24
+ ui: uiProp,
25
+ class: className,
26
+ ...rest
27
+ } = Astro.props as TimelineProps
28
+
29
+ const classes = resolveClasses(
30
+ timeline,
31
+ {
32
+ orientation,
33
+ size,
34
+ color,
35
+ reverse
36
+ },
37
+ useUi("timeline", uiProp),
38
+ className
39
+ )
40
+
41
+ const activeVal = modelValue !== undefined ? modelValue : defaultValue
42
+
43
+ function getItemValue(item: TimelineItem, index: number) {
44
+ return item[valueKey] !== undefined ? item[valueKey] : index
45
+ }
46
+
47
+ function getItemState(item: TimelineItem, index: number): "active" | "completed" | undefined {
48
+ if (activeVal === undefined) return undefined
49
+
50
+ let currentIndex = -1
51
+ if (items && items.length > 0) {
52
+ if (typeof activeVal === "string") {
53
+ currentIndex = items.findIndex((i) => getItemValue(i, items.indexOf(i)) === activeVal)
54
+ } else if (typeof activeVal === "number") {
55
+ if (reverse) {
56
+ currentIndex = items.length - 1 - activeVal
57
+ } else {
58
+ currentIndex = activeVal
59
+ }
60
+ }
61
+ }
62
+
63
+ if (currentIndex === -1) return undefined
64
+ if (index === currentIndex) return "active"
65
+
66
+ if (reverse) {
67
+ return index > currentIndex ? "completed" : undefined
68
+ } else {
69
+ return index < currentIndex ? "completed" : undefined
70
+ }
71
+ }
72
+
73
+ function getSlotName(item: TimelineItem, slotType: string): string {
74
+ return item.slot ? `${item.slot}-${slotType}` : slotType
75
+ }
76
+ ---
77
+
78
+ <Component
79
+ data-orientation={orientation}
80
+ data-slot="root"
81
+ class={classes.root}
82
+ {...rest}
83
+ >
84
+ {items && items.length > 0 ? (
85
+ items.map((item, index) => {
86
+ const state = getItemState(item, index)
87
+ const isLast = index === items.length - 1
88
+
89
+ const itemIndicatorSlot = getSlotName(item, "indicator")
90
+ const itemWrapperSlot = getSlotName(item, "wrapper")
91
+ const itemDateSlot = getSlotName(item, "date")
92
+ const itemTitleSlot = getSlotName(item, "title")
93
+ const itemDescriptionSlot = getSlotName(item, "description")
94
+
95
+ const hasCustomIndicator = Astro.slots.has(itemIndicatorSlot) || (item.slot && Astro.slots.has("indicator"))
96
+ const hasCustomWrapper = Astro.slots.has(itemWrapperSlot) || (item.slot && Astro.slots.has("wrapper"))
97
+ const hasCustomDate = Astro.slots.has(itemDateSlot) || (item.slot && Astro.slots.has("date"))
98
+ const hasCustomTitle = Astro.slots.has(itemTitleSlot) || (item.slot && Astro.slots.has("title"))
99
+ const hasCustomDescription = Astro.slots.has(itemDescriptionSlot) || (item.slot && Astro.slots.has("description"))
100
+
101
+ return (
102
+ <div
103
+ data-slot="item"
104
+ class={cx(classes.item, item.ui?.item, item.class)}
105
+ data-state={state}
106
+ >
107
+ <div
108
+ data-slot="container"
109
+ class={cx(classes.container, item.ui?.container)}
110
+ >
111
+ <RLAAvatar
112
+ size={size as any}
113
+ src={item.avatar?.src}
114
+ alt={item.avatar?.alt}
115
+ {...(typeof item.avatar === "object" ? item.avatar : {})}
116
+ data-slot="indicator"
117
+ class={cx(classes.indicator, item.ui?.indicator)}
118
+ ui={{ icon: "text-highlighted group-data-[state=completed]:text-inverted group-data-[state=active]:text-inverted", fallback: "text-highlighted group-data-[state=completed]:text-inverted group-data-[state=active]:text-inverted" }}
119
+ >
120
+ {hasCustomIndicator ? (
121
+ <slot name={itemIndicatorSlot} item={item}>
122
+ <slot name="indicator" item={item} />
123
+ </slot>
124
+ ) : item.icon ? (
125
+ <RLAIcon name={item.icon} class="text-inherit" />
126
+ ) : null}
127
+ </RLAAvatar>
128
+
129
+ {!isLast && (
130
+ <div
131
+ data-slot="separator"
132
+ class={cx(classes.separator, item.ui?.separator)}
133
+ data-orientation={orientation}
134
+ />
135
+ )}
136
+ </div>
137
+
138
+ <div
139
+ data-slot="wrapper"
140
+ class={cx(classes.wrapper, item.ui?.wrapper)}
141
+ >
142
+ {hasCustomWrapper ? (
143
+ <slot name={itemWrapperSlot} item={item}>
144
+ <slot name="wrapper" item={item} />
145
+ </slot>
146
+ ) : (
147
+ <>
148
+ {(item.date || hasCustomDate) && (
149
+ <div
150
+ data-slot="date"
151
+ class={cx(classes.date, item.ui?.date)}
152
+ >
153
+ <slot name={itemDateSlot} item={item}>
154
+ <slot name="date" item={item}>
155
+ {item.date}
156
+ </slot>
157
+ </slot>
158
+ </div>
159
+ )}
160
+
161
+ {(item.title || hasCustomTitle) && (
162
+ <div
163
+ data-slot="title"
164
+ class={cx(classes.title, item.ui?.title)}
165
+ >
166
+ <slot name={itemTitleSlot} item={item}>
167
+ <slot name="title" item={item}>
168
+ {item.title}
169
+ </slot>
170
+ </slot>
171
+ </div>
172
+ )}
173
+
174
+ {(item.description || hasCustomDescription) && (
175
+ <div
176
+ data-slot="description"
177
+ class={cx(classes.description, item.ui?.description)}
178
+ >
179
+ <slot name={itemDescriptionSlot} item={item}>
180
+ <slot name="description" item={item}>
181
+ {item.description}
182
+ </slot>
183
+ </slot>
184
+ </div>
185
+ )}
186
+ </>
187
+ )}
188
+ </div>
189
+ </div>
190
+ )
191
+ })
192
+ ) : (
193
+ <slot />
194
+ )}
195
+ </Component>
@@ -2,17 +2,315 @@ import { defineTheme } from "../../utils/defineTheme"
2
2
 
3
3
  export const createTimelineTheme = () =>
4
4
  defineTheme({
5
- slots: ["root", "node", "dot", "dotInner", "header", "content"],
5
+ slots: [
6
+ "root",
7
+ "item",
8
+ "container",
9
+ "indicator",
10
+ "separator",
11
+ "wrapper",
12
+ "date",
13
+ "title",
14
+ "description"
15
+ ],
6
16
  base: {
7
- root: "relative flex flex-col pl-6 border-l-2 border-gray-800 ml-2 py-2",
8
- node: "relative timeline-node",
9
- dot: "absolute -left-[33px] top-[2px] flex h-4 w-4 items-center justify-center rounded-full bg-gray-950 ring-2 ring-gray-800",
10
- dotInner: "h-2 w-2 rounded-full bg-primary-500",
11
- header: "flex items-center justify-between gap-4 mb-3",
12
- content: "space-y-2 text-sm text-gray-400"
17
+ root: "flex gap-1.5",
18
+ item: "group relative flex flex-1 gap-3",
19
+ container: "relative flex items-center gap-1.5",
20
+ indicator:
21
+ "group-data-[state=completed]:text-inverted group-data-[state=active]:text-inverted text-highlighted",
22
+ separator: "flex-1 rounded-full bg-elevated",
23
+ wrapper: "w-full",
24
+ date: "text-dimmed text-xs/5",
25
+ title: "font-medium text-highlighted text-sm",
26
+ description: "text-muted text-wrap text-sm"
13
27
  },
14
- variants: {},
15
- defaultVariants: {}
28
+ variants: {
29
+ orientation: {
30
+ horizontal: {
31
+ root: "flex-row w-full",
32
+ item: "flex-col",
33
+ separator: "h-0.5"
34
+ },
35
+ vertical: {
36
+ root: "flex-col",
37
+ container: "flex-col",
38
+ separator: "w-0.5"
39
+ }
40
+ },
41
+ color: {
42
+ primary: {
43
+ indicator: "group-data-[state=completed]:bg-primary group-data-[state=active]:bg-primary"
44
+ },
45
+ secondary: {
46
+ indicator:
47
+ "group-data-[state=completed]:bg-secondary group-data-[state=active]:bg-secondary"
48
+ },
49
+ success: {
50
+ indicator: "group-data-[state=completed]:bg-success group-data-[state=active]:bg-success"
51
+ },
52
+ info: {
53
+ indicator: "group-data-[state=completed]:bg-info group-data-[state=active]:bg-info"
54
+ },
55
+ warning: {
56
+ indicator: "group-data-[state=completed]:bg-warning group-data-[state=active]:bg-warning"
57
+ },
58
+ error: {
59
+ indicator: "group-data-[state=completed]:bg-error group-data-[state=active]:bg-error"
60
+ },
61
+ neutral: {
62
+ indicator:
63
+ "group-data-[state=completed]:bg-inverted group-data-[state=active]:bg-inverted"
64
+ }
65
+ },
66
+ size: {
67
+ "3xs": {},
68
+ "2xs": {},
69
+ "xs": {},
70
+ "sm": {},
71
+ "md": {},
72
+ "lg": {},
73
+ "xl": {},
74
+ "2xl": {},
75
+ "3xl": {}
76
+ },
77
+ reverse: {
78
+ true: {},
79
+ false: {}
80
+ }
81
+ },
82
+ compoundVariants: [
83
+ {
84
+ color: "primary",
85
+ reverse: false,
86
+ class: {
87
+ separator: "group-data-[state=completed]:bg-primary"
88
+ }
89
+ },
90
+ {
91
+ color: "secondary",
92
+ reverse: false,
93
+ class: {
94
+ separator: "group-data-[state=completed]:bg-secondary"
95
+ }
96
+ },
97
+ {
98
+ color: "success",
99
+ reverse: false,
100
+ class: {
101
+ separator: "group-data-[state=completed]:bg-success"
102
+ }
103
+ },
104
+ {
105
+ color: "info",
106
+ reverse: false,
107
+ class: {
108
+ separator: "group-data-[state=completed]:bg-info"
109
+ }
110
+ },
111
+ {
112
+ color: "warning",
113
+ reverse: false,
114
+ class: {
115
+ separator: "group-data-[state=completed]:bg-warning"
116
+ }
117
+ },
118
+ {
119
+ color: "error",
120
+ reverse: false,
121
+ class: {
122
+ separator: "group-data-[state=completed]:bg-error"
123
+ }
124
+ },
125
+ {
126
+ color: "primary",
127
+ reverse: true,
128
+ class: {
129
+ separator: "group-data-[state=active]:bg-primary group-data-[state=completed]:bg-primary"
130
+ }
131
+ },
132
+ {
133
+ color: "secondary",
134
+ reverse: true,
135
+ class: {
136
+ separator:
137
+ "group-data-[state=active]:bg-secondary group-data-[state=completed]:bg-secondary"
138
+ }
139
+ },
140
+ {
141
+ color: "success",
142
+ reverse: true,
143
+ class: {
144
+ separator: "group-data-[state=active]:bg-success group-data-[state=completed]:bg-success"
145
+ }
146
+ },
147
+ {
148
+ color: "info",
149
+ reverse: true,
150
+ class: {
151
+ separator: "group-data-[state=active]:bg-info group-data-[state=completed]:bg-info"
152
+ }
153
+ },
154
+ {
155
+ color: "warning",
156
+ reverse: true,
157
+ class: {
158
+ separator: "group-data-[state=active]:bg-warning group-data-[state=completed]:bg-warning"
159
+ }
160
+ },
161
+ {
162
+ color: "error",
163
+ reverse: true,
164
+ class: {
165
+ separator: "group-data-[state=active]:bg-error group-data-[state=completed]:bg-error"
166
+ }
167
+ },
168
+ {
169
+ color: "neutral",
170
+ reverse: false,
171
+ class: {
172
+ separator: "group-data-[state=completed]:bg-inverted"
173
+ }
174
+ },
175
+ {
176
+ color: "neutral",
177
+ reverse: true,
178
+ class: {
179
+ separator:
180
+ "group-data-[state=active]:bg-inverted group-data-[state=completed]:bg-inverted"
181
+ }
182
+ },
183
+ {
184
+ orientation: "horizontal",
185
+ size: "3xs",
186
+ class: {
187
+ wrapper: "pe-4.5"
188
+ }
189
+ },
190
+ {
191
+ orientation: "horizontal",
192
+ size: "2xs",
193
+ class: {
194
+ wrapper: "pe-5"
195
+ }
196
+ },
197
+ {
198
+ orientation: "horizontal",
199
+ size: "xs",
200
+ class: {
201
+ wrapper: "pe-5.5"
202
+ }
203
+ },
204
+ {
205
+ orientation: "horizontal",
206
+ size: "sm",
207
+ class: {
208
+ wrapper: "pe-6"
209
+ }
210
+ },
211
+ {
212
+ orientation: "horizontal",
213
+ size: "md",
214
+ class: {
215
+ wrapper: "pe-6.5"
216
+ }
217
+ },
218
+ {
219
+ orientation: "horizontal",
220
+ size: "lg",
221
+ class: {
222
+ wrapper: "pe-7"
223
+ }
224
+ },
225
+ {
226
+ orientation: "horizontal",
227
+ size: "xl",
228
+ class: {
229
+ wrapper: "pe-7.5"
230
+ }
231
+ },
232
+ {
233
+ orientation: "horizontal",
234
+ size: "2xl",
235
+ class: {
236
+ wrapper: "pe-8"
237
+ }
238
+ },
239
+ {
240
+ orientation: "horizontal",
241
+ size: "3xl",
242
+ class: {
243
+ wrapper: "pe-8.5"
244
+ }
245
+ },
246
+ {
247
+ orientation: "vertical",
248
+ size: "3xs",
249
+ class: {
250
+ wrapper: "-mt-0.5 pb-4.5"
251
+ }
252
+ },
253
+ {
254
+ orientation: "vertical",
255
+ size: "2xs",
256
+ class: {
257
+ wrapper: "pb-5"
258
+ }
259
+ },
260
+ {
261
+ orientation: "vertical",
262
+ size: "xs",
263
+ class: {
264
+ wrapper: "mt-0.5 pb-5.5"
265
+ }
266
+ },
267
+ {
268
+ orientation: "vertical",
269
+ size: "sm",
270
+ class: {
271
+ wrapper: "mt-1 pb-6"
272
+ }
273
+ },
274
+ {
275
+ orientation: "vertical",
276
+ size: "md",
277
+ class: {
278
+ wrapper: "mt-1.5 pb-6.5"
279
+ }
280
+ },
281
+ {
282
+ orientation: "vertical",
283
+ size: "lg",
284
+ class: {
285
+ wrapper: "mt-2 pb-7"
286
+ }
287
+ },
288
+ {
289
+ orientation: "vertical",
290
+ size: "xl",
291
+ class: {
292
+ wrapper: "mt-2.5 pb-7.5"
293
+ }
294
+ },
295
+ {
296
+ orientation: "vertical",
297
+ size: "2xl",
298
+ class: {
299
+ wrapper: "mt-3 pb-8"
300
+ }
301
+ },
302
+ {
303
+ orientation: "vertical",
304
+ size: "3xl",
305
+ class: {
306
+ wrapper: "mt-3.5 pb-8.5"
307
+ }
308
+ }
309
+ ],
310
+ defaultVariants: {
311
+ size: "md",
312
+ color: "primary"
313
+ }
16
314
  })
17
315
 
18
316
  export default createTimelineTheme()
@@ -1,11 +1,61 @@
1
- export interface TimelineProps {
1
+ export interface TimelineItem {
2
+ date?: string
3
+ title?: string
4
+ description?: string
5
+ icon?: string
6
+ avatar?: any
7
+ value?: string | number
8
+ slot?: string
9
+ class?: any
10
+ ui?: {
11
+ item?: any
12
+ container?: any
13
+ indicator?: any
14
+ separator?: any
15
+ wrapper?: any
16
+ date?: any
17
+ title?: any
18
+ description?: any
19
+ [key: string]: any
20
+ }
21
+ [key: string]: any
22
+ }
23
+
24
+ export interface TimelineProps<T extends TimelineItem = TimelineItem> {
25
+ /**
26
+ * The element or component this component should render as.
27
+ */
28
+ as?: any
29
+ items?: T[]
30
+ size?: "3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl"
31
+ color?: "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral"
2
32
  /**
3
- * Slot-specific CSS class overrides.
33
+ * The orientation of the Timeline.
34
+ *
35
+ * @default "vertical"
4
36
  */
5
- ui?: any
37
+ orientation?: "horizontal" | "vertical"
6
38
  /**
7
- * Additional CSS classes to apply to the root element.
39
+ * The key used to get the value from the item.
40
+ *
41
+ * @default "value"
8
42
  */
43
+ valueKey?: string
44
+ defaultValue?: string | number
45
+ modelValue?: string | number
46
+ reverse?: boolean
47
+ ui?: {
48
+ root?: any
49
+ item?: any
50
+ container?: any
51
+ indicator?: any
52
+ separator?: any
53
+ wrapper?: any
54
+ date?: any
55
+ title?: any
56
+ description?: any
57
+ [key: string]: any
58
+ }
9
59
  class?: any
10
60
  [key: string]: unknown
11
61
  }
@@ -4,7 +4,7 @@ import { useUi } from "../../utils/useUi"
4
4
  import { resolveClasses } from "../../utils/resolveClasses"
5
5
  import type { VideoProps } from "./video"
6
6
  import videoTheme from "./video.theme"
7
- import Image from "astro/components/Image.astro"
7
+ import { Image } from "astro:assets"
8
8
  import RLAIcon from "../icon/RLAIcon.astro"
9
9
 
10
10
  const PLACEHOLDER_THUMBNAIL = "/assets/placeholder.webp";