@ifto/mantine-kit 0.2.0 → 0.3.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/README.md CHANGED
@@ -38,7 +38,7 @@ a REST HTTP client.
38
38
  **1. Install** the library and its required peer dependencies:
39
39
 
40
40
  ```bash
41
- yarn add @ifto/mantine-kit @mantine/core @mantine/hooks @mantine/notifications @tabler/icons-react clsx dayjs \
41
+ yarn add @ifto/mantine-kit @mantine/core @mantine/hooks @mantine/notifications @tabler/icons-react clsx \
42
42
  react react-dom
43
43
  ```
44
44
 
@@ -48,7 +48,7 @@ Install the optional ones only if you use the components that need them:
48
48
  |---|---|
49
49
  | `@mantine/form` | the form templates (`FormPage`, `CreateFormPage`, `EditFormPage`) and `DataForm`, which take a `useForm` form |
50
50
  | `@mantine/modals` | the confirmations of `Table` (delete), `ListPage` and `DataForm` |
51
- | `@mantine/dates` | `DateField` |
51
+ | `@mantine/dates` and `dayjs` (its peer dependency) | `DateField` |
52
52
  | `mantine-datatable` | `Table`, `ViewTable`, `ListPage` |
53
53
  | `echarts` | `EChart` and the chart types |
54
54
  | `exceljs` | Excel export |
