tembro 3.1.15 → 3.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/components/calendar/date-picker.d.ts +2 -1
  3. package/dist/components/display/statistic.d.ts +4 -2
  4. package/dist/components/modern/calendar-scheduler.d.ts +24 -1
  5. package/dist/components/modern/resizable-panel.d.ts +1 -1
  6. package/dist/components/patterns/form-builder.d.ts +9 -4
  7. package/dist/components/ui/button/index.d.ts +2 -1
  8. package/dist/showcase/package-meta.d.ts +2 -2
  9. package/dist/src/components/calendar/date-picker.cjs +1 -1
  10. package/dist/src/components/calendar/date-picker.js +24 -22
  11. package/dist/src/components/display/statistic.cjs +1 -1
  12. package/dist/src/components/display/statistic.js +27 -23
  13. package/dist/src/components/display/status-legend.cjs +1 -1
  14. package/dist/src/components/display/status-legend.js +4 -4
  15. package/dist/src/components/modern/calendar-scheduler.cjs +1 -1
  16. package/dist/src/components/modern/calendar-scheduler.js +125 -59
  17. package/dist/src/components/modern/resizable-panel.cjs +1 -1
  18. package/dist/src/components/modern/resizable-panel.js +4 -4
  19. package/dist/src/components/patterns/detail-layout.cjs +1 -1
  20. package/dist/src/components/patterns/detail-layout.js +1 -1
  21. package/dist/src/components/patterns/form-builder.cjs +1 -1
  22. package/dist/src/components/patterns/form-builder.js +6 -6
  23. package/dist/src/components/patterns/resource-detail-page.cjs +1 -1
  24. package/dist/src/components/patterns/resource-detail-page.js +1 -1
  25. package/dist/src/components/patterns/resource-page.cjs +1 -1
  26. package/dist/src/components/patterns/resource-page.js +1 -1
  27. package/dist/src/components/ui/button/index.cjs +1 -1
  28. package/dist/src/components/ui/button/index.js +10 -9
  29. package/dist/src/components/ui/card/index.cjs +1 -1
  30. package/dist/src/components/ui/card/index.js +1 -0
  31. package/dist/src/showcase/package-meta.cjs +1 -1
  32. package/dist/src/showcase/package-meta.js +1 -1
  33. package/dist/src/showcase/premium/calendar-scheduler/mock.cjs +8 -4
  34. package/dist/src/showcase/premium/calendar-scheduler/mock.js +4 -4
  35. package/dist/src/showcase/premium/calendar-scheduler/showcase.cjs +1 -1
  36. package/dist/src/showcase/premium/calendar-scheduler/showcase.js +29 -5
  37. package/dist/src/showcase/registry-specific.cjs +1 -1
  38. package/dist/src/showcase/registry-specific.js +281 -233
  39. package/dist/src/showcase/site-data.cjs +1 -1
  40. package/dist/src/showcase/site-data.js +1 -1
  41. package/dist/src/showcase/tembro-registry.json.cjs +1 -1
  42. package/dist/src/showcase/tembro-registry.json.js +1 -1
  43. package/package.json +1 -1
  44. package/packages/cli/dist/index.cjs +1 -1
  45. package/packages/cli/vendor/src/components/calendar/date-picker.tsx +11 -7
  46. package/packages/cli/vendor/src/components/display/statistic.tsx +20 -15
  47. package/packages/cli/vendor/src/components/display/status-legend.tsx +4 -4
  48. package/packages/cli/vendor/src/components/modern/calendar-scheduler.tsx +108 -48
  49. package/packages/cli/vendor/src/components/modern/resizable-panel.tsx +1 -1
  50. package/packages/cli/vendor/src/components/patterns/detail-layout.tsx +1 -1
  51. package/packages/cli/vendor/src/components/patterns/form-builder.tsx +30 -30
  52. package/packages/cli/vendor/src/components/patterns/resource-detail-page.tsx +1 -1
  53. package/packages/cli/vendor/src/components/patterns/resource-page.tsx +1 -1
  54. package/packages/cli/vendor/src/components/ui/button/index.tsx +3 -1
  55. package/packages/cli/vendor/src/components/ui/card/index.tsx +1 -0
  56. package/packages/cli/vendor/src/showcase/package-meta.ts +1 -1
  57. package/packages/cli/vendor/src/showcase/premium/calendar-scheduler/mock.ts +10 -7
  58. package/packages/cli/vendor/src/showcase/premium/calendar-scheduler/showcase.tsx +7 -5
  59. package/packages/cli/vendor/src/showcase/registry-specific.tsx +11 -1
  60. package/packages/cli/vendor/src/showcase/tembro-registry.json +1 -1
  61. package/packages/cli/vendor/templates/showcase/src/showcase/layout/HeroSection.tsx +2 -2
  62. package/packages/cli/vendor/templates/showcase/src/showcase/layout/WorkbenchSidebar.tsx +1 -1
  63. package/packages/cli/vendor/templates/styles/globals.css +60 -40
  64. package/registry.json +1 -1
