bonsaif-ui 0.1.14 → 0.1.16

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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.15 - 2026-05-05
4
+
5
+ - Add `Drawer` unsaved-changes guard props so internal close actions can require discard confirmation before closing.
6
+ - Hide the `DataTable` pagination footer automatically when the current row count fits within one page.
7
+
3
8
  ## 0.1.8 - 2026-05-04
4
9
 
5
10
  - Normalize all `PageHeader` action buttons to a compact square icon-button chrome while preserving each button icon, tooltip, label, and action.
package/README.md CHANGED
@@ -46,6 +46,84 @@ The package includes reusable components migrated from `prime-auth` and new shar
46
46
  - `Modal`, `Drawer`, `ConfirmDialog`: overlays and confirmations.
47
47
  - `MouseTooltip`: lightweight hover tooltip.
48
48
 
49
+ #### Drawer Unsaved Changes Guard
50
+
51
+ Use `hasUnsavedChanges` when a drawer edits data and must not close silently. When the prop is `true`, the drawer intercepts its own close controls: backdrop click, Escape, the X button, and the default Cancel button. It shows a confirmation modal and only calls `onClose` after the user confirms that unsaved data can be discarded.
52
+
53
+ The guard is meant for local draft forms. Keep the saved value separate from the draft value, calculate whether the draft is dirty, and pass that boolean to `hasUnsavedChanges`.
54
+
55
+ ```tsx
56
+ import { useState } from "react";
57
+ import { Drawer, Textarea } from "bonsaif-ui";
58
+
59
+ export function TeamNotesDrawer() {
60
+ const [open, setOpen] = useState(false);
61
+ const [savedNote, setSavedNote] = useState("Nota guardada del equipo.");
62
+ const [draftNote, setDraftNote] = useState(savedNote);
63
+ const hasUnsavedChanges = draftNote !== savedNote;
64
+
65
+ function openDrawer() {
66
+ setDraftNote(savedNote);
67
+ setOpen(true);
68
+ }
69
+
70
+ function saveDrawer() {
71
+ setSavedNote(draftNote);
72
+ setOpen(false);
73
+ }
74
+
75
+ return (
76
+ <>
77
+ <button type="button" onClick={openDrawer}>
78
+ Editar nota
79
+ </button>
80
+
81
+ <Drawer
82
+ open={open}
83
+ onClose={() => setOpen(false)}
84
+ title="Editar equipo"
85
+ onConfirm={saveDrawer}
86
+ hasUnsavedChanges={hasUnsavedChanges}
87
+ unsavedChangesTitle="Descartar cambios"
88
+ unsavedChangesDescription="Hay cambios sin guardar. Si cierras el drawer se perderan."
89
+ unsavedChangesConfirmLabel="Descartar"
90
+ unsavedChangesCancelLabel="Seguir editando"
91
+ onDiscardChanges={() => setDraftNote(savedNote)}
92
+ >
93
+ <Textarea
94
+ value={draftNote}
95
+ onChange={(event) => setDraftNote(event.currentTarget.value)}
96
+ className="min-h-28 w-full"
97
+ />
98
+ </Drawer>
99
+ </>
100
+ );
101
+ }
102
+ ```
103
+
104
+ `onDiscardChanges` can be used to clear local draft state right before `onClose` runs.
105
+
106
+ Useful props:
107
+
108
+ - `hasUnsavedChanges`: enables the discard confirmation when `true`.
109
+ - `unsavedChangesTitle`: confirmation title.
110
+ - `unsavedChangesDescription`: confirmation body copy.
111
+ - `unsavedChangesConfirmLabel`: label for the destructive discard action.
112
+ - `unsavedChangesCancelLabel`: label for the action that keeps editing.
113
+ - `onDiscardChanges`: optional cleanup before `onClose` runs after discard confirmation.
114
+
115
+ The playground includes a live drawer example: edit the note, then try closing with the `X`, backdrop, Escape, or Cancel button to see the discard modal.
116
+
117
+ How it works:
118
+
119
+ 1. The consuming screen owns the saved value and draft value.
120
+ 2. The screen passes `hasUnsavedChanges={true}` when the draft differs from the saved value.
121
+ 3. Drawer close requests from backdrop click, Escape, the X button, and the default Cancel button are intercepted.
122
+ 4. The discard modal opens with a fade plus scale/slide animation.
123
+ 5. Choosing `Seguir editando` closes only the modal and keeps the drawer open.
124
+ 6. Choosing the discard action calls `onDiscardChanges` first, then calls `onClose`.
125
+ 7. If `hasUnsavedChanges` is `false`, close actions call `onClose` immediately.
126
+
49
127
  ### Feedback And Data
50
128
 
51
129
  - `Alert`, `StatusBadge`, `EmptyState`, `LoadingState`: visible states and feedback.
@@ -116,6 +194,10 @@ import { CopyButton, CopyLinkButton } from "bonsaif-ui";
116
194
 
117
195
  `DataTable` owns its horizontal and vertical scrolling. When a table is rendered inside a constrained panel, pass `minTableWidth` so the table keeps usable column widths and exposes its own internal horizontal scrollbar.
118
196
 
197
+ When pagination is enabled with `pageSize`, the pagination footer is hidden automatically if the row count fits in a single page. For example, a table with `pageSize={25}` and 25 or fewer rows shows no pagination controls; once it has 26 rows, the footer appears.
198
+
199
+ Header controls for column reordering, resizing, and visibility are floating controls. The drag handle and column visibility menu animate in on hover/focus and do not consume label width, so narrow headers keep as much readable text as possible.
200
+
119
201
  ```tsx
120
202
  <DataTable
121
203
  columns={columns}
@@ -123,6 +205,43 @@ import { CopyButton, CopyLinkButton } from "bonsaif-ui";
123
205
  getRowId={(row) => row.id}
124
206
  minTableWidth={810}
125
207
  />
208
+
209
+ <DataTable
210
+ columns={columns}
211
+ data={rows.slice(0, 25)}
212
+ getRowId={(row) => row.id}
213
+ pageSize={25}
214
+ />
215
+ ```
216
+
217
+ ```tsx
218
+ // No pagination footer: all rows fit in one page.
219
+ <DataTable
220
+ columns={columns}
221
+ data={rows.slice(0, 25)}
222
+ getRowId={(row) => row.id}
223
+ pageSize={25}
224
+ />
225
+
226
+ // Pagination footer appears: row 26 needs another page.
227
+ <DataTable
228
+ columns={columns}
229
+ data={rows.slice(0, 26)}
230
+ getRowId={(row) => row.id}
231
+ pageSize={25}
232
+ />
233
+
234
+ // Server-side mode uses totalRows for the same decision.
235
+ <DataTable
236
+ columns={columns}
237
+ data={rows}
238
+ getRowId={(row) => row.id}
239
+ page={1}
240
+ pageSize={25}
241
+ totalRows={26}
242
+ onPageChange={setPage}
243
+ onPageSizeChange={setPageSize}
244
+ />
126
245
  ```
127
246
 
128
247
  ## StepWizard