@@ -101,6 +101,18 @@ import { SelectField } from '@ifto/mantine-kit/component/molecule/SelectField/Se
101
101
  The path is the one of the file inside `src`, without the extension (see [Components](#components)). There is no
102
102
  `index` with everything, so you only bundle what you import.
103
103
 
104
+ ### Testing with Jest
105
+
106
+ The library is published only as ESM, and Jest runs the tests in CommonJS, so it has to transform the files of the
107
+ library. Allow them in `transformIgnorePatterns` and keep the transform of your project for `.js` files:
108
+
109
+ ```js
110
+ // jest.config.js
111
+ module.exports = {
112
+ transformIgnorePatterns: ['/node_modules/(?!@ifto/mantine-kit/)']
113
+ };
114
+ ```
115
+
104
116
  ## Examples
105
117
 
106
118
  ### CRUD list page
@@ -3,5 +3,9 @@ import { DateInputProps } from '@mantine/dates';
3
3
  export interface DateProps extends DateInputProps {
4
4
  label: string;
5
5
  }
6
- /** Clearable date input in the date format of the locale (e.g. `DD/MM/YYYY` in `pt-BR`). */
6
+ /**
7
+ * Clearable date input in the date format of the locale (e.g. `DD/MM/YYYY` in `pt-BR`).
8
+ * A text `value` can be in that format or in the `YYYY-MM-DD` format `onChange` gives, so it can be controlled with
9
+ * what `onChange` gives.
10
+ */
7
11
  export declare function DateField({ label, value, ...rest }: DateProps): ReactElement;
@@ -1,17 +1,29 @@
1
1
  "use client";
2
2
  import { useKitMessages } from "../../../provider/useKit.js";
3
- import { parseDate } from "../../../util/DateUtil.js";
3
+ import { ISO_DATE_FORMAT, isValidDate, parseDate } from "../../../util/DateUtil.js";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  import { DateInput } from "@mantine/dates";
6
6
  // src/component/atom/DateField/DateField.tsx
7
- /** Clearable date input in the date format of the locale (e.g. `DD/MM/YYYY` in `pt-BR`). */
7
+ /**
8
+ * Reads the value of the field, which is a `YYYY-MM-DD` text when it comes from the `onChange` of `DateInput`.
9
+ * @param format format of the locale, for the texts not in the ISO format
10
+ */
11
+ function parseFieldValue(value, format) {
12
+ const isoDate = parseDate(value, ISO_DATE_FORMAT);
13
+ return isValidDate(isoDate) ? isoDate : parseDate(value, format);
14
+ }
15
+ /**
16
+ * Clearable date input in the date format of the locale (e.g. `DD/MM/YYYY` in `pt-BR`).
17
+ * A text `value` can be in that format or in the `YYYY-MM-DD` format `onChange` gives, so it can be controlled with
18
+ * what `onChange` gives.
19
+ */
8
20
  function DateField({ label, value, ...rest }) {
9
21
  const { dateFormat } = useKitMessages().common;
10
22
  return /* @__PURE__ */ jsx(DateInput, {
11
23
  label,
12
24
  placeholder: dateFormat,
13
25
  valueFormat: dateFormat,
14
- value: parseDate(value, dateFormat),
26
+ value: parseFieldValue(value, dateFormat),
15
27
  clearable: true,
16
28
  ...rest
17
29
  });
@@ -1 +1 @@
1
- {"version":3,"file":"DateField.js","names":[],"sources":["../../../../src/component/atom/DateField/DateField.tsx"],"sourcesContent":["import { ReactElement } from 'react';\nimport { DateInput, DateInputProps } from '@mantine/dates';\n\nimport { useKitMessages } from '../../../provider/useKit';\nimport { parseDate } from '../../../util/DateUtil';\n\nexport interface DateProps extends DateInputProps {\n label: string;\n}\n\n/** Clearable date input in the date format of the locale (e.g. `DD/MM/YYYY` in `pt-BR`). */\nexport function DateField({ label, value, ...rest }: DateProps): ReactElement {\n const { dateFormat } = useKitMessages().common;\n\n return (\n <DateInput\n label={label}\n placeholder={dateFormat}\n valueFormat={dateFormat}\n value={parseDate(value, dateFormat)}\n clearable\n {...rest}\n />\n );\n}\n"],"mappings":";;;;;;;AAWA,SAAgB,UAAU,EAAE,OAAO,OAAO,GAAG,QAAiC;CAC1E,MAAM,EAAE,eAAe,eAAe,CAAC,CAAC;CAExC,OACI,oBAAC,WAAD;EACW;EACP,aAAa;EACb,aAAa;EACb,OAAO,UAAU,OAAO,UAAU;EAClC,WAAA;EACA,GAAI;CACP,CAAA;AAET"}
1
+ {"version":3,"file":"DateField.js","names":[],"sources":["../../../../src/component/atom/DateField/DateField.tsx"],"sourcesContent":["import { ReactElement } from 'react';\nimport { DateInput, DateInputProps } from '@mantine/dates';\n\nimport { useKitMessages } from '../../../provider/useKit';\nimport { DateFieldValue, ISO_DATE_FORMAT, isValidDate, parseDate } from '../../../util/DateUtil';\n\nexport interface DateProps extends DateInputProps {\n label: string;\n}\n\n/**\n * Reads the value of the field, which is a `YYYY-MM-DD` text when it comes from the `onChange` of `DateInput`.\n * @param format format of the locale, for the texts not in the ISO format\n */\nfunction parseFieldValue(value: DateFieldValue, format: string): Date | null {\n const isoDate = parseDate(value, ISO_DATE_FORMAT);\n\n return isValidDate(isoDate) ? isoDate : parseDate(value, format);\n}\n\n/**\n * Clearable date input in the date format of the locale (e.g. `DD/MM/YYYY` in `pt-BR`).\n * A text `value` can be in that format or in the `YYYY-MM-DD` format `onChange` gives, so it can be controlled with\n * what `onChange` gives.\n */\nexport function DateField({ label, value, ...rest }: DateProps): ReactElement {\n const { dateFormat } = useKitMessages().common;\n\n return (\n <DateInput\n label={label}\n placeholder={dateFormat}\n valueFormat={dateFormat}\n value={parseFieldValue(value, dateFormat)}\n clearable\n {...rest}\n />\n );\n}\n"],"mappings":";;;;;;;;;;AAcA,SAAS,gBAAgB,OAAuB,QAA6B;CACzE,MAAM,UAAU,UAAU,OAAO,eAAe;CAEhD,OAAO,YAAY,OAAO,IAAI,UAAU,UAAU,OAAO,MAAM;AACnE;;;;;;AAOA,SAAgB,UAAU,EAAE,OAAO,OAAO,GAAG,QAAiC;CAC1E,MAAM,EAAE,eAAe,eAAe,CAAC,CAAC;CAExC,OACI,oBAAC,WAAD;EACW;EACP,aAAa;EACb,aAAa;EACb,OAAO,gBAAgB,OAAO,UAAU;EACxC,WAAA;EACA,GAAI;CACP,CAAA;AAET"}
@@ -1,4 +1,8 @@
1
+ /** American English locale. */
2
+ export declare const LOCALE_EN_US = "en-US";
3
+ /** Brazilian Portuguese locale. */
4
+ export declare const LOCALE_PT_BR = "pt-BR";
1
5
  /** Locales with a built-in message catalog. */
2
- export type KitLocale = 'pt-BR' | 'en-US';
6
+ export type KitLocale = typeof LOCALE_PT_BR | typeof LOCALE_EN_US;
3
7
  /** Locale used when the app does not choose one. */
4
8
  export declare const DEFAULT_LOCALE: KitLocale;
@@ -1,7 +1,11 @@
1
1
  "use client";
2
2
  // src/i18n/KitLocale.ts
3
+ /** American English locale. */
4
+ var LOCALE_EN_US = "en-US";
5
+ /** Brazilian Portuguese locale. */
6
+ var LOCALE_PT_BR = "pt-BR";
3
7
  /** Locale used when the app does not choose one. */
4
- var DEFAULT_LOCALE = "en-US";
5
- export { DEFAULT_LOCALE };
8
+ var DEFAULT_LOCALE = LOCALE_EN_US;
9
+ export { DEFAULT_LOCALE, LOCALE_EN_US, LOCALE_PT_BR };
6
10
 
7
11
  //# sourceMappingURL=KitLocale.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"KitLocale.js","names":[],"sources":["../../src/i18n/KitLocale.ts"],"sourcesContent":["/** Locales with a built-in message catalog. */\nexport type KitLocale = 'pt-BR' | 'en-US';\n\n/** Locale used when the app does not choose one. */\nexport const DEFAULT_LOCALE: KitLocale = 'en-US';\n"],"mappings":";;;AAIA,IAAa,iBAA4B"}
1
+ {"version":3,"file":"KitLocale.js","names":[],"sources":["../../src/i18n/KitLocale.ts"],"sourcesContent":["/** American English locale. */\nexport const LOCALE_EN_US = 'en-US';\n\n/** Brazilian Portuguese locale. */\nexport const LOCALE_PT_BR = 'pt-BR';\n\n/** Locales with a built-in message catalog. */\nexport type KitLocale = typeof LOCALE_PT_BR | typeof LOCALE_EN_US;\n\n/** Locale used when the app does not choose one. */\nexport const DEFAULT_LOCALE: KitLocale = LOCALE_EN_US;\n"],"mappings":";;;AACA,IAAa,eAAe;;AAG5B,IAAa,eAAe;;AAM5B,IAAa,iBAA4B"}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
- import dayjs from "dayjs";
2
+ import { formatLocaleDate, formatLocaleTime } from "../../util/DateUtil.js";
3
+ import { LOCALE_EN_US } from "../KitLocale.js";
3
4
  // src/i18n/messages/enUS.ts
4
5
  /** American English texts, the default locale. */
5
6
  var enUS = {
@@ -69,7 +70,7 @@ var enUS = {
69
70
  export: {
70
71
  button: "Export",
71
72
  failed: (formatLabel) => `Could not export to ${formatLabel}.`,
72
- generatedAt: (date) => `Generated on ${dayjs(date).format("MM/DD/YYYY [at] h:mm A")}`,
73
+ generatedAt: (date) => `Generated on ${formatLocaleDate(date, LOCALE_EN_US)} at ${formatLocaleTime(date, LOCALE_EN_US)}`,
73
74
  footer: (systemName) => `Data generated by ${systemName}`,
74
75
  excelPageNumber: "Page &P of &N",
75
76
  invalidCell: "Error",
@@ -1 +1 @@
1
- {"version":3,"file":"enUS.js","names":[],"sources":["../../../src/i18n/messages/enUS.ts"],"sourcesContent":["import dayjs from 'dayjs';\n\nimport { KitMessages } from '../KitMessages';\n\n/** American English texts, the default locale. */\nexport const enUS: KitMessages = {\n common: {\n back: 'Back',\n save: 'Save',\n cancel: 'Cancel',\n confirm: 'Confirm',\n add: 'Add',\n home: 'Home',\n success: 'Success',\n warning: 'Warning',\n error: 'Error',\n loadError: 'Error loading data',\n dateFormat: 'MM/DD/YYYY'\n },\n form: {\n action: { create: 'Create', edit: 'Update', view: 'View', custom: 'Customize' },\n saveSuccess: action => `Record ${pastTense(action)} successfully!`,\n saveError: action => `Error trying to ${action.toLowerCase()} the record`,\n createSuccess: 'Record created successfully',\n createError: 'There was an error creating the record!',\n updateSuccess: 'Record updated successfully',\n updateError: 'There was an error updating the record',\n confirmTitle: 'Confirm your action'\n },\n table: {\n actions: 'Actions',\n view: 'View',\n edit: 'Edit',\n delete: 'Delete',\n deleteConfirm: 'You are about to delete this record. Are you sure you want to continue?',\n deleteSuccess: 'Record deleted successfully!',\n noRecords: 'There are no records to display.',\n recordsPerPage: 'Records per page',\n pagination: ({ from, to, totalRecords }) => `Showing ${from} to ${to} of ${totalRecords} records`,\n listTitle: pluralName => `${pluralName} list`\n },\n rowEdit: {\n edit: 'Edit values (Enter)',\n confirm: 'Confirm editing (Enter)',\n cancel: 'Cancel editing (Esc)'\n },\n select: {\n placeholder: label => `Select the ${label}`,\n nothingFound: label => `${label} not found`\n },\n layout: {\n userMenu: 'Open user options',\n userMenuTitle: 'User Options',\n enableDarkTheme: 'Enable dark theme',\n enableLightTheme: 'Enable light theme',\n collapseNavigation: 'Collapse navigation menu',\n expandNavigation: 'Expand navigation menu'\n },\n print: {\n pdfTipTitle: 'To save as PDF',\n pdfTip:\n 'In the print dialog, choose \"Save as PDF\" as the destination and uncheck \"Headers and footers\", so ' +\n 'the PDF has only the ones of the report.',\n printButton: 'Print or save as PDF'\n },\n stats: {\n comparedToLastMonth: 'Compared to last month'\n },\n export: {\n button: 'Export',\n failed: formatLabel => `Could not export to ${formatLabel}.`,\n generatedAt: date => `Generated on ${dayjs(date).format('MM/DD/YYYY [at] h:mm A')}`,\n footer: systemName => `Data generated by ${systemName}`,\n excelPageNumber: 'Page &P of &N',\n invalidCell: 'Error',\n defaultFileName: 'data',\n defaultSheetName: position => `Sheet ${position}`\n },\n chart: {\n types: { bar: 'Columns', line: 'Line', 'stacked-bar': 'Stacked columns', donut: 'Donut (one year)' },\n saveAsImage: 'Save as image',\n total: formattedTotal => `Total: ${formattedTotal}`\n },\n http: {\n unknownError: 'Unknown error',\n noResponse: 'No response received',\n requestFailed: 'Request failed'\n }\n};\n\n/** Past tense of the form action verbs (e.g. \"Create\" → \"created\"), used in the success message. */\nconst PAST_TENSE: Record<string, string> = { create: 'created', update: 'updated', view: 'viewed' };\n\n/**\n * Returns the past tense of a form action verb.\n * @param action verb, as in `form.action`\n * @return the past tense, in lower case; for an unknown verb, the verb itself followed by \"d\"\n */\nfunction pastTense(action: string): string {\n const verb = action.toLowerCase();\n\n return PAST_TENSE[verb] ?? `${verb}d`;\n}\n"],"mappings":";;;;AAKA,IAAa,OAAoB;CAC7B,QAAQ;EACJ,MAAM;EACN,MAAM;EACN,QAAQ;EACR,SAAS;EACT,KAAK;EACL,MAAM;EACN,SAAS;EACT,SAAS;EACT,OAAO;EACP,WAAW;EACX,YAAY;CAChB;CACA,MAAM;EACF,QAAQ;GAAE,QAAQ;GAAU,MAAM;GAAU,MAAM;GAAQ,QAAQ;EAAY;EAC9E,cAAa,WAAU,UAAU,UAAU,MAAM,EAAE;EACnD,YAAW,WAAU,mBAAmB,OAAO,YAAY,EAAE;EAC7D,eAAe;EACf,aAAa;EACb,eAAe;EACf,aAAa;EACb,cAAc;CAClB;CACA,OAAO;EACH,SAAS;EACT,MAAM;EACN,MAAM;EACN,QAAQ;EACR,eAAe;EACf,eAAe;EACf,WAAW;EACX,gBAAgB;EAChB,aAAa,EAAE,MAAM,IAAI,mBAAmB,WAAW,KAAK,MAAM,GAAG,MAAM,aAAa;EACxF,YAAW,eAAc,GAAG,WAAW;CAC3C;CACA,SAAS;EACL,MAAM;EACN,SAAS;EACT,QAAQ;CACZ;CACA,QAAQ;EACJ,cAAa,UAAS,cAAc;EACpC,eAAc,UAAS,GAAG,MAAM;CACpC;CACA,QAAQ;EACJ,UAAU;EACV,eAAe;EACf,iBAAiB;EACjB,kBAAkB;EAClB,oBAAoB;EACpB,kBAAkB;CACtB;CACA,OAAO;EACH,aAAa;EACb,QACI;EAEJ,aAAa;CACjB;CACA,OAAO,EACH,qBAAqB,yBACzB;CACA,QAAQ;EACJ,QAAQ;EACR,SAAQ,gBAAe,uBAAuB,YAAY;EAC1D,cAAa,SAAQ,gBAAgB,MAAM,IAAI,CAAC,CAAC,OAAO,wBAAwB;EAChF,SAAQ,eAAc,qBAAqB;EAC3C,iBAAiB;EACjB,aAAa;EACb,iBAAiB;EACjB,mBAAkB,aAAY,SAAS;CAC3C;CACA,OAAO;EACH,OAAO;GAAE,KAAK;GAAW,MAAM;GAAQ,eAAe;GAAmB,OAAO;EAAmB;EACnG,aAAa;EACb,QAAO,mBAAkB,UAAU;CACvC;CACA,MAAM;EACF,cAAc;EACd,YAAY;EACZ,eAAe;CACnB;AACJ;;AAGA,IAAM,aAAqC;CAAE,QAAQ;CAAW,QAAQ;CAAW,MAAM;AAAS;;;;;;AAOlG,SAAS,UAAU,QAAwB;CACvC,MAAM,OAAO,OAAO,YAAY;CAEhC,OAAO,WAAW,SAAS,GAAG,KAAK;AACvC"}
1
+ {"version":3,"file":"enUS.js","names":[],"sources":["../../../src/i18n/messages/enUS.ts"],"sourcesContent":["import { formatLocaleDate, formatLocaleTime } from '../../util/DateUtil';\nimport { LOCALE_EN_US } from '../KitLocale';\nimport { KitMessages } from '../KitMessages';\n\n/** American English texts, the default locale. */\nexport const enUS: KitMessages = {\n common: {\n back: 'Back',\n save: 'Save',\n cancel: 'Cancel',\n confirm: 'Confirm',\n add: 'Add',\n home: 'Home',\n success: 'Success',\n warning: 'Warning',\n error: 'Error',\n loadError: 'Error loading data',\n dateFormat: 'MM/DD/YYYY'\n },\n form: {\n action: { create: 'Create', edit: 'Update', view: 'View', custom: 'Customize' },\n saveSuccess: action => `Record ${pastTense(action)} successfully!`,\n saveError: action => `Error trying to ${action.toLowerCase()} the record`,\n createSuccess: 'Record created successfully',\n createError: 'There was an error creating the record!',\n updateSuccess: 'Record updated successfully',\n updateError: 'There was an error updating the record',\n confirmTitle: 'Confirm your action'\n },\n table: {\n actions: 'Actions',\n view: 'View',\n edit: 'Edit',\n delete: 'Delete',\n deleteConfirm: 'You are about to delete this record. Are you sure you want to continue?',\n deleteSuccess: 'Record deleted successfully!',\n noRecords: 'There are no records to display.',\n recordsPerPage: 'Records per page',\n pagination: ({ from, to, totalRecords }) => `Showing ${from} to ${to} of ${totalRecords} records`,\n listTitle: pluralName => `${pluralName} list`\n },\n rowEdit: {\n edit: 'Edit values (Enter)',\n confirm: 'Confirm editing (Enter)',\n cancel: 'Cancel editing (Esc)'\n },\n select: {\n placeholder: label => `Select the ${label}`,\n nothingFound: label => `${label} not found`\n },\n layout: {\n userMenu: 'Open user options',\n userMenuTitle: 'User Options',\n enableDarkTheme: 'Enable dark theme',\n enableLightTheme: 'Enable light theme',\n collapseNavigation: 'Collapse navigation menu',\n expandNavigation: 'Expand navigation menu'\n },\n print: {\n pdfTipTitle: 'To save as PDF',\n pdfTip:\n 'In the print dialog, choose \"Save as PDF\" as the destination and uncheck \"Headers and footers\", so ' +\n 'the PDF has only the ones of the report.',\n printButton: 'Print or save as PDF'\n },\n stats: {\n comparedToLastMonth: 'Compared to last month'\n },\n export: {\n button: 'Export',\n failed: formatLabel => `Could not export to ${formatLabel}.`,\n generatedAt: date =>\n `Generated on ${formatLocaleDate(date, LOCALE_EN_US)} at ${formatLocaleTime(date, LOCALE_EN_US)}`,\n footer: systemName => `Data generated by ${systemName}`,\n excelPageNumber: 'Page &P of &N',\n invalidCell: 'Error',\n defaultFileName: 'data',\n defaultSheetName: position => `Sheet ${position}`\n },\n chart: {\n types: { bar: 'Columns', line: 'Line', 'stacked-bar': 'Stacked columns', donut: 'Donut (one year)' },\n saveAsImage: 'Save as image',\n total: formattedTotal => `Total: ${formattedTotal}`\n },\n http: {\n unknownError: 'Unknown error',\n noResponse: 'No response received',\n requestFailed: 'Request failed'\n }\n};\n\n/** Past tense of the form action verbs (e.g. \"Create\" → \"created\"), used in the success message. */\nconst PAST_TENSE: Record<string, string> = { create: 'created', update: 'updated', view: 'viewed' };\n\n/**\n * Returns the past tense of a form action verb.\n * @param action verb, as in `form.action`\n * @return the past tense, in lower case; for an unknown verb, the verb itself followed by \"d\"\n */\nfunction pastTense(action: string): string {\n const verb = action.toLowerCase();\n\n return PAST_TENSE[verb] ?? `${verb}d`;\n}\n"],"mappings":";;;;;AAKA,IAAa,OAAoB;CAC7B,QAAQ;EACJ,MAAM;EACN,MAAM;EACN,QAAQ;EACR,SAAS;EACT,KAAK;EACL,MAAM;EACN,SAAS;EACT,SAAS;EACT,OAAO;EACP,WAAW;EACX,YAAY;CAChB;CACA,MAAM;EACF,QAAQ;GAAE,QAAQ;GAAU,MAAM;GAAU,MAAM;GAAQ,QAAQ;EAAY;EAC9E,cAAa,WAAU,UAAU,UAAU,MAAM,EAAE;EACnD,YAAW,WAAU,mBAAmB,OAAO,YAAY,EAAE;EAC7D,eAAe;EACf,aAAa;EACb,eAAe;EACf,aAAa;EACb,cAAc;CAClB;CACA,OAAO;EACH,SAAS;EACT,MAAM;EACN,MAAM;EACN,QAAQ;EACR,eAAe;EACf,eAAe;EACf,WAAW;EACX,gBAAgB;EAChB,aAAa,EAAE,MAAM,IAAI,mBAAmB,WAAW,KAAK,MAAM,GAAG,MAAM,aAAa;EACxF,YAAW,eAAc,GAAG,WAAW;CAC3C;CACA,SAAS;EACL,MAAM;EACN,SAAS;EACT,QAAQ;CACZ;CACA,QAAQ;EACJ,cAAa,UAAS,cAAc;EACpC,eAAc,UAAS,GAAG,MAAM;CACpC;CACA,QAAQ;EACJ,UAAU;EACV,eAAe;EACf,iBAAiB;EACjB,kBAAkB;EAClB,oBAAoB;EACpB,kBAAkB;CACtB;CACA,OAAO;EACH,aAAa;EACb,QACI;EAEJ,aAAa;CACjB;CACA,OAAO,EACH,qBAAqB,yBACzB;CACA,QAAQ;EACJ,QAAQ;EACR,SAAQ,gBAAe,uBAAuB,YAAY;EAC1D,cAAa,SACT,gBAAgB,iBAAiB,MAAM,YAAY,EAAE,MAAM,iBAAiB,MAAM,YAAY;EAClG,SAAQ,eAAc,qBAAqB;EAC3C,iBAAiB;EACjB,aAAa;EACb,iBAAiB;EACjB,mBAAkB,aAAY,SAAS;CAC3C;CACA,OAAO;EACH,OAAO;GAAE,KAAK;GAAW,MAAM;GAAQ,eAAe;GAAmB,OAAO;EAAmB;EACnG,aAAa;EACb,QAAO,mBAAkB,UAAU;CACvC;CACA,MAAM;EACF,cAAc;EACd,YAAY;EACZ,eAAe;CACnB;AACJ;;AAGA,IAAM,aAAqC;CAAE,QAAQ;CAAW,QAAQ;CAAW,MAAM;AAAS;;;;;;AAOlG,SAAS,UAAU,QAAwB;CACvC,MAAM,OAAO,OAAO,YAAY;CAEhC,OAAO,WAAW,SAAS,GAAG,KAAK;AACvC"}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
- import dayjs from "dayjs";
2
+ import { formatLocaleDate, formatLocaleTime } from "../../util/DateUtil.js";
3
+ import { LOCALE_PT_BR } from "../KitLocale.js";
3
4
  // src/i18n/messages/ptBR.ts
4
5
  /** Brazilian Portuguese texts. */
5
6
  var ptBR = {
@@ -69,7 +70,7 @@ var ptBR = {
69
70
  export: {
70
71
  button: "Exportar",
71
72
  failed: (formatLabel) => `Não foi possível exportar em ${formatLabel}.`,
72
- generatedAt: (date) => `Gerado em ${dayjs(date).format("DD/MM/YYYY [às] HH:mm")}`,
73
+ generatedAt: (date) => `Gerado em ${formatLocaleDate(date, LOCALE_PT_BR)} às ${formatLocaleTime(date, LOCALE_PT_BR)}`,
73
74
  footer: (systemName) => `Dados gerados pelo ${systemName}`,
74
75
  excelPageNumber: "Página &P de &N",
75
76
  invalidCell: "Erro",
@@ -1 +1 @@
1
- {"version":3,"file":"ptBR.js","names":[],"sources":["../../../src/i18n/messages/ptBR.ts"],"sourcesContent":["import dayjs from 'dayjs';\n\nimport { KitMessages } from '../KitMessages';\n\n/** Brazilian Portuguese texts. */\nexport const ptBR: KitMessages = {\n common: {\n back: 'Voltar',\n save: 'Salvar',\n cancel: 'Cancelar',\n confirm: 'Confirmar',\n add: 'Adicionar',\n home: 'Início',\n success: 'Sucesso',\n warning: 'Atenção',\n error: 'Erro',\n loadError: 'Erro ao carregar dados',\n dateFormat: 'DD/MM/YYYY'\n },\n form: {\n action: { create: 'Criar', edit: 'Atualizar', view: 'Visualizar', custom: 'Personalizar' },\n saveSuccess: action => `Sucesso ao ${action.toLowerCase()} cadastro!`,\n saveError: action => `Erro ao ${action.toLowerCase()} cadastro`,\n createSuccess: 'Registro cadastrado com sucesso',\n createError: 'Houve um erro ao realizar o cadastro!',\n updateSuccess: 'Cadastro atualizado com sucesso',\n updateError: 'Houve um erro ao atualizar o cadastro',\n confirmTitle: 'Confirme sua ação'\n },\n table: {\n actions: 'Ações',\n view: 'Visualizar',\n edit: 'Editar',\n delete: 'Deletar',\n deleteConfirm: 'Você está prestes a excluir este cadastro. Tem certeza de que deseja continuar?',\n deleteSuccess: 'Cadastro excluído com sucesso!',\n noRecords: 'Não há registros disponíveis para exibição.',\n recordsPerPage: 'Registros por página',\n pagination: ({ from, to, totalRecords }) => `Exibindo ${from} a ${to} de ${totalRecords} registros`,\n listTitle: pluralName => `Lista de ${pluralName}`\n },\n rowEdit: {\n edit: 'Editar valores (Enter)',\n confirm: 'Confirmar edição (Enter)',\n cancel: 'Cancelar edição (Esc)'\n },\n select: {\n placeholder: label => `Selecione o(a) ${label}`,\n nothingFound: label => `${label} não encontrado(a)`\n },\n layout: {\n userMenu: 'Acessar opções do usuário',\n userMenuTitle: 'Opções do Usuário',\n enableDarkTheme: 'Ativar tema escuro',\n enableLightTheme: 'Ativar tema claro',\n collapseNavigation: 'Recolher menu de navegação',\n expandNavigation: 'Expandir menu de navegação'\n },\n print: {\n pdfTipTitle: 'Para salvar em PDF',\n pdfTip:\n 'Na janela de impressão, escolha \"Salvar como PDF\" como destino e desmarque \"Cabeçalhos e rodapés\", ' +\n 'para o PDF ter só os do relatório.',\n printButton: 'Imprimir ou salvar como PDF'\n },\n stats: {\n comparedToLastMonth: 'Comparado ao último mês'\n },\n export: {\n button: 'Exportar',\n failed: formatLabel => `Não foi possível exportar em ${formatLabel}.`,\n generatedAt: date => `Gerado em ${dayjs(date).format('DD/MM/YYYY [às] HH:mm')}`,\n footer: systemName => `Dados gerados pelo ${systemName}`,\n excelPageNumber: 'Página &P de &N',\n invalidCell: 'Erro',\n defaultFileName: 'dados',\n defaultSheetName: position => `Aba ${position}`\n },\n chart: {\n types: { bar: 'Colunas', line: 'Linha', 'stacked-bar': 'Colunas empilhadas', donut: 'Rosca (um ano)' },\n saveAsImage: 'Exportar imagem',\n total: formattedTotal => `Total: ${formattedTotal}`\n },\n http: {\n unknownError: 'Erro desconhecido',\n noResponse: 'Nenhuma resposta recebida',\n requestFailed: 'Requisição falhou'\n }\n};\n"],"mappings":";;;;AAKA,IAAa,OAAoB;CAC7B,QAAQ;EACJ,MAAM;EACN,MAAM;EACN,QAAQ;EACR,SAAS;EACT,KAAK;EACL,MAAM;EACN,SAAS;EACT,SAAS;EACT,OAAO;EACP,WAAW;EACX,YAAY;CAChB;CACA,MAAM;EACF,QAAQ;GAAE,QAAQ;GAAS,MAAM;GAAa,MAAM;GAAc,QAAQ;EAAe;EACzF,cAAa,WAAU,cAAc,OAAO,YAAY,EAAE;EAC1D,YAAW,WAAU,WAAW,OAAO,YAAY,EAAE;EACrD,eAAe;EACf,aAAa;EACb,eAAe;EACf,aAAa;EACb,cAAc;CAClB;CACA,OAAO;EACH,SAAS;EACT,MAAM;EACN,MAAM;EACN,QAAQ;EACR,eAAe;EACf,eAAe;EACf,WAAW;EACX,gBAAgB;EAChB,aAAa,EAAE,MAAM,IAAI,mBAAmB,YAAY,KAAK,KAAK,GAAG,MAAM,aAAa;EACxF,YAAW,eAAc,YAAY;CACzC;CACA,SAAS;EACL,MAAM;EACN,SAAS;EACT,QAAQ;CACZ;CACA,QAAQ;EACJ,cAAa,UAAS,kBAAkB;EACxC,eAAc,UAAS,GAAG,MAAM;CACpC;CACA,QAAQ;EACJ,UAAU;EACV,eAAe;EACf,iBAAiB;EACjB,kBAAkB;EAClB,oBAAoB;EACpB,kBAAkB;CACtB;CACA,OAAO;EACH,aAAa;EACb,QACI;EAEJ,aAAa;CACjB;CACA,OAAO,EACH,qBAAqB,0BACzB;CACA,QAAQ;EACJ,QAAQ;EACR,SAAQ,gBAAe,gCAAgC,YAAY;EACnE,cAAa,SAAQ,aAAa,MAAM,IAAI,CAAC,CAAC,OAAO,uBAAuB;EAC5E,SAAQ,eAAc,sBAAsB;EAC5C,iBAAiB;EACjB,aAAa;EACb,iBAAiB;EACjB,mBAAkB,aAAY,OAAO;CACzC;CACA,OAAO;EACH,OAAO;GAAE,KAAK;GAAW,MAAM;GAAS,eAAe;GAAsB,OAAO;EAAiB;EACrG,aAAa;EACb,QAAO,mBAAkB,UAAU;CACvC;CACA,MAAM;EACF,cAAc;EACd,YAAY;EACZ,eAAe;CACnB;AACJ"}
1
+ {"version":3,"file":"ptBR.js","names":[],"sources":["../../../src/i18n/messages/ptBR.ts"],"sourcesContent":["import { formatLocaleDate, formatLocaleTime } from '../../util/DateUtil';\nimport { LOCALE_PT_BR } from '../KitLocale';\nimport { KitMessages } from '../KitMessages';\n\n/** Brazilian Portuguese texts. */\nexport const ptBR: KitMessages = {\n common: {\n back: 'Voltar',\n save: 'Salvar',\n cancel: 'Cancelar',\n confirm: 'Confirmar',\n add: 'Adicionar',\n home: 'Início',\n success: 'Sucesso',\n warning: 'Atenção',\n error: 'Erro',\n loadError: 'Erro ao carregar dados',\n dateFormat: 'DD/MM/YYYY'\n },\n form: {\n action: { create: 'Criar', edit: 'Atualizar', view: 'Visualizar', custom: 'Personalizar' },\n saveSuccess: action => `Sucesso ao ${action.toLowerCase()} cadastro!`,\n saveError: action => `Erro ao ${action.toLowerCase()} cadastro`,\n createSuccess: 'Registro cadastrado com sucesso',\n createError: 'Houve um erro ao realizar o cadastro!',\n updateSuccess: 'Cadastro atualizado com sucesso',\n updateError: 'Houve um erro ao atualizar o cadastro',\n confirmTitle: 'Confirme sua ação'\n },\n table: {\n actions: 'Ações',\n view: 'Visualizar',\n edit: 'Editar',\n delete: 'Deletar',\n deleteConfirm: 'Você está prestes a excluir este cadastro. Tem certeza de que deseja continuar?',\n deleteSuccess: 'Cadastro excluído com sucesso!',\n noRecords: 'Não há registros disponíveis para exibição.',\n recordsPerPage: 'Registros por página',\n pagination: ({ from, to, totalRecords }) => `Exibindo ${from} a ${to} de ${totalRecords} registros`,\n listTitle: pluralName => `Lista de ${pluralName}`\n },\n rowEdit: {\n edit: 'Editar valores (Enter)',\n confirm: 'Confirmar edição (Enter)',\n cancel: 'Cancelar edição (Esc)'\n },\n select: {\n placeholder: label => `Selecione o(a) ${label}`,\n nothingFound: label => `${label} não encontrado(a)`\n },\n layout: {\n userMenu: 'Acessar opções do usuário',\n userMenuTitle: 'Opções do Usuário',\n enableDarkTheme: 'Ativar tema escuro',\n enableLightTheme: 'Ativar tema claro',\n collapseNavigation: 'Recolher menu de navegação',\n expandNavigation: 'Expandir menu de navegação'\n },\n print: {\n pdfTipTitle: 'Para salvar em PDF',\n pdfTip:\n 'Na janela de impressão, escolha \"Salvar como PDF\" como destino e desmarque \"Cabeçalhos e rodapés\", ' +\n 'para o PDF ter só os do relatório.',\n printButton: 'Imprimir ou salvar como PDF'\n },\n stats: {\n comparedToLastMonth: 'Comparado ao último mês'\n },\n export: {\n button: 'Exportar',\n failed: formatLabel => `Não foi possível exportar em ${formatLabel}.`,\n generatedAt: date =>\n `Gerado em ${formatLocaleDate(date, LOCALE_PT_BR)} às ${formatLocaleTime(date, LOCALE_PT_BR)}`,\n footer: systemName => `Dados gerados pelo ${systemName}`,\n excelPageNumber: 'Página &P de &N',\n invalidCell: 'Erro',\n defaultFileName: 'dados',\n defaultSheetName: position => `Aba ${position}`\n },\n chart: {\n types: { bar: 'Colunas', line: 'Linha', 'stacked-bar': 'Colunas empilhadas', donut: 'Rosca (um ano)' },\n saveAsImage: 'Exportar imagem',\n total: formattedTotal => `Total: ${formattedTotal}`\n },\n http: {\n unknownError: 'Erro desconhecido',\n noResponse: 'Nenhuma resposta recebida',\n requestFailed: 'Requisição falhou'\n }\n};\n"],"mappings":";;;;;AAKA,IAAa,OAAoB;CAC7B,QAAQ;EACJ,MAAM;EACN,MAAM;EACN,QAAQ;EACR,SAAS;EACT,KAAK;EACL,MAAM;EACN,SAAS;EACT,SAAS;EACT,OAAO;EACP,WAAW;EACX,YAAY;CAChB;CACA,MAAM;EACF,QAAQ;GAAE,QAAQ;GAAS,MAAM;GAAa,MAAM;GAAc,QAAQ;EAAe;EACzF,cAAa,WAAU,cAAc,OAAO,YAAY,EAAE;EAC1D,YAAW,WAAU,WAAW,OAAO,YAAY,EAAE;EACrD,eAAe;EACf,aAAa;EACb,eAAe;EACf,aAAa;EACb,cAAc;CAClB;CACA,OAAO;EACH,SAAS;EACT,MAAM;EACN,MAAM;EACN,QAAQ;EACR,eAAe;EACf,eAAe;EACf,WAAW;EACX,gBAAgB;EAChB,aAAa,EAAE,MAAM,IAAI,mBAAmB,YAAY,KAAK,KAAK,GAAG,MAAM,aAAa;EACxF,YAAW,eAAc,YAAY;CACzC;CACA,SAAS;EACL,MAAM;EACN,SAAS;EACT,QAAQ;CACZ;CACA,QAAQ;EACJ,cAAa,UAAS,kBAAkB;EACxC,eAAc,UAAS,GAAG,MAAM;CACpC;CACA,QAAQ;EACJ,UAAU;EACV,eAAe;EACf,iBAAiB;EACjB,kBAAkB;EAClB,oBAAoB;EACpB,kBAAkB;CACtB;CACA,OAAO;EACH,aAAa;EACb,QACI;EAEJ,aAAa;CACjB;CACA,OAAO,EACH,qBAAqB,0BACzB;CACA,QAAQ;EACJ,QAAQ;EACR,SAAQ,gBAAe,gCAAgC,YAAY;EACnE,cAAa,SACT,aAAa,iBAAiB,MAAM,YAAY,EAAE,MAAM,iBAAiB,MAAM,YAAY;EAC/F,SAAQ,eAAc,sBAAsB;EAC5C,iBAAiB;EACjB,aAAa;EACb,iBAAiB;EACjB,mBAAkB,aAAY,OAAO;CACzC;CACA,OAAO;EACH,OAAO;GAAE,KAAK;GAAW,MAAM;GAAS,eAAe;GAAsB,OAAO;EAAiB;EACrG,aAAa;EACb,QAAO,mBAAkB,UAAU;CACvC;CACA,MAAM;EACF,cAAc;EACd,YAAY;EACZ,eAAe;CACnB;AACJ"}
@@ -1,12 +1,12 @@
1
1
  "use client";
2
- import { DEFAULT_LOCALE } from "./KitLocale.js";
2
+ import { DEFAULT_LOCALE, LOCALE_EN_US, LOCALE_PT_BR } from "./KitLocale.js";
3
3
  import { enUS } from "./messages/enUS.js";
4
4
  import { ptBR } from "./messages/ptBR.js";
5
5
  // src/i18n/resolveMessages.ts
6
6
  /** Built-in message catalog of each locale. */
7
7
  var KIT_MESSAGES = {
8
- "pt-BR": ptBR,
9
- "en-US": enUS
8
+ [LOCALE_PT_BR]: ptBR,
9
+ [LOCALE_EN_US]: enUS
10
10
  };
11
11
  /** Texts of the {@link DEFAULT_LOCALE}, used as defaults by the code outside React. */
12
12
  var DEFAULT_MESSAGES = KIT_MESSAGES[DEFAULT_LOCALE];
@@ -1 +1 @@
1
- {"version":3,"file":"resolveMessages.js","names":[],"sources":["../../src/i18n/resolveMessages.ts"],"sourcesContent":["import { DEFAULT_LOCALE, KitLocale } from './KitLocale';\nimport { KitMessages, KitMessagesOverride } from './KitMessages';\nimport { enUS } from './messages/enUS';\nimport { ptBR } from './messages/ptBR';\n\n/** Built-in message catalog of each locale. */\nexport const KIT_MESSAGES: Readonly<Record<KitLocale, KitMessages>> = { 'pt-BR': ptBR, 'en-US': enUS };\n\n/** Texts of the {@link DEFAULT_LOCALE}, used as defaults by the code outside React. */\nexport const DEFAULT_MESSAGES: KitMessages = KIT_MESSAGES[DEFAULT_LOCALE];\n\n/**\n * Returns the texts of a locale, with the texts the app overrides.\n * @param locale locale of the texts; defaults to {@link DEFAULT_LOCALE}\n * @param override texts that replace the ones of the locale, by section\n * @return the texts, with every section complete\n */\nexport function resolveMessages(locale: KitLocale = DEFAULT_LOCALE, override: KitMessagesOverride = {}): KitMessages {\n const base = KIT_MESSAGES[locale];\n const sections = Object.keys(base) as (keyof KitMessages)[];\n\n return Object.fromEntries(\n sections.map(section => [section, { ...base[section], ...override[section] }])\n ) as KitMessages;\n}\n"],"mappings":";;;;;;AAMA,IAAa,eAAyD;CAAE,SAAS;CAAM,SAAS;AAAK;;AAGrG,IAAa,mBAAgC,aAAa;;;;;;;AAQ1D,SAAgB,gBAAgB,SAAoB,gBAAgB,WAAgC,CAAC,GAAgB;CACjH,MAAM,OAAO,aAAa;CAC1B,MAAM,WAAW,OAAO,KAAK,IAAI;CAEjC,OAAO,OAAO,YACV,SAAS,KAAI,YAAW,CAAC,SAAS;EAAE,GAAG,KAAK;EAAU,GAAG,SAAS;CAAS,CAAC,CAAC,CACjF;AACJ"}
1
+ {"version":3,"file":"resolveMessages.js","names":[],"sources":["../../src/i18n/resolveMessages.ts"],"sourcesContent":["import { DEFAULT_LOCALE, KitLocale, LOCALE_EN_US, LOCALE_PT_BR } from './KitLocale';\nimport { KitMessages, KitMessagesOverride } from './KitMessages';\nimport { enUS } from './messages/enUS';\nimport { ptBR } from './messages/ptBR';\n\n/** Built-in message catalog of each locale. */\nexport const KIT_MESSAGES: Readonly<Record<KitLocale, KitMessages>> = {\n [LOCALE_PT_BR]: ptBR,\n [LOCALE_EN_US]: enUS\n};\n\n/** Texts of the {@link DEFAULT_LOCALE}, used as defaults by the code outside React. */\nexport const DEFAULT_MESSAGES: KitMessages = KIT_MESSAGES[DEFAULT_LOCALE];\n\n/**\n * Returns the texts of a locale, with the texts the app overrides.\n * @param locale locale of the texts; defaults to {@link DEFAULT_LOCALE}\n * @param override texts that replace the ones of the locale, by section\n * @return the texts, with every section complete\n */\nexport function resolveMessages(locale: KitLocale = DEFAULT_LOCALE, override: KitMessagesOverride = {}): KitMessages {\n const base = KIT_MESSAGES[locale];\n const sections = Object.keys(base) as (keyof KitMessages)[];\n\n return Object.fromEntries(\n sections.map(section => [section, { ...base[section], ...override[section] }])\n ) as KitMessages;\n}\n"],"mappings":";;;;;;AAMA,IAAa,eAAyD;EACjE,eAAe;EACf,eAAe;AACpB;;AAGA,IAAa,mBAAgC,aAAa;;;;;;;AAQ1D,SAAgB,gBAAgB,SAAoB,gBAAgB,WAAgC,CAAC,GAAgB;CACjH,MAAM,OAAO,aAAa;CAC1B,MAAM,WAAW,OAAO,KAAK,IAAI;CAEjC,OAAO,OAAO,YACV,SAAS,KAAI,YAAW,CAAC,SAAS;EAAE,GAAG,KAAK;EAAU,GAAG,SAAS;CAAS,CAAC,CAAC,CACjF;AACJ"}
@@ -1,3 +1,4 @@
1
+ import { KitLocale } from '../i18n/KitLocale';
1
2
  /** Value of a date field: a `Date`, a text in the given format, or nothing. */
2
3
  export type DateFieldValue = Date | string | null | undefined;
3
4
  export declare const BR_DATE_FORMAT = "DD/MM/YYYY";
@@ -7,24 +8,40 @@ export declare const ISO_DATE_FORMAT = "YYYY-MM-DD";
7
8
  * `new Date(null)` (12/31/1969), which Zod's `coerce` produces when a date field is not filled and receives `null`.
8
9
  */
9
10
  export declare const MIN_VALID_DATE: Date;
11
+ /** @return whether there is a date and it is valid: `Intl` throws an error for an Invalid Date */
12
+ export declare function isValidDate(date: Date | undefined | null): date is Date;
10
13
  /**
11
- * Formats a date.
14
+ * Formats a date in the numeric format of the locale, with 2-digit day and month (e.g. `10/05/2026` in `en-US`).
15
+ * @param date a valid date, shown in the time zone of the runtime
16
+ * @param locale locale that defines the order of the fields
17
+ */
18
+ export declare function formatLocaleDate(date: Date, locale: KitLocale): string;
19
+ /**
20
+ * Formats a time, without seconds, in the format of the locale (e.g. `2:30 PM` in `en-US`, `14:30` in `pt-BR`).
21
+ * @param date a valid date, shown in the time zone of the runtime
22
+ * @param locale locale that defines the 12 or 24-hour clock
23
+ */
24
+ export declare function formatLocaleTime(date: Date, locale: KitLocale): string;
25
+ /**
26
+ * Formats a date in the numeric format of the locale.
12
27
  * @param date date to format
13
- * @param format format, in Day.js tokens; defaults to {@link BR_DATE_FORMAT}
14
- * @return the formatted date, or `null` if there is no date
28
+ * @param locale locale of the format; defaults to {@link DEFAULT_LOCALE}
29
+ * @return the formatted date, or `null` if there is no valid date
15
30
  */
16
- export declare function formatDate(date: Date | undefined | null, format?: string): string | null;
31
+ export declare function formatDate(date: Date | undefined | null, locale?: KitLocale): string | null;
17
32
  /**
18
- * Formats a date in the ISO 8601 format.
33
+ * Formats a date in the ISO 8601 format, in the time zone of the runtime.
19
34
  * @param date date to format
35
+ * @return the formatted date, or `null` if there is no valid date
20
36
  * @see #ISO_DATE_FORMAT
21
37
  */
22
38
  export declare function formatDateToISO(date: Date | undefined | null): string | null;
23
39
  /**
24
40
  * Converts a date in the given format to a `Date` object.
25
41
  * @param dateValue date to convert
26
- * @param format format of the date, in Day.js tokens; defaults to {@link BR_DATE_FORMAT}
27
- * @return the date, or `null` if there is no date
42
+ * @param format format of a text date, with the tokens `DD`, `MM` and `YYYY`; defaults to {@link BR_DATE_FORMAT}
43
+ * @return the date, `null` if there is no date, or an Invalid Date if the text is not a date in the format
44
+ * @throws Error if a text date is given with an unsupported format
28
45
  */
29
46
  export declare function parseDate(dateValue: DateFieldValue, format?: string): Date | null;
30
47
  /** @return the current year and month in the `YYYY-MM` format */
@@ -1,45 +1,111 @@
1
1
  "use client";
2
- import dayjs from "dayjs";
3
- import customParseFormat from "dayjs/plugin/customParseFormat.js";
2
+ import { DEFAULT_LOCALE } from "../i18n/KitLocale.js";
4
3
  // src/util/DateUtil.ts
5
- dayjs.extend(customParseFormat);
6
4
  var BR_DATE_FORMAT = "DD/MM/YYYY";
7
5
  var ISO_DATE_FORMAT = "YYYY-MM-DD";
6
+ /** Tokens of the formats {@link parseDate} reads, with the part of the date each one captures. */
7
+ var DATE_TOKEN_PATTERNS = {
8
+ YYYY: "(?<year>\\d{4})",
9
+ MM: "(?<month>\\d{2})",
10
+ DD: "(?<day>\\d{2})"
11
+ };
12
+ var DATE_TOKEN = /YYYY|MM|DD/g;
13
+ /** Length of the `YYYY-MM` prefix of an ISO date. */
14
+ var YEAR_MONTH_LENGTH = 7;
8
15
  /**
9
16
  * Minimum value for a date to be considered valid when Zod coerces it. It is later than the date created by
10
17
  * `new Date(null)` (12/31/1969), which Zod's `coerce` produces when a date field is not filled and receives `null`.
11
18
  */
12
19
  var MIN_VALID_DATE = /* @__PURE__ */ new Date("01/01/1970");
20
+ /** @return whether there is a date and it is valid: `Intl` throws an error for an Invalid Date */
21
+ function isValidDate(date) {
22
+ return !!date && !Number.isNaN(date.getTime());
23
+ }
24
+ /**
25
+ * Formats a date in the numeric format of the locale, with 2-digit day and month (e.g. `10/05/2026` in `en-US`).
26
+ * @param date a valid date, shown in the time zone of the runtime
27
+ * @param locale locale that defines the order of the fields
28
+ */
29
+ function formatLocaleDate(date, locale) {
30
+ return date.toLocaleDateString(locale, {
31
+ day: "2-digit",
32
+ month: "2-digit",
33
+ year: "numeric"
34
+ });
35
+ }
36
+ /**
37
+ * Formats a time, without seconds, in the format of the locale (e.g. `2:30 PM` in `en-US`, `14:30` in `pt-BR`).
38
+ * @param date a valid date, shown in the time zone of the runtime
39
+ * @param locale locale that defines the 12 or 24-hour clock
40
+ */
41
+ function formatLocaleTime(date, locale) {
42
+ return date.toLocaleTimeString(locale, { timeStyle: "short" });
43
+ }
13
44
  /**
14
- * Formats a date.
45
+ * Formats a date in the numeric format of the locale.
15
46
  * @param date date to format
16
- * @param format format, in Day.js tokens; defaults to {@link BR_DATE_FORMAT}
17
- * @return the formatted date, or `null` if there is no date
47
+ * @param locale locale of the format; defaults to {@link DEFAULT_LOCALE}
48
+ * @return the formatted date, or `null` if there is no valid date
18
49
  */
19
- function formatDate(date, format = BR_DATE_FORMAT) {
20
- return date ? dayjs(date).format(format) : null;
50
+ function formatDate(date, locale = DEFAULT_LOCALE) {
51
+ return isValidDate(date) ? formatLocaleDate(date, locale) : null;
52
+ }
53
+ /** @return the date in the {@link ISO_DATE_FORMAT}, in the time zone of the runtime */
54
+ function toLocalIsoText(date) {
55
+ return `${date.getFullYear().toString().padStart(4, "0")}-${(date.getMonth() + 1).toString().padStart(2, "0")}-${date.getDate().toString().padStart(2, "0")}`;
21
56
  }
22
57
  /**
23
- * Formats a date in the ISO 8601 format.
58
+ * Formats a date in the ISO 8601 format, in the time zone of the runtime.
24
59
  * @param date date to format
60
+ * @return the formatted date, or `null` if there is no valid date
25
61
  * @see #ISO_DATE_FORMAT
26
62
  */
27
63
  function formatDateToISO(date) {
28
- return date ? dayjs(date).format(ISO_DATE_FORMAT) : null;
64
+ return isValidDate(date) ? toLocalIsoText(date) : null;
65
+ }
66
+ /**
67
+ * Creates the regular expression that reads a date in the given format.
68
+ * @param format format with the tokens `DD`, `MM` and `YYYY`, once each, and no other letters
69
+ * @throws Error if the format has anything else, such as the `MMM` or `YY` tokens of Mantine's `valueFormat`
70
+ */
71
+ function dateFormatRegex(format) {
72
+ const tokens = format.match(DATE_TOKEN) ?? [];
73
+ const onlyKnownTokens = !/[a-z]/i.test(format.replace(DATE_TOKEN, ""));
74
+ if (tokens.length !== 3 || new Set(tokens).size !== 3 || !onlyKnownTokens) throw new Error(`Unsupported date format: ${format}. Use DD, MM and YYYY.`);
75
+ const literalsEscaped = format.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
76
+ return new RegExp(`^${literalsEscaped.replace(DATE_TOKEN, (token) => DATE_TOKEN_PATTERNS[token])}$`);
77
+ }
78
+ /**
79
+ * Converts the text of a date to a `Date` at midnight in the time zone of the runtime.
80
+ * @return the date, or an Invalid Date if the text is not in the format or is not a date of the calendar (e.g. 02/31)
81
+ */
82
+ function parseDateText(text, format) {
83
+ const parts = dateFormatRegex(format).exec(text)?.groups;
84
+ if (!parts) return /* @__PURE__ */ new Date(NaN);
85
+ const [year, month, day] = [
86
+ Number(parts.year),
87
+ Number(parts.month),
88
+ Number(parts.day)
89
+ ];
90
+ const date = /* @__PURE__ */ new Date(0);
91
+ date.setFullYear(year, month - 1, day);
92
+ date.setHours(0, 0, 0, 0);
93
+ return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day ? date : /* @__PURE__ */ new Date(NaN);
29
94
  }
30
95
  /**
31
96
  * Converts a date in the given format to a `Date` object.
32
97
  * @param dateValue date to convert
33
- * @param format format of the date, in Day.js tokens; defaults to {@link BR_DATE_FORMAT}
34
- * @return the date, or `null` if there is no date
98
+ * @param format format of a text date, with the tokens `DD`, `MM` and `YYYY`; defaults to {@link BR_DATE_FORMAT}
99
+ * @return the date, `null` if there is no date, or an Invalid Date if the text is not a date in the format
100
+ * @throws Error if a text date is given with an unsupported format
35
101
  */
36
102
  function parseDate(dateValue, format = BR_DATE_FORMAT) {
37
- return dateValue ? dayjs(dateValue, format).toDate() : null;
103
+ if (!dateValue) return null;
104
+ return dateValue instanceof Date ? new Date(dateValue) : parseDateText(dateValue, format);
38
105
  }
39
106
  /** @return the current year and month in the `YYYY-MM` format */
40
107
  function getCurrentYearMonth() {
41
- const currentDate = /* @__PURE__ */ new Date();
42
- return `${currentDate.getFullYear()}-${(currentDate.getMonth() + 1).toString().padStart(2, "0")}`;
108
+ return toLocalIsoText(/* @__PURE__ */ new Date()).slice(0, YEAR_MONTH_LENGTH);
43
109
  }
44
110
  /**
45
111
  * Checks whether the end date is after the start date.
@@ -50,6 +116,6 @@ function getCurrentYearMonth() {
50
116
  function validDateInterval(startDate, endDate) {
51
117
  return !endDate || endDate <= MIN_VALID_DATE || endDate > startDate;
52
118
  }
53
- export { BR_DATE_FORMAT, ISO_DATE_FORMAT, MIN_VALID_DATE, formatDate, formatDateToISO, getCurrentYearMonth, parseDate, validDateInterval };
119
+ export { BR_DATE_FORMAT, ISO_DATE_FORMAT, MIN_VALID_DATE, formatDate, formatDateToISO, formatLocaleDate, formatLocaleTime, getCurrentYearMonth, isValidDate, parseDate, validDateInterval };
54
120
 
55
121
  //# sourceMappingURL=DateUtil.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DateUtil.js","names":[],"sources":["../../src/util/DateUtil.ts"],"sourcesContent":["import dayjs from 'dayjs';\nimport customParseFormat from 'dayjs/plugin/customParseFormat.js';\n\n/* Without this plugin, Day.js ignores the format given to `dayjs(value, format)` and parses the text month first. */\ndayjs.extend(customParseFormat);\n\n/** Value of a date field: a `Date`, a text in the given format, or nothing. */\nexport type DateFieldValue = Date | string | null | undefined;\n\nexport const BR_DATE_FORMAT = 'DD/MM/YYYY';\nexport const ISO_DATE_FORMAT = 'YYYY-MM-DD';\n\n/**\n * Minimum value for a date to be considered valid when Zod coerces it. It is later than the date created by\n * `new Date(null)` (12/31/1969), which Zod's `coerce` produces when a date field is not filled and receives `null`.\n */\nexport const MIN_VALID_DATE = new Date('01/01/1970');\n\n/**\n * Formats a date.\n * @param date date to format\n * @param format format, in Day.js tokens; defaults to {@link BR_DATE_FORMAT}\n * @return the formatted date, or `null` if there is no date\n */\nexport function formatDate(date: Date | undefined | null, format: string = BR_DATE_FORMAT): string | null {\n return date ? dayjs(date).format(format) : null;\n}\n\n/**\n * Formats a date in the ISO 8601 format.\n * @param date date to format\n * @see #ISO_DATE_FORMAT\n */\nexport function formatDateToISO(date: Date | undefined | null): string | null {\n return date ? dayjs(date).format(ISO_DATE_FORMAT) : null;\n}\n\n/**\n * Converts a date in the given format to a `Date` object.\n * @param dateValue date to convert\n * @param format format of the date, in Day.js tokens; defaults to {@link BR_DATE_FORMAT}\n * @return the date, or `null` if there is no date\n */\nexport function parseDate(dateValue: DateFieldValue, format: string = BR_DATE_FORMAT): Date | null {\n return dateValue ? dayjs(dateValue, format).toDate() : null;\n}\n\n/** @return the current year and month in the `YYYY-MM` format */\nexport function getCurrentYearMonth(): string {\n const currentDate = new Date();\n const year = currentDate.getFullYear();\n const month = (currentDate.getMonth() + 1).toString().padStart(2, '0');\n return `${year}-${month}`;\n}\n\n/**\n * Checks whether the end date is after the start date.\n * @param startDate start of the interval\n * @param endDate end of the interval; an absent or empty (before {@link MIN_VALID_DATE}) end date is valid\n * @return `true` if the interval is valid\n */\nexport function validDateInterval(startDate: Date, endDate: Date | undefined): boolean {\n return !endDate || endDate <= MIN_VALID_DATE || endDate > startDate;\n}\n"],"mappings":";;;;AAIA,MAAM,OAAO,iBAAiB;AAK9B,IAAa,iBAAiB;AAC9B,IAAa,kBAAkB;;;;;AAM/B,IAAa,iCAAiB,IAAI,KAAK,YAAY;;;;;;;AAQnD,SAAgB,WAAW,MAA+B,SAAiB,gBAA+B;CACtG,OAAO,OAAO,MAAM,IAAI,CAAC,CAAC,OAAO,MAAM,IAAI;AAC/C;;;;;;AAOA,SAAgB,gBAAgB,MAA8C;CAC1E,OAAO,OAAO,MAAM,IAAI,CAAC,CAAC,OAAO,eAAe,IAAI;AACxD;;;;;;;AAQA,SAAgB,UAAU,WAA2B,SAAiB,gBAA6B;CAC/F,OAAO,YAAY,MAAM,WAAW,MAAM,CAAC,CAAC,OAAO,IAAI;AAC3D;;AAGA,SAAgB,sBAA8B;CAC1C,MAAM,8BAAc,IAAI,KAAK;CAG7B,OAAO,GAFM,YAAY,YAEf,EAAK,IADA,YAAY,SAAS,IAAI,EAAA,CAAG,SAAS,CAAC,CAAC,SAAS,GAAG,GAChD;AACtB;;;;;;;AAQA,SAAgB,kBAAkB,WAAiB,SAAoC;CACnF,OAAO,CAAC,WAAW,WAAW,kBAAkB,UAAU;AAC9D"}
1
+ {"version":3,"file":"DateUtil.js","names":[],"sources":["../../src/util/DateUtil.ts"],"sourcesContent":["import { DEFAULT_LOCALE, KitLocale } from '../i18n/KitLocale';\n\n/** Value of a date field: a `Date`, a text in the given format, or nothing. */\nexport type DateFieldValue = Date | string | null | undefined;\n\nexport const BR_DATE_FORMAT = 'DD/MM/YYYY';\nexport const ISO_DATE_FORMAT = 'YYYY-MM-DD';\n\n/** Tokens of the formats {@link parseDate} reads, with the part of the date each one captures. */\nconst DATE_TOKEN_PATTERNS: Readonly<Record<string, string>> = {\n YYYY: '(?<year>\\\\d{4})',\n MM: '(?<month>\\\\d{2})',\n DD: '(?<day>\\\\d{2})'\n};\n\nconst DATE_TOKEN = /YYYY|MM|DD/g;\n\n/** Length of the `YYYY-MM` prefix of an ISO date. */\nconst YEAR_MONTH_LENGTH = 7;\n\n/**\n * Minimum value for a date to be considered valid when Zod coerces it. It is later than the date created by\n * `new Date(null)` (12/31/1969), which Zod's `coerce` produces when a date field is not filled and receives `null`.\n */\nexport const MIN_VALID_DATE = new Date('01/01/1970');\n\n/** @return whether there is a date and it is valid: `Intl` throws an error for an Invalid Date */\nexport function isValidDate(date: Date | undefined | null): date is Date {\n return !!date && !Number.isNaN(date.getTime());\n}\n\n/**\n * Formats a date in the numeric format of the locale, with 2-digit day and month (e.g. `10/05/2026` in `en-US`).\n * @param date a valid date, shown in the time zone of the runtime\n * @param locale locale that defines the order of the fields\n */\nexport function formatLocaleDate(date: Date, locale: KitLocale): string {\n return date.toLocaleDateString(locale, { day: '2-digit', month: '2-digit', year: 'numeric' });\n}\n\n/**\n * Formats a time, without seconds, in the format of the locale (e.g. `2:30 PM` in `en-US`, `14:30` in `pt-BR`).\n * @param date a valid date, shown in the time zone of the runtime\n * @param locale locale that defines the 12 or 24-hour clock\n */\nexport function formatLocaleTime(date: Date, locale: KitLocale): string {\n return date.toLocaleTimeString(locale, { timeStyle: 'short' });\n}\n\n/**\n * Formats a date in the numeric format of the locale.\n * @param date date to format\n * @param locale locale of the format; defaults to {@link DEFAULT_LOCALE}\n * @return the formatted date, or `null` if there is no valid date\n */\nexport function formatDate(date: Date | undefined | null, locale: KitLocale = DEFAULT_LOCALE): string | null {\n return isValidDate(date) ? formatLocaleDate(date, locale) : null;\n}\n\n/** @return the date in the {@link ISO_DATE_FORMAT}, in the time zone of the runtime */\nfunction toLocalIsoText(date: Date): string {\n const year = date.getFullYear().toString().padStart(4, '0');\n const month = (date.getMonth() + 1).toString().padStart(2, '0');\n const day = date.getDate().toString().padStart(2, '0');\n\n return `${year}-${month}-${day}`;\n}\n\n/**\n * Formats a date in the ISO 8601 format, in the time zone of the runtime.\n * @param date date to format\n * @return the formatted date, or `null` if there is no valid date\n * @see #ISO_DATE_FORMAT\n */\nexport function formatDateToISO(date: Date | undefined | null): string | null {\n return isValidDate(date) ? toLocalIsoText(date) : null;\n}\n\n/**\n * Creates the regular expression that reads a date in the given format.\n * @param format format with the tokens `DD`, `MM` and `YYYY`, once each, and no other letters\n * @throws Error if the format has anything else, such as the `MMM` or `YY` tokens of Mantine's `valueFormat`\n */\nfunction dateFormatRegex(format: string): RegExp {\n const tokens = format.match(DATE_TOKEN) ?? [];\n const onlyKnownTokens = !/[a-z]/i.test(format.replace(DATE_TOKEN, ''));\n if (tokens.length !== 3 || new Set(tokens).size !== 3 || !onlyKnownTokens) {\n throw new Error(`Unsupported date format: ${format}. Use DD, MM and YYYY.`);\n }\n\n const literalsEscaped = format.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n return new RegExp(`^${literalsEscaped.replace(DATE_TOKEN, token => DATE_TOKEN_PATTERNS[token])}$`);\n}\n\n/**\n * Converts the text of a date to a `Date` at midnight in the time zone of the runtime.\n * @return the date, or an Invalid Date if the text is not in the format or is not a date of the calendar (e.g. 02/31)\n */\nfunction parseDateText(text: string, format: string): Date {\n const parts = dateFormatRegex(format).exec(text)?.groups;\n if (!parts) return new Date(NaN);\n\n const [year, month, day] = [Number(parts.year), Number(parts.month), Number(parts.day)];\n\n // setFullYear, since the Date constructor reads the years 0 to 99 as 1900 to 1999.\n const date = new Date(0);\n date.setFullYear(year, month - 1, day);\n date.setHours(0, 0, 0, 0);\n\n // Date moves a day missing from the calendar (e.g. 02/31) to the next month, which changes the fields.\n const isSameDate = date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;\n return isSameDate ? date : new Date(NaN);\n}\n\n/**\n * Converts a date in the given format to a `Date` object.\n * @param dateValue date to convert\n * @param format format of a text date, with the tokens `DD`, `MM` and `YYYY`; defaults to {@link BR_DATE_FORMAT}\n * @return the date, `null` if there is no date, or an Invalid Date if the text is not a date in the format\n * @throws Error if a text date is given with an unsupported format\n */\nexport function parseDate(dateValue: DateFieldValue, format: string = BR_DATE_FORMAT): Date | null {\n if (!dateValue) return null;\n\n return dateValue instanceof Date ? new Date(dateValue) : parseDateText(dateValue, format);\n}\n\n/** @return the current year and month in the `YYYY-MM` format */\nexport function getCurrentYearMonth(): string {\n return toLocalIsoText(new Date()).slice(0, YEAR_MONTH_LENGTH);\n}\n\n/**\n * Checks whether the end date is after the start date.\n * @param startDate start of the interval\n * @param endDate end of the interval; an absent or empty (before {@link MIN_VALID_DATE}) end date is valid\n * @return `true` if the interval is valid\n */\nexport function validDateInterval(startDate: Date, endDate: Date | undefined): boolean {\n return !endDate || endDate <= MIN_VALID_DATE || endDate > startDate;\n}\n"],"mappings":";;;AAKA,IAAa,iBAAiB;AAC9B,IAAa,kBAAkB;;AAG/B,IAAM,sBAAwD;CAC1D,MAAM;CACN,IAAI;CACJ,IAAI;AACR;AAEA,IAAM,aAAa;;AAGnB,IAAM,oBAAoB;;;;;AAM1B,IAAa,iCAAiB,IAAI,KAAK,YAAY;;AAGnD,SAAgB,YAAY,MAA6C;CACrE,OAAO,CAAC,CAAC,QAAQ,CAAC,OAAO,MAAM,KAAK,QAAQ,CAAC;AACjD;;;;;;AAOA,SAAgB,iBAAiB,MAAY,QAA2B;CACpE,OAAO,KAAK,mBAAmB,QAAQ;EAAE,KAAK;EAAW,OAAO;EAAW,MAAM;CAAU,CAAC;AAChG;;;;;;AAOA,SAAgB,iBAAiB,MAAY,QAA2B;CACpE,OAAO,KAAK,mBAAmB,QAAQ,EAAE,WAAW,QAAQ,CAAC;AACjE;;;;;;;AAQA,SAAgB,WAAW,MAA+B,SAAoB,gBAA+B;CACzG,OAAO,YAAY,IAAI,IAAI,iBAAiB,MAAM,MAAM,IAAI;AAChE;;AAGA,SAAS,eAAe,MAAoB;CAKxC,OAAO,GAJM,KAAK,YAAY,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,GAAG,GAI7C,EAAK,IAHA,KAAK,SAAS,IAAI,EAAA,CAAG,SAAS,CAAC,CAAC,SAAS,GAAG,GAGzC,EAAM,GAFZ,KAAK,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,GAAG,GAEvB;AAC/B;;;;;;;AAQA,SAAgB,gBAAgB,MAA8C;CAC1E,OAAO,YAAY,IAAI,IAAI,eAAe,IAAI,IAAI;AACtD;;;;;;AAOA,SAAS,gBAAgB,QAAwB;CAC7C,MAAM,SAAS,OAAO,MAAM,UAAU,KAAK,CAAC;CAC5C,MAAM,kBAAkB,CAAC,SAAS,KAAK,OAAO,QAAQ,YAAY,EAAE,CAAC;CACrE,IAAI,OAAO,WAAW,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,SAAS,KAAK,CAAC,iBACtD,MAAM,IAAI,MAAM,4BAA4B,OAAO,uBAAuB;CAG9E,MAAM,kBAAkB,OAAO,QAAQ,uBAAuB,MAAM;CACpE,OAAO,IAAI,OAAO,IAAI,gBAAgB,QAAQ,aAAY,UAAS,oBAAoB,MAAM,EAAE,EAAE;AACrG;;;;;AAMA,SAAS,cAAc,MAAc,QAAsB;CACvD,MAAM,QAAQ,gBAAgB,MAAM,CAAC,CAAC,KAAK,IAAI,CAAC,EAAE;CAClD,IAAI,CAAC,OAAO,uBAAO,IAAI,KAAK,GAAG;CAE/B,MAAM,CAAC,MAAM,OAAO,OAAO;EAAC,OAAO,MAAM,IAAI;EAAG,OAAO,MAAM,KAAK;EAAG,OAAO,MAAM,GAAG;CAAC;CAGtF,MAAM,uBAAO,IAAI,KAAK,CAAC;CACvB,KAAK,YAAY,MAAM,QAAQ,GAAG,GAAG;CACrC,KAAK,SAAS,GAAG,GAAG,GAAG,CAAC;CAIxB,OADmB,KAAK,YAAY,MAAM,QAAQ,KAAK,SAAS,MAAM,QAAQ,KAAK,KAAK,QAAQ,MAAM,MAClF,uBAAO,IAAI,KAAK,GAAG;AAC3C;;;;;;;;AASA,SAAgB,UAAU,WAA2B,SAAiB,gBAA6B;CAC/F,IAAI,CAAC,WAAW,OAAO;CAEvB,OAAO,qBAAqB,OAAO,IAAI,KAAK,SAAS,IAAI,cAAc,WAAW,MAAM;AAC5F;;AAGA,SAAgB,sBAA8B;CAC1C,OAAO,+BAAe,IAAI,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,iBAAiB;AAChE;;;;;;;AAQA,SAAgB,kBAAkB,WAAiB,SAAoC;CACnF,OAAO,CAAC,WAAW,WAAW,kBAAkB,UAAU;AAC9D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ifto/mantine-kit",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "General purpose Mantine components for React apps: CRUD tables and forms, layout, ECharts charts, Excel/CSV export, print pages and a REST HTTP client, with i18n (en-US and pt-BR).",
5
5
  "license": "MIT",
6
6
  "author": "IFTO - Campus Palmas",
@@ -34,12 +34,14 @@
34
34
  "README.md",
35
35
  "LICENSE"
36
36
  ],
37
+ "//exports": "The default condition of \"./*\" is needed by Jest, which resolves packages with the require and default conditions. Without it, deep imports fail with Cannot find module. The import condition only matches tools that load the package as ESM, while default matches any tool when no previous condition matched. It must stay as the last condition.",
37
38
  "exports": {
38
39
  "./styles.css": "./dist/styles.css",
39
40
  "./print.css": "./dist/styles/print.css",
40
41
  "./*": {
41
42
  "types": "./dist/*.d.ts",
42
- "import": "./dist/*.js"
43
+ "import": "./dist/*.js",
44
+ "default": "./dist/*.js"
43
45
  },
44
46
  "./package.json": "./package.json"
45
47
  },
@@ -70,7 +72,6 @@
70
72
  "@tabler/icons-react": "^3.24.0",
71
73
  "axios": "^1.20.0",
72
74
  "clsx": "^2.1.1",
73
- "dayjs": "^1.11.13",
74
75
  "echarts": "^6.1.0",
75
76
  "exceljs": "^4.4.0",
76
77
  "mantine-datatable": "^9.4.0",