@@ -64,7 +64,7 @@ function StatusLegend({
64
64
  <Card
65
65
  data-slot="status-legend"
66
66
  className={cn(
67
- "min-w-0 border-[color:var(--aui-surface-border)] bg-[color:color-mix(in_srgb,var(--aui-page-bg)_92%,white_8%)] shadow-sm dark:bg-[color:color-mix(in_srgb,var(--aui-page-bg)_96%,black_4%)]",
67
+ "min-w-0 border-[color:var(--aui-card-border,var(--border))] bg-card shadow-[var(--aui-card-shadow,var(--aui-shadow-xs))]",
68
68
  className
69
69
  )}
70
70
  {...props}
@@ -95,7 +95,7 @@ function StatusLegend({
95
95
  data-slot="status-legend-item"
96
96
  data-tone={item.tone ?? "default"}
97
97
  className={cn(
98
- "flex min-w-0 items-start justify-between gap-3 rounded-2xl border border-[color:var(--aui-surface-border)] bg-[color:var(--aui-control-bg)] p-3 shadow-[inset_0_1px_0_rgba(255,255,255,0.35)] transition hover:border-[color:color-mix(in_srgb,var(--aui-brand-strong)_26%,var(--aui-surface-border))]",
98
+ "flex min-w-0 items-start justify-between gap-3 rounded-lg border border-[color:var(--aui-card-border,var(--border))] bg-[color:var(--aui-control-surface,var(--background))] p-3 transition hover:border-[color:var(--aui-control-hover-border,var(--ring))]",
99
99
  compact && "p-2.5",
100
100
  itemClassName,
101
101
  item.className
@@ -105,7 +105,7 @@ function StatusLegend({
105
105
  {item.icon ? (
106
106
  <span className="mt-0.5 inline-flex size-8 shrink-0 items-center justify-center rounded-xl bg-[color:var(--aui-page-bg)] text-muted-foreground [&_svg]:size-4">{item.icon}</span>
107
107
  ) : (
108
- <span className={cn("mt-1.5 size-2.5 shrink-0 rounded-full shadow-[0_0_0_4px_color-mix(in_srgb,var(--aui-page-bg)_72%,transparent)]", dotClassName[item.tone ?? "default"])} />
108
+ <span className={cn("mt-1.5 size-2.5 shrink-0 rounded-full ring-4 ring-background", dotClassName[item.tone ?? "default"])} />
109
109
  )}
110
110
  <div className="min-w-0 space-y-0.5">
111
111
  <div className="truncate text-sm font-medium text-foreground">{item.label}</div>
@@ -113,7 +113,7 @@ function StatusLegend({
113
113
  </div>
114
114
  </div>
115
115
  {showCounts && item.count !== undefined && (
116
- <Badge variant="secondary" className="shrink-0 rounded-full border border-[color:var(--aui-surface-border)] bg-[color:var(--aui-page-bg)] px-2.5 py-1 shadow-sm">
116
+ <Badge variant="secondary" className="shrink-0 rounded-full border border-[color:var(--aui-card-border,var(--border))] bg-background px-2.5 py-1 shadow-sm">
117
117
  {item.count}
118
118
  </Badge>
119
119
  )}
@@ -9,15 +9,36 @@ export type CalendarSchedulerEvent = {
9
9
  time?: string
10
10
  tone?: "default" | "success" | "warning" | "danger"
11
11
  durationMinutes?: number
12
+ description?: React.ReactNode
13
+ meta?: React.ReactNode
14
+ badge?: React.ReactNode
15
+ icon?: React.ReactNode
16
+ disabled?: boolean
17
+ hidden?: boolean
18
+ className?: string
12
19
  }
13
20
 
14
21
  export type CalendarSchedulerProps = React.ComponentProps<"div"> & {
15
22
  events: CalendarSchedulerEvent[]
16
23
  days?: string[]
17
24
  view?: "day" | "week" | "month"
25
+ variant?: "board" | "agenda"
26
+ density?: "compact" | "default" | "comfortable"
27
+ title?: React.ReactNode
28
+ description?: React.ReactNode
29
+ actions?: React.ReactNode
18
30
  empty?: React.ReactNode
31
+ selectedEventId?: string
32
+ defaultSelectedEventId?: string
33
+ onSelectedEventChange?: (eventId: string, event: CalendarSchedulerEvent) => void
19
34
  onCreateEvent?: (date: string) => void
20
35
  onEventClick?: (event: CalendarSchedulerEvent) => void
36
+ renderEvent?: (event: CalendarSchedulerEvent, state: { selected: boolean }) => React.ReactNode
37
+ createLabel?: React.ReactNode
38
+ showCount?: boolean
39
+ dayClassName?: string
40
+ eventClassName?: string
41
+ formatDay?: (day: string) => React.ReactNode
21
42
  }
22
43
 
23
44
  const toneClassName = {
@@ -31,39 +52,52 @@ function CalendarScheduler({
31
52
  events,
32
53
  days,
33
54
  view = "week",
55
+ variant = "board",
56
+ density = "default",
57
+ title,
58
+ description,
59
+ actions,
34
60
  empty = "No events scheduled.",
61
+ selectedEventId,
62
+ defaultSelectedEventId,
63
+ onSelectedEventChange,
35
64
  onCreateEvent,
36
65
  onEventClick,
66
+ renderEvent,
67
+ createLabel = "Create event",
68
+ showCount = true,
69
+ dayClassName,
70
+ eventClassName,
71
+ formatDay,
37
72
  className,
38
73
  ...props
39
74
  }: CalendarSchedulerProps) {
40
- const visibleDays = days ?? Array.from(new Set(events.map((event) => event.date)))
75
+ const [internalSelectedEventId, setInternalSelectedEventId] = React.useState(defaultSelectedEventId)
76
+ const currentSelectedEventId = selectedEventId ?? internalSelectedEventId
77
+ const visibleEvents = events.filter((event) => !event.hidden)
78
+ const visibleDays = days ?? Array.from(new Set(visibleEvents.map((event) => event.date)))
79
+
80
+ const selectEvent = (event: CalendarSchedulerEvent) => {
81
+ if (event.disabled) return
82
+ if (selectedEventId === undefined) setInternalSelectedEventId(event.id)
83
+ onSelectedEventChange?.(event.id, event)
84
+ onEventClick?.(event)
85
+ }
41
86
 
42
87
  if (visibleDays.length === 0) {
43
88
  return (
44
89
  <div
45
90
  data-slot="calendar-scheduler"
46
91
  data-view={view}
47
- className={cn(
48
- "rounded-[var(--aui-card-radius,var(--radius-xl))] border border-[color:var(--aui-card-border,var(--border))] bg-card p-5 text-sm text-muted-foreground shadow-[var(--aui-card-shadow,var(--aui-control-shadow,none))]",
49
- className
50
- )}
92
+ data-variant={variant}
93
+ data-density={density}
94
+ className={cn("rounded-[var(--aui-card-radius,var(--radius-xl))] border border-[color:var(--aui-card-border,var(--border))] bg-card p-5 text-sm text-muted-foreground shadow-[var(--aui-card-shadow,var(--aui-control-shadow,none))]", className)}
51
95
  {...props}
52
96
  >
53
97
  <div className="flex min-h-32 items-center justify-center rounded-[var(--radius-lg)] border border-dashed border-[color:var(--aui-card-border,var(--border))] bg-muted/20 px-4 py-6 text-center">
54
98
  <div className="space-y-1.5">
55
- <p data-empty="true" className="text-sm font-medium text-foreground">
56
- {empty}
57
- </p>
58
- {onCreateEvent ? (
59
- <button
60
- type="button"
61
- className="rounded-md border border-dashed px-3 py-2 text-sm text-muted-foreground hover:bg-muted/50"
62
- onClick={() => onCreateEvent("today")}
63
- >
64
- Create event
65
- </button>
66
- ) : null}
99
+ <p data-empty="true" className="text-sm font-medium text-foreground">{empty}</p>
100
+ {onCreateEvent ? <button type="button" className="rounded-md border border-dashed px-3 py-2 text-sm text-muted-foreground hover:bg-muted/50" onClick={() => onCreateEvent("today")}>{createLabel}</button> : null}
67
101
  </div>
68
102
  </div>
69
103
  </div>
@@ -74,53 +108,79 @@ function CalendarScheduler({
74
108
  <div
75
109
  data-slot="calendar-scheduler"
76
110
  data-view={view}
77
- className={cn("grid gap-3", view === "month" && "md:grid-cols-2 xl:grid-cols-3", className)}
111
+ data-variant={variant}
112
+ data-density={density}
113
+ className={cn(
114
+ "grid",
115
+ density === "compact" ? "gap-2" : density === "comfortable" ? "gap-5" : "gap-3",
116
+ view === "month" && variant === "board" && "md:grid-cols-2 xl:grid-cols-3",
117
+ variant === "agenda" && "rounded-[var(--aui-card-radius,var(--radius-xl))] border border-[color:var(--aui-card-border,var(--border))] bg-card p-4 shadow-[var(--aui-card-shadow,var(--aui-control-shadow,none))]",
118
+ className
119
+ )}
78
120
  {...props}
79
121
  >
122
+ {(title || description || actions) ? (
123
+ <div data-slot="calendar-scheduler-header" className="flex flex-wrap items-start justify-between gap-3">
124
+ <div className="min-w-[min(100%,16rem)] flex-1 space-y-1">
125
+ {title ? <h2 className="text-base font-semibold text-foreground">{title}</h2> : null}
126
+ {description ? <p className="text-sm text-muted-foreground">{description}</p> : null}
127
+ </div>
128
+ {actions ? <div className="flex shrink-0 flex-wrap items-center gap-2">{actions}</div> : null}
129
+ </div>
130
+ ) : null}
131
+
80
132
  {visibleDays.map((day) => {
81
- const dayEvents = events.filter((event) => event.date === day)
133
+ const dayEvents = visibleEvents.filter((event) => event.date === day)
82
134
 
83
135
  return (
84
- <section
85
- key={day}
86
- className="rounded-[var(--aui-card-radius,var(--radius-xl))] border border-[color:var(--aui-card-border,var(--border))] bg-card p-4 shadow-[var(--aui-card-shadow,var(--aui-control-shadow,none))]"
87
- >
136
+ <section key={day} data-slot="calendar-scheduler-day" className={cn(variant === "board" && "rounded-[var(--aui-card-radius,var(--radius-xl))] border border-[color:var(--aui-card-border,var(--border))] bg-card p-4 shadow-[var(--aui-card-shadow,var(--aui-control-shadow,none))]", dayClassName)}>
88
137
  <div className="mb-3 flex items-center justify-between gap-3">
89
- <div className="text-sm font-semibold text-foreground">{day}</div>
90
- <div className="text-xs text-muted-foreground">{dayEvents.length} items</div>
138
+ <div className="text-sm font-semibold text-foreground">{formatDay?.(day) ?? day}</div>
139
+ {showCount ? <div className="text-xs text-muted-foreground">{dayEvents.length} items</div> : null}
91
140
  </div>
92
- <div className="grid gap-2">
141
+ <div className={cn("grid", density === "compact" ? "gap-1.5" : "gap-2")}>
93
142
  {dayEvents.length === 0 ? (
94
- <div className="rounded-[var(--radius-md)] border border-dashed border-[color:var(--aui-card-border,var(--border))] px-3 py-4 text-sm text-muted-foreground">
95
- {empty}
96
- </div>
97
- ) : (
98
- dayEvents.map((event) => (
143
+ <div className="rounded-[var(--radius-md)] border border-dashed border-[color:var(--aui-card-border,var(--border))] px-3 py-4 text-sm text-muted-foreground">{empty}</div>
144
+ ) : dayEvents.map((event) => {
145
+ const selected = currentSelectedEventId === event.id
146
+ return (
99
147
  <button
100
148
  key={event.id}
101
149
  type="button"
150
+ disabled={event.disabled}
151
+ data-slot="calendar-scheduler-event"
152
+ data-selected={selected || undefined}
102
153
  className={cn(
103
- "rounded-[var(--radius-md)] border px-3 py-2.5 text-left text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40",
104
- toneClassName[event.tone ?? "default"],
154
+ "border text-left text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40 disabled:cursor-not-allowed disabled:opacity-50",
155
+ variant === "board" ? "rounded-[var(--radius-md)] px-3 py-2.5 shadow-sm" : "grid w-full grid-cols-[auto_minmax(0,1fr)_auto] items-start gap-3 rounded-[var(--radius-md)] bg-background px-3 py-3 hover:bg-muted/35 data-[selected=true]:border-primary/40 data-[selected=true]:bg-primary/5",
156
+ variant === "board" && toneClassName[event.tone ?? "default"], eventClassName, event.className
105
157
  )}
106
- onClick={() => onEventClick?.(event)}
158
+ onClick={() => selectEvent(event)}
107
159
  >
108
- <div className="font-medium">{event.title}</div>
109
- {event.time && <div className="mt-1 text-xs opacity-70">{event.time}</div>}
110
- {event.durationMinutes && <div className="mt-1 text-xs opacity-70">{event.durationMinutes} min</div>}
160
+ {renderEvent ? renderEvent(event, { selected }) : variant === "agenda" ? (
161
+ <>
162
+ <span className="inline-flex min-w-14 items-center gap-1.5 font-medium">{event.icon}{event.time ?? "—"}</span>
163
+ <span className="min-w-0">
164
+ <span className="block truncate font-medium text-foreground">{event.title}</span>
165
+ {event.description ? <span className="mt-0.5 block text-xs text-muted-foreground">{event.description}</span> : null}
166
+ {event.meta ? <span className="mt-1 block text-xs text-muted-foreground">{event.meta}</span> : null}
167
+ </span>
168
+ {event.badge ? <span className="shrink-0">{event.badge}</span> : event.durationMinutes ? <span className="shrink-0 text-xs opacity-70">{event.durationMinutes} min</span> : null}
169
+ </>
170
+ ) : (
171
+ <>
172
+ <div className="font-medium">{event.title}</div>
173
+ {event.description ? <div className="mt-1 text-xs opacity-80">{event.description}</div> : null}
174
+ {event.time ? <div className="mt-1 text-xs opacity-70">{event.time}</div> : null}
175
+ {event.durationMinutes ? <div className="mt-1 text-xs opacity-70">{event.durationMinutes} min</div> : null}
176
+ {event.meta ? <div className="mt-1 text-xs opacity-70">{event.meta}</div> : null}
177
+ </>
178
+ )}
111
179
  </button>
112
- ))
113
- )}
180
+ )
181
+ })}
114
182
  </div>
115
- {onCreateEvent && (
116
- <button
117
- type="button"
118
- className="mt-3 w-full rounded-md border border-dashed px-3 py-2 text-sm text-muted-foreground hover:bg-muted/50"
119
- onClick={() => onCreateEvent(day)}
120
- >
121
- Create event
122
- </button>
123
- )}
183
+ {onCreateEvent ? <button type="button" className="mt-3 w-full rounded-md border border-dashed px-3 py-2 text-sm text-muted-foreground hover:bg-muted/50" onClick={() => onCreateEvent(day)}>{createLabel}</button> : null}
124
184
  </section>
125
185
  )
126
186
  })}
@@ -132,7 +132,7 @@ function ResizablePanelGroup({ direction = "horizontal", className, children, ..
132
132
  )
133
133
  }
134
134
 
135
- function ResizablePanel({ className, style, defaultSize, ...props }: ResizablePanelInternalProps) {
135
+ function ResizablePanel({ className, style, defaultSize, minSize: _minSize, ...props }: ResizablePanelInternalProps) {
136
136
  const context = React.useContext(ResizablePanelContext)
137
137
  const panelIndex = Number(props["data-panel-index"] ?? 0)
138
138
  const basis = context ? context.getSize(panelIndex) : normalizeSizeValue(defaultSize) ?? 100
@@ -55,7 +55,7 @@ function DetailLayout({
55
55
  {hasContent && (
56
56
  <div
57
57
  data-slot="detail-layout-content"
58
- className={cn("grid min-w-0 gap-4 xl:grid-cols-[minmax(0,1fr)_20rem]", contentClassName)}
58
+ className={cn("grid min-w-0 gap-4", aside && "xl:grid-cols-[minmax(0,1fr)_20rem]", contentClassName)}
59
59
  >
60
60
  <main data-slot="detail-layout-main" className="min-w-0">
61
61
  {children}
@@ -22,8 +22,10 @@ import {
22
22
  } from "@/components/form/form-date-range-input"
23
23
  import { cn } from "@/lib/utils"
24
24
 
25
- export type FormBuilderLayout = "grid" | "stack"
26
- export type FormBuilderDensity = "compact" | "default" | "comfortable"
25
+ export type FormBuilderLayout = "grid" | "stack"
26
+ export type FormBuilderDensity = "compact" | "default" | "comfortable"
27
+
28
+ type DistributiveOmit<T, TKey extends PropertyKey> = T extends unknown ? Omit<T, TKey> : never
27
29
 
28
30
  export type FormBuilderFieldRenderContext<TFieldValues extends FieldValues> = {
29
31
  control: Control<TFieldValues>
@@ -48,7 +50,7 @@ export type FormBuilderInputField<
48
50
  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
49
51
  > = BaseFormBuilderField & {
50
52
  type: "input"
51
- props: Omit<FormInputProps<TFieldValues, TName>, "control">
53
+ props: DistributiveOmit<FormInputProps<TFieldValues, TName>, "control">
52
54
  }
53
55
 
54
56
  export type FormBuilderTextareaField<
@@ -64,7 +66,7 @@ export type FormBuilderSelectField<
64
66
  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
65
67
  > = BaseFormBuilderField & {
66
68
  type: "select"
67
- props: Omit<FormSelectProps<TFieldValues, TName>, "control">
69
+ props: DistributiveOmit<FormSelectProps<TFieldValues, TName>, "control">
68
70
  }
69
71
 
70
72
  export type FormBuilderAsyncSelectField<
@@ -116,17 +118,14 @@ export type FormBuilderDateRangeField<
116
118
  props: Omit<FormDateRangeInputProps<TFieldValues, TFromName, TToName>, "control">
117
119
  }
118
120
 
119
- export type FormBuilderField<TFieldValues extends FieldValues> =
120
- | FormBuilderCustomField<TFieldValues>
121
- | FormBuilderInputField<TFieldValues>
122
- | FormBuilderTextareaField<TFieldValues>
123
- | FormBuilderSelectField<TFieldValues>
124
- | FormBuilderAsyncSelectField<TFieldValues>
125
- | FormBuilderSwitchField<TFieldValues>
126
- | FormBuilderNumberField<TFieldValues>
127
- | FormBuilderPhoneField<TFieldValues>
128
- | FormBuilderDateField<TFieldValues>
129
- | FormBuilderDateRangeField<TFieldValues>
121
+ type FormBuilderStoredField = BaseFormBuilderField & {
122
+ type: "input" | "textarea" | "select" | "async-select" | "switch" | "number" | "phone" | "date" | "date-range"
123
+ props: object
124
+ }
125
+
126
+ export type FormBuilderField<TFieldValues extends FieldValues> =
127
+ | FormBuilderCustomField<TFieldValues>
128
+ | FormBuilderStoredField
130
129
 
131
130
  export type FormBuilderSection<TFieldValues extends FieldValues> = {
132
131
  id: string
@@ -280,8 +279,11 @@ function renderFormBuilderField<TFieldValues extends FieldValues>(
280
279
  field: FormBuilderField<TFieldValues>,
281
280
  context: FormBuilderFieldRenderContext<TFieldValues>
282
281
  ) {
283
- const FormInputComponent = FormInput as unknown as React.ComponentType<Record<string, unknown>>
284
- const FormSelectComponent = FormSelect as unknown as React.ComponentType<Record<string, unknown>>
282
+ const FormInputComponent = FormInput as unknown as React.ComponentType<Record<string, unknown>>
283
+ const FormSelectComponent = FormSelect as unknown as React.ComponentType<Record<string, unknown>>
284
+ const FormTextareaComponent = FormTextarea as unknown as React.ComponentType<Record<string, unknown>>
285
+ const FormSwitchComponent = FormSwitch as unknown as React.ComponentType<Record<string, unknown>>
286
+ const FormDateRangeInputComponent = FormDateRangeInput as unknown as React.ComponentType<Record<string, unknown>>
285
287
 
286
288
  switch (field.type) {
287
289
  case "custom":
@@ -293,8 +295,8 @@ function renderFormBuilderField<TFieldValues extends FieldValues>(
293
295
  readOnly: context.readOnly,
294
296
  ...(field.props as Omit<FormInputProps<TFieldValues, FieldPath<TFieldValues>>, "control">),
295
297
  })
296
- case "textarea":
297
- return <FormTextarea control={context.control} disabled={context.disabled} readOnly={context.readOnly} {...field.props} />
298
+ case "textarea":
299
+ return React.createElement(FormTextareaComponent, { control: context.control, disabled: context.disabled, readOnly: context.readOnly, ...field.props })
298
300
  case "select":
299
301
  return React.createElement(FormSelectComponent, {
300
302
  control: context.control,
@@ -308,8 +310,8 @@ function renderFormBuilderField<TFieldValues extends FieldValues>(
308
310
  kind: "async",
309
311
  ...field.props,
310
312
  })
311
- case "switch":
312
- return <FormSwitch control={context.control} disabled={context.disabled} {...field.props} />
313
+ case "switch":
314
+ return React.createElement(FormSwitchComponent, { control: context.control, disabled: context.disabled, ...field.props })
313
315
  case "number":
314
316
  return React.createElement(FormInputComponent, {
315
317
  control: context.control,
@@ -334,15 +336,13 @@ function renderFormBuilderField<TFieldValues extends FieldValues>(
334
336
  kind: "date",
335
337
  ...field.props,
336
338
  })
337
- case "date-range":
338
- return (
339
- <FormDateRangeInput
340
- control={context.control}
341
- fromInputProps={{ disabled: context.disabled, readOnly: context.readOnly }}
342
- toInputProps={{ disabled: context.disabled, readOnly: context.readOnly }}
343
- {...field.props}
344
- />
345
- )
339
+ case "date-range":
340
+ return React.createElement(FormDateRangeInputComponent, {
341
+ control: context.control,
342
+ fromInputProps: { disabled: context.disabled, readOnly: context.readOnly },
343
+ toInputProps: { disabled: context.disabled, readOnly: context.readOnly },
344
+ ...field.props,
345
+ })
346
346
  default:
347
347
  return null
348
348
  }
@@ -120,7 +120,7 @@ function ResourceDetailPage({
120
120
  {(hasMainContent || aside) && (
121
121
  <div
122
122
  data-slot="resource-detail-page-content"
123
- className={cn("grid min-w-0 gap-4 xl:grid-cols-[minmax(0,1fr)_20rem]", contentClassName)}
123
+ className={cn("grid min-w-0 gap-4", aside && "xl:grid-cols-[minmax(0,1fr)_20rem]", contentClassName)}
124
124
  >
125
125
  <div data-slot="resource-detail-page-main" className="grid min-w-0 gap-4">
126
126
  {children}
@@ -136,7 +136,7 @@ function ResourcePage<TData, TValue = unknown>({
136
136
  {(hasMainContent || aside) && (
137
137
  <div
138
138
  data-slot="resource-page-content"
139
- className={cn("grid min-w-0 gap-4 xl:grid-cols-[minmax(0,1fr)_20rem]", contentClassName)}
139
+ className={cn("grid min-w-0 gap-4", aside && "xl:grid-cols-[minmax(0,1fr)_20rem]", contentClassName)}
140
140
  >
141
141
  <div data-slot="resource-page-main" className="grid min-w-0 gap-4">
142
142
  {children}
@@ -46,6 +46,7 @@ export type ButtonProps = ButtonPrimitive.Props &
46
46
  iconOnly?: boolean
47
47
  fullWidth?: boolean
48
48
  pressed?: boolean
49
+ labelClassName?: string
49
50
  }
50
51
 
51
52
  function Button({
@@ -60,6 +61,7 @@ function Button({
60
61
  iconOnly = false,
61
62
  fullWidth = false,
62
63
  pressed = false,
64
+ labelClassName,
63
65
  children,
64
66
  "aria-label": ariaLabel,
65
67
  ...props
@@ -96,7 +98,7 @@ function Button({
96
98
  {leftIcon}
97
99
  </span>
98
100
  ) : null}
99
- {children ? <span data-slot="button-label">{loading ? loadingLabel : children}</span> : null}
101
+ {children ? <span data-slot="button-label" className={cn("inline-flex min-w-0 items-center gap-[inherit]", labelClassName)}>{loading ? loadingLabel : children}</span> : null}
100
102
  {!loading && rightIcon ? (
101
103
  <span data-icon="inline-end" data-slot="button-icon" className="inline-flex shrink-0 items-center justify-center">
102
104
  {rightIcon}
@@ -181,6 +181,7 @@ function Card({
181
181
  return (
182
182
  <div
183
183
  data-slot="card"
184
+ data-card=""
184
185
  data-surface={surfaceMode || undefined}
185
186
  data-size={size ?? "default"}
186
187
  data-variant={variant ?? "default"}
@@ -1,4 +1,4 @@
1
- export const PACKAGE_LATEST_VERSION = "3.1.15"
1
+ export const PACKAGE_LATEST_VERSION = "3.1.17"
2
2
  export const PACKAGE_LATEST_RELEASE_DATE = "July 15, 2026"
3
3
  export const PACKAGE_DEPENDENCY_RANGE = PACKAGE_LATEST_VERSION
4
4
 
@@ -6,23 +6,26 @@ export const calendarSchedulerMock: ComponentDemoMock = {
6
6
  export function Example() {
7
7
  return (
8
8
  <CalendarScheduler
9
- days={["Mon, Jul 6", "Tue, Jul 7"]}
9
+ variant="agenda"
10
+ density="compact"
11
+ title="Delivery schedule"
12
+ defaultSelectedEventId="1"
13
+ days={["Mon, Jul 6"]}
10
14
  events={[
11
- { id: "1", title: "Design review", date: "Mon, Jul 6", time: "10:00", tone: "warning" },
12
- { id: "2", title: "Release freeze", date: "Tue, Jul 7", time: "14:00", tone: "danger" },
15
+ { id: "1", title: "Design review", description: "API review", date: "Mon, Jul 6", time: "10:00" },
16
+ { id: "2", title: "Release freeze", description: "Production gate", date: "Mon, Jul 6", time: "14:00" },
13
17
  ]}
14
18
  />
15
19
  )
16
20
  }`,
17
21
  cliCommand: "npx tembro add calendar-scheduler",
18
22
  highlights: [
19
- "Compact scheduler timeline by day",
20
- "Tone variants for different event priorities",
21
- "Works with custom day labels",
23
+ "Board and agenda variants with compact or comfortable density",
24
+ "Rich metadata, badges, hidden and disabled event states",
25
+ "Controlled or uncontrolled selection and custom event rendering",
22
26
  ],
23
27
  scenarios: [
24
28
  { title: "Ops dashboard", description: "Show day tasks by lane without heavy calendar widgets." },
25
29
  { title: "Sprint board", description: "Keep event density low and readable." },
26
30
  ],
27
31
  }
28
-
@@ -7,13 +7,14 @@ const defaultDays = ["Mon, Jul 6", "Tue, Jul 7", "Wed, Jul 8"]
7
7
 
8
8
  export function CalendarSchedulerShowcase() {
9
9
  const [showWarning, setShowWarning] = React.useState(true)
10
+ const [variant, setVariant] = React.useState<"board" | "agenda">("agenda")
10
11
 
11
12
  const events = React.useMemo(
12
13
  () => ([
13
- { id: "1", title: "Design review", date: "Mon, Jul 6", time: "10:00", tone: "warning" },
14
- { id: "2", title: "Release freeze", date: "Tue, Jul 7", time: "14:00", tone: "danger" },
15
- { id: "3", title: "Daily standup", date: "Wed, Jul 8", time: "09:30", tone: "default" },
16
- ...(showWarning ? [{ id: "4", title: "Priority follow-up", date: "Mon, Jul 6", time: "16:00", tone: "warning" }] : []),
14
+ { id: "1", title: "Design review", description: "Component API and release readiness", meta: "Product room", badge: <span className="rounded bg-amber-100 px-2 py-0.5 text-xs text-amber-800">Review</span>, date: "Mon, Jul 6", time: "10:00", tone: "warning" },
15
+ { id: "2", title: "Release freeze", description: "Production deployment checkpoint", meta: "Platform team", badge: <span className="rounded bg-red-100 px-2 py-0.5 text-xs text-red-800">Required</span>, date: "Tue, Jul 7", time: "14:00", tone: "danger" },
16
+ { id: "3", title: "Daily standup", description: "Owners, blockers and delivery status", date: "Wed, Jul 8", time: "09:30", tone: "default" },
17
+ ...(showWarning ? [{ id: "4", title: "Priority follow-up", description: "Customer escalation review", date: "Mon, Jul 6", time: "16:00", tone: "warning" as const }] : []),
17
18
  ] as CalendarSchedulerEvent[]),
18
19
  [showWarning]
19
20
  )
@@ -21,6 +22,7 @@ export function CalendarSchedulerShowcase() {
21
22
  return (
22
23
  <div className="space-y-3">
23
24
  <div className="flex gap-2">
25
+ <button type="button" className="inline-flex items-center rounded-md border px-3 py-1.5 text-sm" onClick={() => setVariant((value) => value === "agenda" ? "board" : "agenda")}>Use {variant === "agenda" ? "board" : "agenda"}</button>
24
26
  <button
25
27
  type="button"
26
28
  className="inline-flex items-center rounded-md border px-3 py-1.5 text-sm"
@@ -29,7 +31,7 @@ export function CalendarSchedulerShowcase() {
29
31
  {showWarning ? "Hide warning" : "Show warning"}
30
32
  </button>
31
33
  </div>
32
- <CalendarScheduler days={defaultDays} events={events} />
34
+ <CalendarScheduler title="Delivery schedule" description="Production agenda with rich event metadata and selection." variant={variant} density="compact" days={defaultDays} events={events} defaultSelectedEventId="1" />
33
35
  </div>
34
36
  )
35
37
  }
@@ -48,7 +48,7 @@ import {
48
48
  Avatar,
49
49
  } from "@/index"
50
50
  import { PreviewFileDropzone as FileDropzone, PreviewStatCard as StatCard } from "@/showcase/preview-compositions"
51
- import { FormBuilder, customField, formSection } from "@/components/patterns/form-builder"
51
+ import { FormBuilder, customField, formSection, inputField, selectField, switchField, textareaField } from "@/components/patterns/form-builder"
52
52
  import { ResourceDetailPage } from "@/components/patterns/resource-detail-page"
53
53
  import { ResourcePage, ResourcePageSection } from "@/components/patterns/resource-page"
54
54
  import { useForm } from "react-hook-form"
@@ -750,6 +750,7 @@ function FormBuilderPreview() {
750
750
  email: string
751
751
  notes: string
752
752
  marketing: boolean
753
+ role: string
753
754
  }
754
755
 
755
756
  const form = useForm<FormBuilderDemoValues>({
@@ -758,6 +759,7 @@ function FormBuilderPreview() {
758
759
  email: "azamat@example.com",
759
760
  notes: "Release gate is ready.",
760
761
  marketing: true,
762
+ role: "admin",
761
763
  },
762
764
  })
763
765
 
@@ -779,6 +781,14 @@ function FormBuilderPreview() {
779
781
  </div>
780
782
  ),
781
783
  }),
784
+ inputField<FormBuilderDemoValues, "name">({ id: "name", props: { name: "name", label: "Name", required: true } }),
785
+ inputField<FormBuilderDemoValues, "email">({ id: "email", props: { name: "email", label: "Email", type: "email" } }),
786
+ selectField<FormBuilderDemoValues, "role">({ id: "role", props: { name: "role", label: "Role", options: [
787
+ { value: "admin", label: "Administrator" },
788
+ { value: "member", label: "Member" },
789
+ ] } }),
790
+ switchField<FormBuilderDemoValues, "marketing">({ id: "marketing", props: { name: "marketing", label: "Product updates" } }),
791
+ textareaField<FormBuilderDemoValues, "notes">({ id: "notes", colSpan: "full", props: { name: "notes", label: "Release notes" } }),
782
792
  ],
783
793
  }),
784
794
  ]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://tembro.dev/registry.schema.json",
3
3
  "name": "tembro",
4
- "version": "3.1.15",
4
+ "version": "3.1.17",
5
5
  "description": "Reusable React + TypeScript UI kit registry",
6
6
  "groups": {
7
7
  "ui": [
@@ -25,7 +25,7 @@ export function HeroSection() {
25
25
  <div className="flex flex-wrap gap-2">
26
26
  <Button leftIcon={<PlusIcon className="size-4" />}>Create</Button>
27
27
  <Button variant="outline" leftIcon={<SettingsIcon className="size-4" />}>Settings</Button>
28
- <CopyButton value="npx tembro@3.1.15 list --json">Copy list command</CopyButton>
28
+ <CopyButton value="npx tembro@3.1.17 list --json">Copy list command</CopyButton>
29
29
  </div>
30
30
  </div>
31
31
 
@@ -33,7 +33,7 @@ export function HeroSection() {
33
33
  <StatisticCard label="Registry components" value={moduleCount} change="visible below" trend="up" description="tembro add <name>" />
34
34
  <StatisticCard label="Local source files" value="152" change="with hooks/lib" trend="up" description="installed by CLI" />
35
35
  <StatisticCard label="Categories" value={registryGroups.length} change="all shown" trend="up" description="actions to wizard" />
36
- <StatisticCard label="Build" value="Pass" change="doctor pass" trend="up" description="tembro@3.1.15" />
36
+ <StatisticCard label="Build" value="Pass" change="doctor pass" trend="up" description="tembro@3.1.17" />
37
37
  </StatisticGrid>
38
38
  </div>
39
39
  </section>
@@ -72,7 +72,7 @@ export function WorkbenchSidebar({ selectedKey, onSelect }: WorkbenchSidebarProp
72
72
  }),
73
73
  ]}
74
74
  footerAccount={{
75
- label: "tembro@3.1.15",
75
+ label: "tembro@3.1.17",
76
76
  description: `${moduleCount} registry components`,
77
77
  }}
78
78
  activeIndicator="bar"