gbs-add-block 1.2.10 → 1.2.11
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/README.md +3 -2
- package/index.cjs +133 -123
- package/package.json +1 -1
- package/source/beta-components/datepicker/README.md +141 -0
- package/source/beta-components/datepicker/__tests__/core.test.ts +220 -0
- package/source/beta-components/datepicker/core/calendar.ts +263 -0
- package/source/beta-components/datepicker/core/format.ts +170 -0
- package/source/beta-components/datepicker/core/index.ts +40 -0
- package/source/beta-components/datepicker/core/types.ts +68 -0
- package/source/beta-components/datepicker/index.ts +16 -0
- package/source/beta-components/datepicker/react/Calendar.tsx +160 -0
- package/source/beta-components/datepicker/react/CalendarPanel.tsx +170 -0
- package/source/beta-components/datepicker/react/DatePicker.tsx +249 -0
- package/source/beta-components/datepicker/react/DateRangePicker.tsx +291 -0
- package/source/beta-components/datepicker/react/MonthYearPanels.tsx +123 -0
- package/source/beta-components/datepicker/react/Popover.tsx +87 -0
- package/source/beta-components/datepicker/react/icons.tsx +32 -0
- package/source/beta-components/datepicker/react/locale.ts +23 -0
- package/source/beta-components/datepicker/react/props.ts +76 -0
- package/source/beta-components/datepicker/react/useDatePicker.ts +547 -0
- package/source/beta-components/datepicker/styles.css +446 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# GBS Building Blocks 2.0 (v1.2.
|
|
1
|
+
# GBS Building Blocks 2.0 (v1.2.11)
|
|
2
2
|
|
|
3
3
|
Latest and upgraded version of GBS building blocks with headless UI and removed dependencies.
|
|
4
4
|
|
|
@@ -13,9 +13,10 @@ DataGrid and Combobox use the redesigned core API and design system. Install the
|
|
|
13
13
|
```bash
|
|
14
14
|
npx gbs-add-block -a DataGrid -beta
|
|
15
15
|
npx gbs-add-block -a Combobox -beta
|
|
16
|
+
npx gbs-add-block -a DatePicker -beta
|
|
16
17
|
```
|
|
17
18
|
|
|
18
|
-
## What's New 🎉 (Ver 1.2.
|
|
19
|
+
## What's New 🎉 (Ver 1.2.11)
|
|
19
20
|
|
|
20
21
|
- Update Candidate for next major change 2.0.0
|
|
21
22
|
|
package/index.cjs
CHANGED
|
@@ -32,13 +32,10 @@ const CONFIG = {
|
|
|
32
32
|
"DataGrid",
|
|
33
33
|
"BreadCrumb",
|
|
34
34
|
"Bargraph",
|
|
35
|
-
"UsePaginatedData",
|
|
36
|
-
"UseUploader",
|
|
37
|
-
],
|
|
38
|
-
betaComponents: [
|
|
39
|
-
"DataGrid",
|
|
40
|
-
"Combobox",
|
|
41
|
-
],
|
|
35
|
+
"UsePaginatedData",
|
|
36
|
+
"UseUploader",
|
|
37
|
+
],
|
|
38
|
+
betaComponents: ["DataGrid", "Combobox", "DatePicker"],
|
|
42
39
|
// Define component dependencies
|
|
43
40
|
dependencies: {
|
|
44
41
|
FormRenderer: ["Select", "MultiSelect", "Input", "DatePicker"],
|
|
@@ -46,19 +43,19 @@ const CONFIG = {
|
|
|
46
43
|
docs: "https://gramprokit.vercel.app/",
|
|
47
44
|
};
|
|
48
45
|
|
|
49
|
-
const SOURCE_PATH = path.join(__dirname, "source", "components");
|
|
50
|
-
const BETA_SOURCE_PATH = path.join(__dirname, "source", "beta-components");
|
|
51
|
-
const DEFAULT_DEST_PATH = path.join(process.cwd(), "component-lib");
|
|
52
|
-
|
|
53
|
-
const normalizeComponent = (component, availableComponents) =>
|
|
54
|
-
availableComponents.find(
|
|
55
|
-
(available) => available.toLowerCase() === component.toLowerCase(),
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
const getAvailableComponents = (beta = false) =>
|
|
59
|
-
beta ? CONFIG.betaComponents : CONFIG.components;
|
|
60
|
-
|
|
61
|
-
const getSourcePath = (beta = false) => (beta ? BETA_SOURCE_PATH : SOURCE_PATH);
|
|
46
|
+
const SOURCE_PATH = path.join(__dirname, "source", "components");
|
|
47
|
+
const BETA_SOURCE_PATH = path.join(__dirname, "source", "beta-components");
|
|
48
|
+
const DEFAULT_DEST_PATH = path.join(process.cwd(), "component-lib");
|
|
49
|
+
|
|
50
|
+
const normalizeComponent = (component, availableComponents) =>
|
|
51
|
+
availableComponents.find(
|
|
52
|
+
(available) => available.toLowerCase() === component.toLowerCase(),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const getAvailableComponents = (beta = false) =>
|
|
56
|
+
beta ? CONFIG.betaComponents : CONFIG.components;
|
|
57
|
+
|
|
58
|
+
const getSourcePath = (beta = false) => (beta ? BETA_SOURCE_PATH : SOURCE_PATH);
|
|
62
59
|
|
|
63
60
|
const copyCommonFiles = async (destPath) => {
|
|
64
61
|
const commonFiles = [
|
|
@@ -81,16 +78,19 @@ const checkComponentExists = (component, destPath) => {
|
|
|
81
78
|
return fs.existsSync(componentPath);
|
|
82
79
|
};
|
|
83
80
|
|
|
84
|
-
const copyComponent = async (component, destPath, beta = false) => {
|
|
85
|
-
try {
|
|
86
|
-
const componentSrc = path.join(
|
|
81
|
+
const copyComponent = async (component, destPath, beta = false) => {
|
|
82
|
+
try {
|
|
83
|
+
const componentSrc = path.join(
|
|
84
|
+
getSourcePath(beta),
|
|
85
|
+
component.toLowerCase(),
|
|
86
|
+
);
|
|
87
87
|
const componentDest = path.join(destPath, component.toLowerCase());
|
|
88
88
|
|
|
89
|
-
if (!fs.existsSync(componentSrc)) {
|
|
90
|
-
throw new Error(
|
|
91
|
-
`Component ${component} not found in ${beta ? "beta " : ""}source directory.`,
|
|
92
|
-
);
|
|
93
|
-
}
|
|
89
|
+
if (!fs.existsSync(componentSrc)) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Component ${component} not found in ${beta ? "beta " : ""}source directory.`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
94
|
|
|
95
95
|
await fs.copy(componentSrc, componentDest, { overwrite: true });
|
|
96
96
|
console.log(
|
|
@@ -106,17 +106,21 @@ const copyComponent = async (component, destPath, beta = false) => {
|
|
|
106
106
|
}
|
|
107
107
|
};
|
|
108
108
|
|
|
109
|
-
const installComponentWithDependencies = async (
|
|
109
|
+
const installComponentWithDependencies = async (
|
|
110
|
+
component,
|
|
111
|
+
destPath,
|
|
112
|
+
beta = false,
|
|
113
|
+
) => {
|
|
110
114
|
// Get dependencies for the component
|
|
111
115
|
const dependencies = CONFIG.dependencies[component] || [];
|
|
112
116
|
const componentsToInstall = new Set([component, ...dependencies]);
|
|
113
117
|
|
|
114
118
|
// Check which components need to be installed
|
|
115
|
-
const pendingInstalls = beta
|
|
116
|
-
? Array.from(componentsToInstall)
|
|
117
|
-
: Array.from(componentsToInstall).filter(
|
|
118
|
-
(comp) => !checkComponentExists(comp, destPath),
|
|
119
|
-
);
|
|
119
|
+
const pendingInstalls = beta
|
|
120
|
+
? Array.from(componentsToInstall)
|
|
121
|
+
: Array.from(componentsToInstall).filter(
|
|
122
|
+
(comp) => !checkComponentExists(comp, destPath),
|
|
123
|
+
);
|
|
120
124
|
|
|
121
125
|
if (pendingInstalls.length === 0) {
|
|
122
126
|
console.log(
|
|
@@ -127,7 +131,7 @@ const installComponentWithDependencies = async (component, destPath, beta = fals
|
|
|
127
131
|
|
|
128
132
|
// Install all pending components
|
|
129
133
|
for (const comp of pendingInstalls) {
|
|
130
|
-
await copyComponent(comp, destPath, beta);
|
|
134
|
+
await copyComponent(comp, destPath, beta);
|
|
131
135
|
}
|
|
132
136
|
|
|
133
137
|
if (dependencies.length > 0) {
|
|
@@ -140,7 +144,11 @@ const installComponentWithDependencies = async (component, destPath, beta = fals
|
|
|
140
144
|
console.log(`\nFor documentation visit: ${CONFIG.docs}`);
|
|
141
145
|
};
|
|
142
146
|
|
|
143
|
-
const installMultipleComponents = async (
|
|
147
|
+
const installMultipleComponents = async (
|
|
148
|
+
components,
|
|
149
|
+
destPath,
|
|
150
|
+
beta = false,
|
|
151
|
+
) => {
|
|
144
152
|
const allComponentsToInstall = new Set();
|
|
145
153
|
|
|
146
154
|
// Collect all components and their dependencies
|
|
@@ -151,11 +159,11 @@ const installMultipleComponents = async (components, destPath, beta = false) =>
|
|
|
151
159
|
});
|
|
152
160
|
|
|
153
161
|
// Filter out already installed components
|
|
154
|
-
const pendingInstalls = beta
|
|
155
|
-
? Array.from(allComponentsToInstall)
|
|
156
|
-
: Array.from(allComponentsToInstall).filter(
|
|
157
|
-
(comp) => !checkComponentExists(comp, destPath),
|
|
158
|
-
);
|
|
162
|
+
const pendingInstalls = beta
|
|
163
|
+
? Array.from(allComponentsToInstall)
|
|
164
|
+
: Array.from(allComponentsToInstall).filter(
|
|
165
|
+
(comp) => !checkComponentExists(comp, destPath),
|
|
166
|
+
);
|
|
159
167
|
|
|
160
168
|
if (pendingInstalls.length === 0) {
|
|
161
169
|
console.log(
|
|
@@ -168,7 +176,7 @@ const installMultipleComponents = async (components, destPath, beta = false) =>
|
|
|
168
176
|
|
|
169
177
|
// Install all pending components
|
|
170
178
|
for (const comp of pendingInstalls) {
|
|
171
|
-
await copyComponent(comp, destPath, beta);
|
|
179
|
+
await copyComponent(comp, destPath, beta);
|
|
172
180
|
}
|
|
173
181
|
|
|
174
182
|
// Show dependency information
|
|
@@ -188,7 +196,7 @@ const installMultipleComponents = async (components, destPath, beta = false) =>
|
|
|
188
196
|
console.log(`\nFor documentation visit: ${CONFIG.docs}`);
|
|
189
197
|
};
|
|
190
198
|
|
|
191
|
-
const interactiveComponentSelector = async (beta = false) => {
|
|
199
|
+
const interactiveComponentSelector = async (beta = false) => {
|
|
192
200
|
return new Promise((resolve) => {
|
|
193
201
|
const rl = readline.createInterface({
|
|
194
202
|
input: process.stdin,
|
|
@@ -205,9 +213,9 @@ const interactiveComponentSelector = async (beta = false) => {
|
|
|
205
213
|
"Use ↑/↓ arrow keys to navigate, SPACE to select/deselect, ENTER to install\n",
|
|
206
214
|
);
|
|
207
215
|
|
|
208
|
-
const availableComponents = getAvailableComponents(beta);
|
|
209
|
-
|
|
210
|
-
availableComponents.forEach((component, index) => {
|
|
216
|
+
const availableComponents = getAvailableComponents(beta);
|
|
217
|
+
|
|
218
|
+
availableComponents.forEach((component, index) => {
|
|
211
219
|
const isSelected = selectedComponents.has(component);
|
|
212
220
|
const isCurrentIndex = index === currentIndex;
|
|
213
221
|
const deps = CONFIG.dependencies[component]
|
|
@@ -237,13 +245,13 @@ const interactiveComponentSelector = async (beta = false) => {
|
|
|
237
245
|
break;
|
|
238
246
|
case "\u001b[B": // Down arrow
|
|
239
247
|
currentIndex = Math.min(
|
|
240
|
-
getAvailableComponents(beta).length - 1,
|
|
248
|
+
getAvailableComponents(beta).length - 1,
|
|
241
249
|
currentIndex + 1,
|
|
242
250
|
);
|
|
243
251
|
renderMenu();
|
|
244
252
|
break;
|
|
245
253
|
case " ": // Space bar
|
|
246
|
-
const component = getAvailableComponents(beta)[currentIndex];
|
|
254
|
+
const component = getAvailableComponents(beta)[currentIndex];
|
|
247
255
|
if (selectedComponents.has(component)) {
|
|
248
256
|
selectedComponents.delete(component);
|
|
249
257
|
} else {
|
|
@@ -286,29 +294,29 @@ const parseMultipleComponents = (componentString) => {
|
|
|
286
294
|
.filter((comp) => comp.length > 0);
|
|
287
295
|
};
|
|
288
296
|
|
|
289
|
-
const validateComponents = (components, beta = false) => {
|
|
290
|
-
const availableComponents = getAvailableComponents(beta);
|
|
291
|
-
const invalidComponents = components.filter(
|
|
292
|
-
(comp) => !availableComponents.includes(comp),
|
|
293
|
-
);
|
|
294
|
-
if (invalidComponents.length > 0) {
|
|
295
|
-
console.error(`Invalid components: ${invalidComponents.join(", ")}`);
|
|
296
|
-
console.log(`\nAvailable ${beta ? "beta " : ""}components:`);
|
|
297
|
-
availableComponents.forEach((comp) => console.log(`- ${comp}`));
|
|
298
|
-
if (!beta) {
|
|
299
|
-
console.log("\nRedesigned beta components (install with -beta):");
|
|
300
|
-
CONFIG.betaComponents.forEach((comp) => console.log(`- ${comp}`));
|
|
301
|
-
}
|
|
302
|
-
return false;
|
|
303
|
-
}
|
|
304
|
-
return true;
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
const main = async () => {
|
|
308
|
-
const args = hideBin(process.argv).map((arg) =>
|
|
309
|
-
arg === "-beta" ? "--beta" : arg,
|
|
310
|
-
);
|
|
311
|
-
const argv = yargs(args)
|
|
297
|
+
const validateComponents = (components, beta = false) => {
|
|
298
|
+
const availableComponents = getAvailableComponents(beta);
|
|
299
|
+
const invalidComponents = components.filter(
|
|
300
|
+
(comp) => !availableComponents.includes(comp),
|
|
301
|
+
);
|
|
302
|
+
if (invalidComponents.length > 0) {
|
|
303
|
+
console.error(`Invalid components: ${invalidComponents.join(", ")}`);
|
|
304
|
+
console.log(`\nAvailable ${beta ? "beta " : ""}components:`);
|
|
305
|
+
availableComponents.forEach((comp) => console.log(`- ${comp}`));
|
|
306
|
+
if (!beta) {
|
|
307
|
+
console.log("\nRedesigned beta components (install with -beta):");
|
|
308
|
+
CONFIG.betaComponents.forEach((comp) => console.log(`- ${comp}`));
|
|
309
|
+
}
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
return true;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const main = async () => {
|
|
316
|
+
const args = hideBin(process.argv).map((arg) =>
|
|
317
|
+
arg === "-beta" ? "--beta" : arg,
|
|
318
|
+
);
|
|
319
|
+
const argv = yargs(args)
|
|
312
320
|
.option("add", {
|
|
313
321
|
alias: "a",
|
|
314
322
|
describe:
|
|
@@ -320,44 +328,44 @@ const main = async () => {
|
|
|
320
328
|
describe: "Interactive component selection mode",
|
|
321
329
|
type: "boolean",
|
|
322
330
|
})
|
|
323
|
-
.option("list", {
|
|
324
|
-
alias: "l",
|
|
325
|
-
describe: "List available components",
|
|
326
|
-
type: "boolean",
|
|
327
|
-
})
|
|
328
|
-
.option("beta", {
|
|
329
|
-
describe: "Install redesigned beta components",
|
|
330
|
-
type: "boolean",
|
|
331
|
-
default: false,
|
|
332
|
-
})
|
|
333
|
-
.example("$0 -a Button", "Install a single component")
|
|
334
|
-
.example("$0 -a Button,Card,Modal", "Install multiple components")
|
|
335
|
-
.example("$0 -a DataGrid -beta", "Install the redesigned beta DataGrid")
|
|
336
|
-
.example("$0 -a Combobox -beta", "Install the redesigned beta Combobox")
|
|
337
|
-
.example("$0 -i", "Interactive selection mode")
|
|
331
|
+
.option("list", {
|
|
332
|
+
alias: "l",
|
|
333
|
+
describe: "List available components",
|
|
334
|
+
type: "boolean",
|
|
335
|
+
})
|
|
336
|
+
.option("beta", {
|
|
337
|
+
describe: "Install redesigned beta components",
|
|
338
|
+
type: "boolean",
|
|
339
|
+
default: false,
|
|
340
|
+
})
|
|
341
|
+
.example("$0 -a Button", "Install a single component")
|
|
342
|
+
.example("$0 -a Button,Card,Modal", "Install multiple components")
|
|
343
|
+
.example("$0 -a DataGrid -beta", "Install the redesigned beta DataGrid")
|
|
344
|
+
.example("$0 -a Combobox -beta", "Install the redesigned beta Combobox")
|
|
345
|
+
.example("$0 -i", "Interactive selection mode")
|
|
338
346
|
.help().argv;
|
|
339
347
|
|
|
340
|
-
// List components if requested
|
|
341
|
-
if (argv.list) {
|
|
342
|
-
const availableComponents = getAvailableComponents(argv.beta);
|
|
343
|
-
console.log(`\nAvailable ${argv.beta ? "beta " : ""}components:`);
|
|
344
|
-
availableComponents.forEach((comp) => {
|
|
345
|
-
const deps = CONFIG.dependencies[comp]
|
|
346
|
-
? ` (requires: ${CONFIG.dependencies[comp].join(", ")})`
|
|
347
|
-
: "";
|
|
348
|
-
console.log(`- ${comp}${deps}`);
|
|
349
|
-
});
|
|
350
|
-
if (!argv.beta) {
|
|
351
|
-
console.log("\nRedesigned beta components (install with -beta):");
|
|
352
|
-
CONFIG.betaComponents.forEach((comp) => console.log(`- ${comp}`));
|
|
353
|
-
}
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
348
|
+
// List components if requested
|
|
349
|
+
if (argv.list) {
|
|
350
|
+
const availableComponents = getAvailableComponents(argv.beta);
|
|
351
|
+
console.log(`\nAvailable ${argv.beta ? "beta " : ""}components:`);
|
|
352
|
+
availableComponents.forEach((comp) => {
|
|
353
|
+
const deps = CONFIG.dependencies[comp]
|
|
354
|
+
? ` (requires: ${CONFIG.dependencies[comp].join(", ")})`
|
|
355
|
+
: "";
|
|
356
|
+
console.log(`- ${comp}${deps}`);
|
|
357
|
+
});
|
|
358
|
+
if (!argv.beta) {
|
|
359
|
+
console.log("\nRedesigned beta components (install with -beta):");
|
|
360
|
+
CONFIG.betaComponents.forEach((comp) => console.log(`- ${comp}`));
|
|
361
|
+
}
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
356
364
|
|
|
357
365
|
// Interactive mode
|
|
358
|
-
if (argv.interactive) {
|
|
359
|
-
console.log("Starting interactive component selector...\n");
|
|
360
|
-
const selectedComponents = await interactiveComponentSelector(argv.beta);
|
|
366
|
+
if (argv.interactive) {
|
|
367
|
+
console.log("Starting interactive component selector...\n");
|
|
368
|
+
const selectedComponents = await interactiveComponentSelector(argv.beta);
|
|
361
369
|
|
|
362
370
|
if (selectedComponents.length === 0) {
|
|
363
371
|
console.log("No components selected. Exiting...");
|
|
@@ -373,8 +381,8 @@ const main = async () => {
|
|
|
373
381
|
await copyCommonFiles(destPath);
|
|
374
382
|
}
|
|
375
383
|
|
|
376
|
-
// Install selected components
|
|
377
|
-
await installMultipleComponents(selectedComponents, destPath, argv.beta);
|
|
384
|
+
// Install selected components
|
|
385
|
+
await installMultipleComponents(selectedComponents, destPath, argv.beta);
|
|
378
386
|
return;
|
|
379
387
|
}
|
|
380
388
|
|
|
@@ -385,14 +393,16 @@ const main = async () => {
|
|
|
385
393
|
process.exit(1);
|
|
386
394
|
}
|
|
387
395
|
|
|
388
|
-
// Parse components (single or multiple)
|
|
389
|
-
const componentInput = argv.add;
|
|
390
|
-
const components = parseMultipleComponents(componentInput).map(
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
+
// Parse components (single or multiple)
|
|
397
|
+
const componentInput = argv.add;
|
|
398
|
+
const components = parseMultipleComponents(componentInput).map(
|
|
399
|
+
(component) =>
|
|
400
|
+
normalizeComponent(component, getAvailableComponents(argv.beta)) ||
|
|
401
|
+
component,
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
// Validate all components
|
|
405
|
+
if (!validateComponents(components, argv.beta)) {
|
|
396
406
|
process.exit(1);
|
|
397
407
|
}
|
|
398
408
|
|
|
@@ -406,14 +416,14 @@ const main = async () => {
|
|
|
406
416
|
}
|
|
407
417
|
|
|
408
418
|
// Install components
|
|
409
|
-
if (components.length === 1) {
|
|
410
|
-
// Single component installation (existing behavior)
|
|
411
|
-
await installComponentWithDependencies(components[0], destPath, argv.beta);
|
|
412
|
-
} else {
|
|
413
|
-
// Multiple components installation
|
|
414
|
-
await installMultipleComponents(components, destPath, argv.beta);
|
|
415
|
-
}
|
|
416
|
-
};
|
|
419
|
+
if (components.length === 1) {
|
|
420
|
+
// Single component installation (existing behavior)
|
|
421
|
+
await installComponentWithDependencies(components[0], destPath, argv.beta);
|
|
422
|
+
} else {
|
|
423
|
+
// Multiple components installation
|
|
424
|
+
await installMultipleComponents(components, destPath, argv.beta);
|
|
425
|
+
}
|
|
426
|
+
};
|
|
417
427
|
|
|
418
428
|
main().catch((error) => {
|
|
419
429
|
console.error("Error:", error.message);
|
package/package.json
CHANGED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# DatePicker and DateRangePicker
|
|
2
|
+
|
|
3
|
+
Two date fields built on one engine and styled to match the DataGrid and the
|
|
4
|
+
Combobox. No runtime dependencies besides React 19.
|
|
5
|
+
|
|
6
|
+
- **`<DatePicker>`** — one date.
|
|
7
|
+
- **`<DateRangePicker>`** — a start and an end, with a live preview between them.
|
|
8
|
+
|
|
9
|
+
Both support typed entry in the user's own date order, min/max limits, blocked
|
|
10
|
+
days, month and year panels, week numbers, forms, full keyboard control, dark
|
|
11
|
+
mode and right-to-left layouts.
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { DatePicker, DateRangePicker } from "@/components/date-picker";
|
|
17
|
+
import "@/components/date-picker/styles.css";
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The stylesheet reuses the DataGrid's `--dg-*` variables when that stylesheet is
|
|
21
|
+
loaded, so every component shares a theme, and falls back to the same palette
|
|
22
|
+
when used on its own.
|
|
23
|
+
|
|
24
|
+
## Basic usage
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
const [date, setDate] = useState<Date | null>(null);
|
|
28
|
+
|
|
29
|
+
<DatePicker label="Start date" value={date} onChange={setDate} />;
|
|
30
|
+
|
|
31
|
+
const [range, setRange] = useState<DateRange>({ start: null, end: null });
|
|
32
|
+
|
|
33
|
+
<DateRangePicker label="Period" value={range} onChange={setRange} />;
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Both are controlled with `value` + `onChange`, or uncontrolled with
|
|
37
|
+
`defaultValue`. `value`, `defaultValue`, `min` and `max` accept a `Date`, an ISO
|
|
38
|
+
`yyyy-mm-dd` string or a timestamp; `onChange` always gives you `Date` objects at
|
|
39
|
+
local midnight. The range picker's `onChange` also receives a `complete` flag,
|
|
40
|
+
which is `false` after the first of the two clicks.
|
|
41
|
+
|
|
42
|
+
## Props
|
|
43
|
+
|
|
44
|
+
Shared by both components:
|
|
45
|
+
|
|
46
|
+
| Prop | Type | Default | Description |
|
|
47
|
+
| --- | --- | --- | --- |
|
|
48
|
+
| `min` / `max` | `Date \| string \| number` | — | Earliest and latest selectable day. |
|
|
49
|
+
| `isDateDisabled` | `(date: Date) => boolean` | — | Called per rendered day; return true to block it. |
|
|
50
|
+
| `locale` | `string` | runtime locale | Decides month names, field order and the first day of the week. |
|
|
51
|
+
| `weekStartsOn` | `0`–`6` | locale's first day | 0 is Sunday. |
|
|
52
|
+
| `format` | `Intl.DateTimeFormatOptions \| (date, locale) => string` | `{ year, month: "short", day }` | How the chosen date is written in the field. |
|
|
53
|
+
| `numberOfMonths` | `number` | `1` (range: `2`) | Months shown side by side. |
|
|
54
|
+
| `showWeekNumbers` | `boolean` | `false` | Adds an ISO week column. |
|
|
55
|
+
| `showToday` | `boolean` | `true` (range: `false`) | The "Today" shortcut in the footer. |
|
|
56
|
+
| `allowInput` | `boolean` | `true` | Let people type a date. When false the field is read-only and opens on click. |
|
|
57
|
+
| `fixedWeeks` | `boolean` | `true` | Always six week rows, so the popover never changes height. |
|
|
58
|
+
| `label`, `description`, `error` | `ReactNode` | — | Field label, hint and error message. `error` also marks the control invalid. |
|
|
59
|
+
| `placeholder` | `string` | the locale's pattern | e.g. `dd/mm/yyyy`. |
|
|
60
|
+
| `required`, `disabled`, `readOnly` | `boolean` | `false` | Field states. |
|
|
61
|
+
| `clearable` | `boolean` | `true` | Show the clear button. |
|
|
62
|
+
| `size` | `"sm"` \| `"md"` \| `"lg"` | `"md"` | Control height and font size. |
|
|
63
|
+
| `name` | `string` | — | Posts `yyyy-mm-dd` in a hidden input. The range picker posts `name-start` and `name-end`. |
|
|
64
|
+
| `className`, `classNames`, `style` | — | — | Styling hooks. Slots: `root`, `label`, `control`, `input`, `popover`, `calendar`, `day`, `footer`, `presets`. |
|
|
65
|
+
| `localeText` | `Partial<DatePickerLocaleText>` | English | Overrides UI text. |
|
|
66
|
+
| `onOpenChange` | `(open: boolean) => void` | — | Fires when the calendar opens or closes. |
|
|
67
|
+
| `ref` | `Ref<DatePickerHandle<T>>` | — | `open()`, `close()`, `toggle()`, `focus()`, `clear()`, `getValue()`. |
|
|
68
|
+
|
|
69
|
+
`DatePicker` adds `value` / `defaultValue` (`Date \| null`) and `closeOnSelect`
|
|
70
|
+
(default `true`). `DateRangePicker` adds `value` / `defaultValue`
|
|
71
|
+
(`{ start, end }`), `presets` and `closeOnSelect` (closes once both ends are set).
|
|
72
|
+
|
|
73
|
+
## Typed entry
|
|
74
|
+
|
|
75
|
+
People can type instead of picking. Input is read in the locale's own field
|
|
76
|
+
order, so `03/04/2026` is 3 April in `en-GB` and 4 March in `en-US`. ISO
|
|
77
|
+
(`2026-03-12`) is accepted in every locale, month names work (`12 Mar 2026`), and
|
|
78
|
+
a missing year or month falls back to the month on screen. The calendar follows
|
|
79
|
+
along as you type. Text that isn't a date, or a date outside the limits, shows a
|
|
80
|
+
message when the field loses focus and leaves the value untouched.
|
|
81
|
+
|
|
82
|
+
## Range presets
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
<DateRangePicker
|
|
86
|
+
value={range}
|
|
87
|
+
onChange={setRange}
|
|
88
|
+
presets={[
|
|
89
|
+
{ label: "Last 7 days", range: { start: addDays(new Date(), -6), end: new Date() } },
|
|
90
|
+
{ label: "This month", range: { start: startOfMonth(new Date()), end: new Date() } },
|
|
91
|
+
]}
|
|
92
|
+
/>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Keyboard
|
|
96
|
+
|
|
97
|
+
| Keys | Action |
|
|
98
|
+
| --- | --- |
|
|
99
|
+
| **Enter**, **↓** | Open the calendar (from the field). |
|
|
100
|
+
| **←** / **→** | Previous / next day (swapped in right-to-left layouts). |
|
|
101
|
+
| **↑** / **↓** | Same weekday, previous / next week. |
|
|
102
|
+
| **Home** / **End** | First / last day of the week. |
|
|
103
|
+
| **Page Up** / **Page Down** | Previous / next month. |
|
|
104
|
+
| **Shift + Page Up/Down** | Previous / next year. |
|
|
105
|
+
| **Enter**, **Space** | Choose the focused day. |
|
|
106
|
+
| **Escape** | Close and return focus to the calendar button. |
|
|
107
|
+
|
|
108
|
+
Each month is a `role="grid"` where only the focused day is tabbable, which is
|
|
109
|
+
the WAI-ARIA pattern for a date picker. The calendar renders in the top layer
|
|
110
|
+
through the native Popover API, so it is never clipped by a scrolling parent
|
|
111
|
+
(including inside a DataGrid cell).
|
|
112
|
+
|
|
113
|
+
## Theming
|
|
114
|
+
|
|
115
|
+
Override `--dp-*` variables (they default to the grid's `--dg-*`):
|
|
116
|
+
|
|
117
|
+
```css
|
|
118
|
+
.dp-root { --dp-accent: #7c3aed; --dp-radius: 12px; --dp-day-size: 38px; }
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
State attributes for styling: `data-state="open"`, `data-size`, `data-invalid`,
|
|
122
|
+
`data-disabled` on the root and control; `data-today`, `data-selected`,
|
|
123
|
+
`data-outside`, `data-weekend` on days; `data-in-range`, `data-range-start`,
|
|
124
|
+
`data-range-end` on day cells.
|
|
125
|
+
|
|
126
|
+
## Headless use
|
|
127
|
+
|
|
128
|
+
`useDatePicker()` holds the whole engine (selection, visible months, typed
|
|
129
|
+
input, keyboard movement, popover state) and is exported if you want a different
|
|
130
|
+
UI on top. The framework-free helpers — `buildMonth`, `buildMonths`, `parseDate`,
|
|
131
|
+
`formatDate`, `moveByKey`, `normalizeRange`, `isInRange`, `toISODate` — are
|
|
132
|
+
exported from `@/components/date-picker/core` and also run on a server.
|
|
133
|
+
|
|
134
|
+
## Known limits
|
|
135
|
+
|
|
136
|
+
- Dates only: no time-of-day or time zone selection. Values are local midnight.
|
|
137
|
+
- One range per field; multiple disjoint ranges aren't supported.
|
|
138
|
+
- Only the Gregorian calendar is handled, though month and weekday names,
|
|
139
|
+
field order and the first day of the week all follow the locale.
|
|
140
|
+
- When rendering on a server, pass `locale` explicitly so the server and the
|
|
141
|
+
browser format the field identically.
|