@motor-hero/ui-kit 0.49.0 → 0.50.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.49.0",
3
+ "version": "0.50.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,10 +1,10 @@
1
- export interface PaginationProps {
1
+ import { useId, useState } from "react"
2
+
3
+ interface PaginationBaseProps {
2
4
  page: number
3
5
  onPageChange: (page: number) => void
4
6
  hasNextPage: boolean
5
7
  hasPreviousPage: boolean
6
- /** Total de páginas. Quando informado, o rótulo vira "Página {page} de {totalPages}". */
7
- totalPages?: number
8
8
  /** Exibe o seletor de itens por página. Use com `pageSize`, `onPageSizeChange` e `pageSizeOptions`. */
9
9
  showPageSizeSelector?: boolean
10
10
  pageSize?: number
@@ -13,6 +13,20 @@ export interface PaginationProps {
13
13
  className?: string
14
14
  }
15
15
 
16
+ type PageJumpProps =
17
+ | {
18
+ /** Total de páginas. Quando informado, o rótulo vira "Página {page} de {totalPages}". */
19
+ totalPages?: number
20
+ showPageJump?: false
21
+ }
22
+ | {
23
+ totalPages: number
24
+ /** Torna o número da página editável para pular direto a uma página. Exige `totalPages`. */
25
+ showPageJump: true
26
+ }
27
+
28
+ export type PaginationProps = PaginationBaseProps & PageJumpProps
29
+
16
30
  // `max-sm:h-10`: alvo de toque de 40px no celular; o desktop segue em 32px.
17
31
  const navButtonClass =
18
32
  "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"
@@ -23,6 +37,7 @@ export function Pagination({
23
37
  hasNextPage,
24
38
  hasPreviousPage,
25
39
  totalPages,
40
+ showPageJump = false,
26
41
  showPageSizeSelector = false,
27
42
  pageSize,
28
43
  onPageSizeChange,
@@ -64,10 +79,14 @@ export function Pagination({
64
79
  >
65
80
  Anterior
66
81
  </button>
67
- <span className="text-sm text-muted-foreground">
68
- Página {page}
69
- {totalPages ? ` de ${totalPages}` : ""}
70
- </span>
82
+ {showPageJump && totalPages !== undefined ? (
83
+ <PageJumpInput page={page} totalPages={totalPages} onPageChange={onPageChange} />
84
+ ) : (
85
+ <span className="text-sm text-muted-foreground">
86
+ Página {page}
87
+ {totalPages ? ` de ${totalPages}` : ""}
88
+ </span>
89
+ )}
71
90
  <button
72
91
  type="button"
73
92
  onClick={() => onPageChange(page + 1)}
@@ -80,3 +99,56 @@ export function Pagination({
80
99
  </div>
81
100
  )
82
101
  }
102
+
103
+ interface PageJumpInputProps {
104
+ page: number
105
+ totalPages: number
106
+ onPageChange: (page: number) => void
107
+ }
108
+
109
+ function PageJumpInput({ page, totalPages, onPageChange }: PageJumpInputProps) {
110
+ const inputId = useId()
111
+ // `null` = sem edição em andamento; o campo mostra a página atual.
112
+ const [draft, setDraft] = useState<string | null>(null)
113
+ const [prevPage, setPrevPage] = useState(page)
114
+ if (page !== prevPage) {
115
+ setPrevPage(page)
116
+ setDraft(null)
117
+ }
118
+
119
+ // Enter e blur confirmam; valores fora do intervalo são ajustados em silêncio.
120
+ const commit = () => {
121
+ if (draft === null) return
122
+ setDraft(null)
123
+ if (draft === "") return
124
+ const target = Math.max(1, Math.min(Number(draft), totalPages))
125
+ if (target !== page) onPageChange(target)
126
+ }
127
+
128
+ return (
129
+ <span className="flex items-center gap-1.5 whitespace-nowrap text-sm text-muted-foreground">
130
+ <label htmlFor={inputId} className="max-sm:sr-only">
131
+ Página
132
+ </label>
133
+ <input
134
+ id={inputId}
135
+ type="text"
136
+ inputMode="numeric"
137
+ enterKeyHint="go"
138
+ autoComplete="off"
139
+ aria-label="Ir para a página"
140
+ value={draft ?? String(page)}
141
+ onChange={(e) => setDraft(e.target.value.replace(/\D/g, ""))}
142
+ onFocus={(e) => e.target.select()}
143
+ onBlur={commit}
144
+ onKeyDown={(e) => {
145
+ if (e.key === "Enter") commit()
146
+ else if (e.key === "Escape") setDraft(null)
147
+ }}
148
+ style={{ width: `${String(totalPages).length + 3}ch` }}
149
+ className="h-8 max-sm:h-10 rounded-md border border-input bg-background px-1.5 text-center text-xs font-medium tabular-nums text-foreground shadow-sm transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
150
+ />
151
+ <span>de {totalPages}</span>
152
+ </span>
153
+ )
154
+ }