@motor-hero/ui-kit 0.47.0 → 0.48.1

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.47.0",
3
+ "version": "0.48.1",
4
4
  "description": "Componentes React reutilizáveis para o ecossistema MotorHero — shadcn/ui + Tailwind CSS v4",
5
5
  "author": "MotorHero",
6
6
  "license": "UNLICENSED",
@@ -49,6 +49,7 @@
49
49
  "lint": "tsc --noEmit",
50
50
  "docs:dev": "vite --config docs/vite.config.ts",
51
51
  "docs:build": "vite build --config docs/vite.config.ts",
52
+ "release": "scripts/release.sh",
52
53
  "prepublishOnly": "npm run build",
53
54
  "prepare": "husky"
54
55
  },
@@ -124,7 +124,7 @@ export function AppShell({
124
124
  <button
125
125
  type="button"
126
126
  onClick={() => setMobileOpen(true)}
127
- className="inline-flex h-9 w-9 items-center justify-center rounded-md border border-input bg-background shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground md:hidden"
127
+ className="inline-flex h-9 w-9 max-sm:h-10 max-sm:w-10 items-center justify-center rounded-md border border-input bg-background shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground md:hidden"
128
128
  aria-label="Abrir menu"
129
129
  >
130
130
  <Menu className="h-4 w-4" />
@@ -116,7 +116,10 @@ export function Combobox({
116
116
 
117
117
  const handleOpenChange = (next: boolean) => {
118
118
  setOpen(next);
119
- if (!next) setSearch("");
119
+ if (!next) {
120
+ setSearch("");
121
+ onSearchChange?.("");
122
+ }
120
123
  };
121
124
 
122
125
  const handleSelect = (next: string) => {
@@ -269,7 +269,7 @@ function DatePickerImpl({
269
269
  aria-haspopup="dialog"
270
270
  aria-expanded={open}
271
271
  onClick={() => setOpen(!open)}
272
- className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground transition-colors hover:text-foreground disabled:pointer-events-none disabled:opacity-50"
272
+ className="absolute right-1 top-1/2 inline-flex size-8 -translate-y-1/2 items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground disabled:pointer-events-none disabled:opacity-50"
273
273
  >
274
274
  {withTime ? <Clock className="h-4 w-4" /> : <CalendarIcon className="h-4 w-4" />}
275
275
  </button>
@@ -10,7 +10,7 @@ export function ModeToggle() {
10
10
  <DropdownMenu.Trigger asChild>
11
11
  <button
12
12
  type="button"
13
- className="relative inline-flex h-9 w-9 items-center justify-center rounded-md border border-input bg-background text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
13
+ className="relative inline-flex h-9 w-9 max-sm:h-10 max-sm:w-10 items-center justify-center rounded-md border border-input bg-background text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
14
14
  aria-label="Alternar tema"
15
15
  >
16
16
  <Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
@@ -73,6 +73,10 @@ export function MultiCombobox({
73
73
  const [open, setOpen] = React.useState(false)
74
74
  const isDesktop = useIsDesktop()
75
75
  const isServer = onSearchChange !== undefined
76
+ const handleOpenChange = (next: boolean) => {
77
+ setOpen(next)
78
+ if (!next) onSearchChange?.("")
79
+ }
76
80
  // Em modo servidor `options` é só a página carregada no momento, então um
77
81
  // rótulo visto uma vez precisa sobreviver à troca de página: sem esse cache,
78
82
  // buscar (ou paginar) fazia o chip de um valor ainda selecionado sumir do
@@ -223,7 +227,7 @@ export function MultiCombobox({
223
227
 
224
228
  if (isDesktop) {
225
229
  return (
226
- <PopoverPrimitive.Root open={open} onOpenChange={setOpen}>
230
+ <PopoverPrimitive.Root open={open} onOpenChange={handleOpenChange}>
227
231
  <PopoverPrimitive.Trigger asChild disabled={disabled}>
228
232
  {trigger}
229
233
  </PopoverPrimitive.Trigger>
@@ -242,7 +246,7 @@ export function MultiCombobox({
242
246
  }
243
247
 
244
248
  return (
245
- <DrawerPrimitive.Root open={open} onOpenChange={setOpen}>
249
+ <DrawerPrimitive.Root open={open} onOpenChange={handleOpenChange}>
246
250
  <DrawerPrimitive.Trigger asChild disabled={disabled}>
247
251
  {trigger}
248
252
  </DrawerPrimitive.Trigger>
@@ -13,8 +13,9 @@ export interface PaginationProps {
13
13
  className?: string
14
14
  }
15
15
 
16
+ // `max-sm:h-10`: alvo de toque de 40px no celular; o desktop segue em 32px.
16
17
  const navButtonClass =
17
- "inline-flex h-8 items-center justify-center rounded-md border border-input bg-background px-3 text-xs font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground cursor-pointer disabled:pointer-events-none disabled:opacity-50"
18
+ "inline-flex h-8 max-sm:h-10 items-center justify-center rounded-md border border-input bg-background px-3 text-xs font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground cursor-pointer disabled:pointer-events-none disabled:opacity-50"
18
19
 
19
20
  export function Pagination({
20
21
  page,
@@ -44,7 +45,7 @@ export function Pagination({
44
45
  id="pagination-page-size"
45
46
  value={pageSize}
46
47
  onChange={(e) => onPageSizeChange?.(Number(e.target.value))}
47
- className="h-8 rounded-md border border-input bg-background px-2 text-xs font-medium shadow-sm transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring cursor-pointer"
48
+ className="h-8 max-sm:h-10 rounded-md border border-input bg-background px-2 text-xs font-medium shadow-sm transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring cursor-pointer"
48
49
  >
49
50
  {(pageSizeOptions ?? []).map((size) => (
50
51
  <option key={size} value={size}>
@@ -25,7 +25,7 @@ export const PasswordInput = React.forwardRef<
25
25
  onClick={() => setVisible((prev) => !prev)}
26
26
  aria-label={visible ? "Ocultar senha" : "Mostrar senha"}
27
27
  tabIndex={-1}
28
- className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground transition-colors hover:text-foreground"
28
+ className="absolute right-1 top-1/2 inline-flex size-8 -translate-y-1/2 items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground"
29
29
  >
30
30
  {visible ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
31
31
  </button>
@@ -20,6 +20,24 @@ interface ResponsiveDataViewProps {
20
20
  /** CTA do estado vazio — normalmente o mesmo botão "Adicionar X" da página. */
21
21
  emptyAction?: ReactNode
22
22
  pagination?: ReactNode
23
+ /**
24
+ * A partir de qual largura a tabela substitui os cards. Padrão `md`. Use
25
+ * `lg`/`xl` quando a tabela tem colunas demais para caber ao lado da barra
26
+ * lateral num tablet — os cards leem melhor do que uma tabela rolando de lado.
27
+ */
28
+ breakpoint?: ResponsiveDataViewBreakpoint
29
+ }
30
+
31
+ export type ResponsiveDataViewBreakpoint = "md" | "lg" | "xl"
32
+
33
+ // Classes literais: o Tailwind do consumidor só gera o que encontra escrito.
34
+ const BREAKPOINT_CLASSES: Record<
35
+ ResponsiveDataViewBreakpoint,
36
+ { table: string; cards: string }
37
+ > = {
38
+ md: { table: "md:block", cards: "md:hidden" },
39
+ lg: { table: "lg:block", cards: "lg:hidden" },
40
+ xl: { table: "xl:block", cards: "xl:hidden" },
23
41
  }
24
42
 
25
43
  export function ResponsiveDataView({
@@ -33,6 +51,7 @@ export function ResponsiveDataView({
33
51
  emptyDescription,
34
52
  emptyAction,
35
53
  pagination,
54
+ breakpoint = "md",
36
55
  }: ResponsiveDataViewProps) {
37
56
  // Lista vazia mostra só o EmptyState. Antes renderizava a caixa com o
38
57
  // cabeçalho da tabela vazio, o bloco de texto por fora dela e a paginação
@@ -51,18 +70,20 @@ export function ResponsiveDataView({
51
70
  }
52
71
 
53
72
  const busyClass = isBusy && "pointer-events-none opacity-50"
73
+ const visibility = BREAKPOINT_CLASSES[breakpoint]
54
74
  return (
55
75
  <div className="space-y-4">
56
76
  <div
57
77
  aria-busy={isBusy}
58
78
  className={cn(
59
- "hidden overflow-x-auto rounded-md border transition-opacity md:block",
79
+ "hidden overflow-x-auto rounded-md border transition-opacity",
80
+ visibility.table,
60
81
  busyClass,
61
82
  )}
62
83
  >
63
84
  {table}
64
85
  </div>
65
- <div aria-busy={isBusy} className={cn("transition-opacity md:hidden", busyClass)}>
86
+ <div aria-busy={isBusy} className={cn("transition-opacity", visibility.cards, busyClass)}>
66
87
  {cards}
67
88
  </div>
68
89
  {pagination}
@@ -27,8 +27,9 @@ export interface RowActionsMenuProps {
27
27
  renderLink?: RenderLink
28
28
  }
29
29
 
30
+ // 32px no desktop, 40px de alvo de toque no celular.
30
31
  const triggerClass =
31
- "inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50"
32
+ "inline-flex h-8 w-8 max-sm:h-10 max-sm:w-10 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50"
32
33
 
33
34
  // Desktop: itens compactos do dropdown. Mobile: alvos de toque maiores.
34
35
  const itemClass =
@@ -57,7 +57,7 @@ export function UserMenu({
57
57
  const trigger = (
58
58
  <button
59
59
  type="button"
60
- className="inline-flex h-9 w-9 items-center justify-center overflow-hidden rounded-full border border-input bg-background text-xs font-semibold shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
60
+ className="inline-flex h-9 w-9 max-sm:h-10 max-sm:w-10 items-center justify-center overflow-hidden rounded-full border border-input bg-background text-xs font-semibold shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
61
61
  aria-label="Menu do usuário"
62
62
  data-testid="user-menu"
63
63
  >
package/src/index.ts CHANGED
@@ -43,7 +43,7 @@ export type { PaginationProps } from "./components/pagination";
43
43
  export { Pagination } from "./components/pagination";
44
44
  export { PasswordInput } from "./components/password-input";
45
45
  export { ProgressBar } from "./components/progress-bar";
46
- export { ResponsiveDataView } from "./components/responsive-data-view";
46
+ export { ResponsiveDataView, type ResponsiveDataViewBreakpoint } from "./components/responsive-data-view";
47
47
  export type {
48
48
  RowAction,
49
49
  RowActionsMenuProps,