@motor-hero/ui-kit 0.34.0 → 0.35.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motor-hero/ui-kit",
3
- "version": "0.34.0",
3
+ "version": "0.35.0",
4
4
  "description": "Componentes React reutilizáveis para o ecossistema MotorHero — shadcn/ui + Tailwind CSS v4",
5
5
  "author": "MotorHero",
6
6
  "license": "UNLICENSED",
@@ -1,8 +1,8 @@
1
1
  import * as PopoverPrimitive from "@radix-ui/react-popover"
2
2
  import { InputMask } from "@react-input/mask"
3
- import { format, isValid, parse } from "date-fns"
3
+ import { endOfYear, format, isValid, parse, subYears } from "date-fns"
4
4
  import { ptBR } from "date-fns/locale"
5
- import { CalendarIcon, ChevronLeft, ChevronRight, Clock } from "lucide-react"
5
+ import { CalendarIcon, ChevronDown, ChevronLeft, ChevronRight, Clock } from "lucide-react"
6
6
  import * as React from "react"
7
7
  import { DayPicker, type Matcher } from "react-day-picker"
8
8
  import { Drawer as DrawerPrimitive } from "vaul"
@@ -91,25 +91,31 @@ const triggerInputClass =
91
91
  const fieldInputClass =
92
92
  "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
93
93
 
94
+ /** Botão "ghost" (sem borda, hover:bg-accent) — mesma variante que o shadcn usa nas
95
+ * setas de navegação do calendário; inline porque o ui-kit não tem um Button próprio. */
96
+ const navButtonClass =
97
+ "inline-flex size-(--cell-size) shrink-0 select-none items-center justify-center rounded-md p-0 transition-colors hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:opacity-50"
98
+
94
99
  const calendarClassNames = {
95
- months: "flex flex-col",
96
- month: "flex flex-col gap-4",
97
- month_caption: "flex justify-center pt-1 relative items-center h-7",
98
- caption_label: "text-sm font-medium",
99
- nav: "flex items-center gap-1 absolute inset-x-0 top-0 justify-between px-1",
100
- button_previous:
101
- "inline-flex size-7 items-center justify-center rounded-md border border-input bg-transparent p-0 opacity-50 hover:opacity-100",
102
- button_next:
103
- "inline-flex size-7 items-center justify-center rounded-md border border-input bg-transparent p-0 opacity-50 hover:opacity-100",
104
- month_grid: "w-full border-collapse space-y-1",
100
+ months: "relative flex flex-col gap-4",
101
+ month: "flex w-full flex-col gap-4",
102
+ month_caption: "flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)",
103
+ caption_label:
104
+ "flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm font-medium select-none [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
105
+ dropdowns: "flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium",
106
+ dropdown_root:
107
+ "relative rounded-md border border-input shadow-xs has-focus:border-ring has-focus:ring-[3px] has-focus:ring-ring/50",
108
+ dropdown: "absolute inset-0 bg-popover opacity-0",
109
+ nav: "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
110
+ button_previous: navButtonClass,
111
+ button_next: navButtonClass,
112
+ month_grid: "w-full border-collapse",
105
113
  weekdays: "flex",
106
- weekday: "text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
107
- week: "flex w-full mt-2",
108
- day: "relative p-0 text-center text-sm focus-within:relative focus-within:z-20",
114
+ weekday: "flex-1 select-none rounded-md text-[0.8rem] font-normal text-muted-foreground",
115
+ week: "mt-2 flex w-full",
116
+ day: "relative aspect-square h-full w-full select-none p-0 text-center aria-selected:[&>button]:bg-primary aria-selected:[&>button]:text-primary-foreground aria-selected:[&>button]:hover:bg-primary",
109
117
  day_button:
110
- "inline-flex size-8 items-center justify-center rounded-md p-0 font-normal hover:bg-accent hover:text-accent-foreground aria-selected:opacity-100",
111
- selected:
112
- "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground rounded-md [&>button]:bg-primary [&>button]:text-primary-foreground",
118
+ "inline-flex aspect-square size-auto w-full min-w-(--cell-size) items-center justify-center rounded-md p-0 font-normal transition-colors hover:bg-accent hover:text-accent-foreground",
113
119
  today: "bg-accent text-accent-foreground rounded-md",
114
120
  outside: "text-muted-foreground opacity-50",
115
121
  disabled: "text-muted-foreground opacity-50 pointer-events-none",
@@ -117,14 +123,17 @@ const calendarClassNames = {
117
123
  }
118
124
 
119
125
  const calendarComponents = {
120
- Chevron: ({ orientation }: { orientation?: string }) =>
121
- orientation === "left" ? (
122
- <ChevronLeft className="size-4" />
123
- ) : (
124
- <ChevronRight className="size-4" />
125
- ),
126
+ Chevron: ({ orientation }: { orientation?: string }) => {
127
+ if (orientation === "left") return <ChevronLeft className="size-4" />
128
+ if (orientation === "right") return <ChevronRight className="size-4" />
129
+ return <ChevronDown className="size-4" />
130
+ },
126
131
  }
127
132
 
133
+ /** Nome curto do mês em pt-BR pro rótulo do dropdown (ex. "jul") — explícito porque
134
+ * `toLocaleString("default", ...)` seguiria o locale do navegador, não o do app. */
135
+ const formatMonthDropdown = (date: Date) => date.toLocaleString("pt-BR", { month: "short" })
136
+
128
137
  /**
129
138
  * DatePicker / DateTimePicker — calendário + campo digitável com máscara
130
139
  * (nunca o `<input type="date"/time">` nativo). Desktop: Popover não-modal;
@@ -159,6 +168,12 @@ function DatePickerImpl({
159
168
  const committedDate = withTime ? dateTimeToDate(value) : dateOnlyToDate(value)
160
169
  const displayValue = displayFor(value, withTime)
161
170
  const matchers = toMatchers(minDate, maxDate, disabledDates)
171
+ // Alcance do dropdown de ano: minDate/maxDate quando informados, senão o mesmo
172
+ // padrão da lib (100 anos atrás -> fim do ano atual) — calculado explícito nos dois
173
+ // lados porque passar só um dos dois via prop faz a lib truncar o outro (visto na
174
+ // prática: só uma opção de ano no lado sem prop), em vez de cair no padrão dela.
175
+ const startMonth = dateOnlyToDate(minDate) ?? subYears(new Date(), 100)
176
+ const endMonth = dateOnlyToDate(maxDate) ?? endOfYear(new Date())
162
177
 
163
178
  const setOpen = (next: boolean) => {
164
179
  setOpenRaw(next)
@@ -260,11 +275,15 @@ function DatePickerImpl({
260
275
  <DayPicker
261
276
  mode="single"
262
277
  locale={ptBR}
278
+ captionLayout="dropdown"
279
+ formatters={{ formatMonthDropdown }}
280
+ startMonth={startMonth}
281
+ endMonth={endMonth}
263
282
  selected={withTime ? pendingDate : committedDate}
264
283
  defaultMonth={(withTime ? pendingDate : committedDate) ?? new Date()}
265
284
  onSelect={(date) => date && handleCalendarSelect(date)}
266
285
  disabled={matchers}
267
- className={withTime ? "p-0" : "p-3"}
286
+ className={cn(withTime ? "p-0" : "p-3", "[--cell-size:2rem]")}
268
287
  classNames={calendarClassNames}
269
288
  components={calendarComponents}
270
289
  />