refine-mantine 1.6.1 → 1.7.0-dev.10

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
@@ -1,3 +1,5 @@
1
+ [![Discord](https://img.shields.io/badge/Chat%20on-Discord-%235865f2)](https://discord.gg/BCGmvSSJBk)
2
+
1
3
  # Refine-Mantine
2
4
 
3
5
  ## Refine Mantine Components
@@ -25,3 +27,13 @@ npm i refine-mantine
25
27
 
26
28
  **Early development stage:** The project is actively being developed, and APIs or component structures may change frequently.
27
29
  Feedback, suggestions, and contributions are welcome as the project evolves.
30
+
31
+ ## How to Contribute
32
+
33
+ - leave a star ⭐
34
+ - report a bug 🐞
35
+ - open a pull request 🏗️
36
+ - help others ❤️
37
+ - [buy me a coffee ☕](https://www.buymeacoffee.com/kruschid)
38
+
39
+ <a href="https://www.buymeacoffee.com/kruschid" target="_blank"><img width="200px" src="https://cdn.buymeacoffee.com/buttons/v2/default-orange.png" alt="Buy Me A Coffee" ></a>
package/dist/index.d.ts CHANGED
@@ -194,7 +194,7 @@ declare const FileField: React.FC<UrlFieldProps>;
194
194
  declare const PhoneField: React.FC<UrlFieldProps>;
195
195
  //#endregion
196
196
  //#region src/components/layout/Layout.d.ts
197
- interface LayoutProps {
197
+ interface LayoutProps$1 {
198
198
  children: ReactNode$1;
199
199
  shellProps?: AppShellProps;
200
200
  headerProps?: AppShellHeaderProps;
@@ -216,7 +216,27 @@ interface LayoutLocale {
216
216
  label: string;
217
217
  icon?: ReactNode$1;
218
218
  }
219
- declare const Layout: React.FC<LayoutProps>;
219
+ declare const Layout: React.FC<LayoutProps$1>;
220
+ //#endregion
221
+ //#region src/components/layout/LayoutMinimal.d.ts
222
+ interface LayoutProps {
223
+ children: ReactNode$1;
224
+ shellProps?: AppShellProps;
225
+ headerProps?: AppShellHeaderProps;
226
+ navbarProps?: AppShellNavbarProps;
227
+ navbarConfiguration?: Partial<AppShellNavbarConfiguration>;
228
+ navbarMenuProps?: AppShellSectionProps;
229
+ navbarFooterProps?: AppShellSectionProps;
230
+ mainProps?: AppShellMainProps;
231
+ hideNavbar?: boolean;
232
+ footer?: ReactNode$1;
233
+ footerProps?: AppShellFooterProps;
234
+ locales?: LayoutLocale[];
235
+ renderHeader?: (toggle: () => void) => ReactNode$1;
236
+ renderMenu?: (params: ReturnType<typeof useMenu>) => ReactNode$1;
237
+ renderIdentity?: <T extends BaseRecord>(identity: T, logout: () => void) => ReactNode$1;
238
+ }
239
+ declare const LayoutMinimal: React.FC<LayoutProps>;
220
240
  //#endregion
221
241
  //#region src/components/notification/AutoSaveIndicator.d.ts
222
242
  interface AutoSaveIndicatorProps {
@@ -332,6 +352,8 @@ type LoginPageProps = {
332
352
  registerLink?: string;
333
353
  forgotPasswordLink?: string;
334
354
  validate?: FormValidateInput<LoginForm>;
355
+ onBeforeLogin?: (args: LoginArgs) => void | Promise<void>;
356
+ onBeforeProviderLogin?: (args: LoginArgs) => void | Promise<void>;
335
357
  wrapperProps?: StackProps;
336
358
  scrollAreaProps?: ScrollAreaProps;
337
359
  emailFieldProps?: TextInputProps;
@@ -382,6 +404,19 @@ interface RegisterForm {
382
404
  }
383
405
  declare const RegisterPage: React.FC<RegisterPageProps>;
384
406
  //#endregion
407
+ //#region src/pages/auth/RegistrationVerificationPage.d.ts
408
+ interface RegistrationVerificationPageProps {
409
+ loginLink?: string;
410
+ wrapperProps?: StackProps;
411
+ scrollAreaProps?: ScrollAreaProps;
412
+ cardProps?: CardProps;
413
+ buttonProps?: ButtonProps;
414
+ icon?: ReactNode$1;
415
+ title?: ReactNode$1;
416
+ description?: ReactNode$1;
417
+ }
418
+ declare const RegistrationVerificationPage: React.FC<RegistrationVerificationPageProps>;
419
+ //#endregion
385
420
  //#region src/pages/auth/UpdatePasswordPage.d.ts
386
421
  interface UpdatePasswordPageProps {
387
422
  mutationVariables?: UpdatePasswordFormTypes;
@@ -398,6 +433,9 @@ interface UpdatePasswordPageProps {
398
433
  }
399
434
  declare const UpdatePasswordPage: React.FC<UpdatePasswordPageProps>;
400
435
  //#endregion
436
+ //#region src/pages/auth/DefaultTitle.d.ts
437
+ declare const DefaultTitle: () => react_jsx_runtime0.JSX.Element;
438
+ //#endregion
401
439
  //#region src/pages/NotFound.d.ts
402
440
  interface NotFoundProps {
403
441
  returnTo: string;
@@ -410,4 +448,4 @@ declare const authProvider: AuthProvider;
410
448
  //#region src/providers/notificationProvider.d.ts
411
449
  declare const notificationProvider: () => NotificationProvider;
412
450
  //#endregion
413
- export { AutoSaveIndicator, AutoSaveIndicatorProps, BooleanField, BooleanFieldProps, Breadcrumb, BreadcrumbProps, CloneButton, CloneButtonProps, ColorSchemeToggle, ColumnFilter, ColumnSorter, Create, CreateButton, CreateButtonProps, CreateProps, DateField, DateFieldProps, DeleteButton, DeleteButtonProps, Edit, EditButton, EditButtonProps, EditProps, EmailField, Empty, ExportButton, ExportButtonProps, FileField, ForgotPasswordForm, ForgotPasswordPage, ForgotPasswordPageProps, ImportButton, ImportButtonProps, Layout, LayoutLocale, List, ListButton, ListButtonProps, ListProps, LoginArgs, LoginPage, LoginPageProps, Message, MessageProps, NotFound, OAuthProviderMantine, OtpHandler, PhoneField, RefreshButton, RefreshButtonProps, RegisterForm, RegisterPage, RegisterPageProps, SaveButton, SaveButtonProps, Show, ShowButton, ShowButtonProps, ShowProps, Table, TranslateFn, UpdatePasswordPage, UpdatePasswordPageProps, UrlField, UrlFieldProps, UseFormProps, UseFormReturnType, authProvider, notificationProvider, useForm, useOtp };
451
+ export { AutoSaveIndicator, AutoSaveIndicatorProps, BooleanField, BooleanFieldProps, Breadcrumb, BreadcrumbProps, CloneButton, CloneButtonProps, ColorSchemeToggle, ColumnFilter, ColumnSorter, Create, CreateButton, CreateButtonProps, CreateProps, DateField, DateFieldProps, DefaultTitle, DeleteButton, DeleteButtonProps, Edit, EditButton, EditButtonProps, EditProps, EmailField, Empty, ExportButton, ExportButtonProps, FileField, ForgotPasswordForm, ForgotPasswordPage, ForgotPasswordPageProps, ImportButton, ImportButtonProps, Layout, LayoutLocale, LayoutMinimal, List, ListButton, ListButtonProps, ListProps, LoginArgs, LoginPage, LoginPageProps, Message, MessageProps, NotFound, OAuthProviderMantine, OtpHandler, PhoneField, RefreshButton, RefreshButtonProps, RegisterForm, RegisterPage, RegisterPageProps, RegistrationVerificationPage, RegistrationVerificationPageProps, SaveButton, SaveButtonProps, Show, ShowButton, ShowButtonProps, ShowProps, Table, TranslateFn, UpdatePasswordPage, UpdatePasswordPageProps, UrlField, UrlFieldProps, UseFormProps, UseFormReturnType, authProvider, notificationProvider, useForm, useOtp };
package/dist/index.js CHANGED
@@ -952,18 +952,23 @@ const Show = (props) => {
952
952
  /* @__PURE__ */ jsxs(Group, {
953
953
  justify: "space-between",
954
954
  align: "center",
955
+ wrap: "nowrap",
955
956
  ...headerProps,
956
957
  children: [/* @__PURE__ */ jsxs(Stack, {
957
958
  gap: "xs",
958
959
  children: [breadcrumbComponent, /* @__PURE__ */ jsxs(Group, {
959
960
  gap: "xs",
961
+ wrap: "nowrap",
960
962
  children: [buttonBack, /* @__PURE__ */ jsx(Title, {
961
963
  order: 3,
964
+ lineClamp: 2,
965
+ textWrap: "balance",
962
966
  children: title ?? translate(`${identifier}.titles.show`, `Show ${getUserFriendlyName(resource?.meta?.label ?? identifier, "singular")}`)
963
967
  })]
964
968
  })]
965
969
  }), /* @__PURE__ */ jsx(Group, {
966
970
  ...headerButtonProps,
971
+ wrap: "nowrap",
967
972
  children: headerButtons
968
973
  })]
969
974
  }),
@@ -1103,7 +1108,7 @@ const Layout = (p) => {
1103
1108
  to: "/",
1104
1109
  style: { all: "unset" },
1105
1110
  children: /* @__PURE__ */ jsxs(Group, { children: [defaultIcon, /* @__PURE__ */ jsx(Text, { children: defaultText })] })
1106
- })] }), /* @__PURE__ */ jsxs(Group, { children: [p.locales && /* @__PURE__ */ jsx(Locales, { locales: p.locales }), /* @__PURE__ */ jsx(Tooltip, {
1111
+ })] }), /* @__PURE__ */ jsxs(Group, { children: [p.locales && /* @__PURE__ */ jsx(Locales$1, { locales: p.locales }), /* @__PURE__ */ jsx(Tooltip, {
1107
1112
  label: computedColorScheme === "dark" ? translate("layout.header.lightMode", "Light mode") : translate("layout.header.darkMode", "Dark mode"),
1108
1113
  children: /* @__PURE__ */ jsx(ActionIcon, {
1109
1114
  onClick: () => setColorScheme(computedColorScheme === "light" ? "dark" : "light"),
@@ -1181,7 +1186,7 @@ const MenuItem = (p) => {
1181
1186
  }, p.item.key)
1182
1187
  }, p.item.key);
1183
1188
  };
1184
- const Locales = (p) => {
1189
+ const Locales$1 = (p) => {
1185
1190
  const { changeLocale, getLocale } = useTranslation();
1186
1191
  const locale = getLocale();
1187
1192
  return /* @__PURE__ */ jsxs(Menu, {
@@ -1196,6 +1201,284 @@ const Locales = (p) => {
1196
1201
  });
1197
1202
  };
1198
1203
 
1204
+ //#endregion
1205
+ //#region src/components/layout/LayoutMinimal.tsx
1206
+ const LayoutMinimal = (p) => {
1207
+ const [opened, { toggle, close }] = useDisclosure();
1208
+ const { title: { icon: defaultIcon, text: defaultText } = {} } = useRefineOptions();
1209
+ const { data: identity } = useGetIdentity();
1210
+ const menu = useMenu();
1211
+ const { mutate: logout } = useLogout();
1212
+ const { setColorScheme } = useMantineColorScheme();
1213
+ const computedColorScheme = useComputedColorScheme("light", { getInitialValueInEffect: true });
1214
+ const translate = useTranslate();
1215
+ const languageLabel = translate("layout.navbar.languageLabel", "Language");
1216
+ const colorSchemeLabel = computedColorScheme === "dark" ? translate("layout.header.lightMode", "Light mode") : translate("layout.header.darkMode", "Dark mode");
1217
+ const handleLogout = useCallback(() => {
1218
+ logout();
1219
+ }, [logout]);
1220
+ return /* @__PURE__ */ jsxs(AppShell, {
1221
+ header: { height: {
1222
+ base: 60,
1223
+ sm: 0
1224
+ } },
1225
+ navbar: {
1226
+ width: {
1227
+ base: 260,
1228
+ sm: 80
1229
+ },
1230
+ breakpoint: "sm",
1231
+ collapsed: {
1232
+ mobile: !opened || p.hideNavbar,
1233
+ desktop: p.hideNavbar
1234
+ },
1235
+ ...p.navbarConfiguration
1236
+ },
1237
+ padding: "md",
1238
+ ...p.shellProps,
1239
+ children: [
1240
+ /* @__PURE__ */ jsx(AppShell.Header, {
1241
+ p: "md",
1242
+ ...p.headerProps,
1243
+ hiddenFrom: "sm",
1244
+ children: p.renderHeader ? p.renderHeader(toggle) : /* @__PURE__ */ jsx(Group, {
1245
+ justify: "space-between",
1246
+ children: /* @__PURE__ */ jsxs(Group, { children: [/* @__PURE__ */ jsx(Burger, {
1247
+ opened,
1248
+ onClick: toggle,
1249
+ size: "sm",
1250
+ hidden: p.hideNavbar
1251
+ }), /* @__PURE__ */ jsx(Anchor, {
1252
+ component: Link,
1253
+ to: "/",
1254
+ style: { all: "unset" },
1255
+ children: /* @__PURE__ */ jsxs(Group, { children: [defaultIcon, defaultText ? /* @__PURE__ */ jsx(Text, { children: defaultText }) : null] })
1256
+ })] })
1257
+ })
1258
+ }),
1259
+ /* @__PURE__ */ jsxs(AppShell.Navbar, {
1260
+ ...p.navbarProps,
1261
+ children: [
1262
+ /* @__PURE__ */ jsx(AppShell.Section, {
1263
+ visibleFrom: "sm",
1264
+ p: "md",
1265
+ children: /* @__PURE__ */ jsx(Stack, {
1266
+ align: "center",
1267
+ gap: "xs",
1268
+ children: /* @__PURE__ */ jsx(Tooltip, {
1269
+ label: defaultText ?? translate("layout.navbar.homeLabel", "Home"),
1270
+ position: "right",
1271
+ transitionProps: { duration: 0 },
1272
+ children: /* @__PURE__ */ jsx(Anchor, {
1273
+ component: Link,
1274
+ to: "/",
1275
+ style: { all: "unset" },
1276
+ children: /* @__PURE__ */ jsx(Stack, {
1277
+ align: "center",
1278
+ gap: 2,
1279
+ children: defaultIcon
1280
+ })
1281
+ })
1282
+ })
1283
+ })
1284
+ }),
1285
+ /* @__PURE__ */ jsx(AppShell.Section, {
1286
+ component: ScrollArea,
1287
+ grow: true,
1288
+ mt: "xs",
1289
+ ...p.navbarMenuProps,
1290
+ visibleFrom: "sm",
1291
+ children: p.renderMenu ? p.renderMenu(menu) : /* @__PURE__ */ jsx(Stack, {
1292
+ align: "center",
1293
+ gap: 0,
1294
+ children: menu.menuItems.map((item) => /* @__PURE__ */ jsx(MenuItemIcon, {
1295
+ item,
1296
+ selectedKey: menu.selectedKey,
1297
+ onClick: close
1298
+ }, item.key))
1299
+ })
1300
+ }),
1301
+ /* @__PURE__ */ jsx(AppShell.Section, {
1302
+ component: ScrollArea,
1303
+ grow: true,
1304
+ mt: "xs",
1305
+ ...p.navbarMenuProps,
1306
+ hiddenFrom: "sm",
1307
+ children: p.renderMenu ? p.renderMenu(menu) : /* @__PURE__ */ jsx(Stack, {
1308
+ gap: "xs",
1309
+ children: menu.menuItems.map((item) => /* @__PURE__ */ jsx(MenuItemFull, {
1310
+ item,
1311
+ selectedKey: menu.selectedKey,
1312
+ onClick: close
1313
+ }, item.key))
1314
+ })
1315
+ }),
1316
+ /* @__PURE__ */ jsx(AppShell.Section, {
1317
+ pb: "sm",
1318
+ ...p.navbarFooterProps,
1319
+ visibleFrom: "sm",
1320
+ children: p.renderIdentity ? p.renderIdentity(identity, handleLogout) : /* @__PURE__ */ jsxs(Stack, {
1321
+ align: "center",
1322
+ gap: "xs",
1323
+ children: [
1324
+ p.locales && /* @__PURE__ */ jsx(Locales, {
1325
+ locales: p.locales,
1326
+ variant: "icon",
1327
+ label: languageLabel
1328
+ }),
1329
+ /* @__PURE__ */ jsx(Tooltip, {
1330
+ label: colorSchemeLabel,
1331
+ position: "right",
1332
+ transitionProps: { duration: 0 },
1333
+ children: /* @__PURE__ */ jsx(ActionIcon, {
1334
+ onClick: () => setColorScheme(computedColorScheme === "light" ? "dark" : "light"),
1335
+ "aria-label": translate("layout.header.toggleColorScheme", "Toggle color scheme"),
1336
+ variant: "subtle",
1337
+ size: "xl",
1338
+ children: computedColorScheme === "light" ? /* @__PURE__ */ jsx(IconSun, {
1339
+ stroke: 1.5,
1340
+ size: 22
1341
+ }) : /* @__PURE__ */ jsx(IconMoon, {
1342
+ stroke: 1.5,
1343
+ size: 22
1344
+ })
1345
+ })
1346
+ }),
1347
+ /* @__PURE__ */ jsx(Tooltip, {
1348
+ label: translate("layout.navbar.signOutLabel", "Sign out"),
1349
+ position: "right",
1350
+ transitionProps: { duration: 0 },
1351
+ children: /* @__PURE__ */ jsx(ActionIcon, {
1352
+ onClick: handleLogout,
1353
+ "aria-label": translate("layout.navbar.signOutLabel", "Sign out"),
1354
+ variant: "subtle",
1355
+ size: "xl",
1356
+ children: /* @__PURE__ */ jsx(IconLogout, { size: 20 })
1357
+ })
1358
+ })
1359
+ ]
1360
+ })
1361
+ }),
1362
+ /* @__PURE__ */ jsx(AppShell.Section, {
1363
+ pb: "sm",
1364
+ ...p.navbarFooterProps,
1365
+ hiddenFrom: "sm",
1366
+ children: /* @__PURE__ */ jsxs(Stack, {
1367
+ gap: "xs",
1368
+ children: [
1369
+ p.locales && /* @__PURE__ */ jsx(Locales, {
1370
+ locales: p.locales,
1371
+ variant: "full",
1372
+ label: languageLabel
1373
+ }),
1374
+ /* @__PURE__ */ jsx(NavLink, {
1375
+ onClick: () => setColorScheme(computedColorScheme === "light" ? "dark" : "light"),
1376
+ leftSection: computedColorScheme === "light" ? /* @__PURE__ */ jsx(IconSun, {
1377
+ stroke: 1.5,
1378
+ size: 18
1379
+ }) : /* @__PURE__ */ jsx(IconMoon, {
1380
+ stroke: 1.5,
1381
+ size: 18
1382
+ }),
1383
+ label: colorSchemeLabel,
1384
+ variant: "subtle"
1385
+ }),
1386
+ p.renderIdentity ? p.renderIdentity(identity, handleLogout) : /* @__PURE__ */ jsx(NavLink, {
1387
+ onClick: handleLogout,
1388
+ leftSection: identity?.avatar ? /* @__PURE__ */ jsx(Avatar, { src: identity.avatar }) : /* @__PURE__ */ jsx(IconLogout, { size: 18 }),
1389
+ label: translate("layout.navbar.signOutLabel", "Sign out"),
1390
+ description: identity?.email ? translate("layout.navbar.signOutDescription", { email: identity.email }, `Signed in as ${identity.email}`) : void 0,
1391
+ variant: "filled",
1392
+ active: true
1393
+ })
1394
+ ]
1395
+ })
1396
+ })
1397
+ ]
1398
+ }),
1399
+ /* @__PURE__ */ jsx(AppShell.Main, {
1400
+ ...p.mainProps,
1401
+ children: p.children
1402
+ }),
1403
+ /* @__PURE__ */ jsx(AppShell.Footer, {
1404
+ ...p.footerProps,
1405
+ children: p.footer
1406
+ })
1407
+ ]
1408
+ });
1409
+ };
1410
+ const MenuItemIcon = (p) => {
1411
+ const { listUrl } = useNavigation();
1412
+ const isSelected = p.item.key === p.selectedKey;
1413
+ const label = p.item.meta?.label ?? p.item.label ?? p.item.name;
1414
+ return /* @__PURE__ */ jsx(CanAccess, {
1415
+ resource: p.item.name,
1416
+ action: "list",
1417
+ params: { resource: p.item },
1418
+ children: /* @__PURE__ */ jsx(Tooltip, {
1419
+ label,
1420
+ position: "right",
1421
+ transitionProps: { duration: 0 },
1422
+ children: /* @__PURE__ */ jsx(ActionIcon, {
1423
+ component: Link,
1424
+ to: listUrl(p.item.name),
1425
+ variant: isSelected ? "filled" : "subtle",
1426
+ size: "xl",
1427
+ mb: "xs",
1428
+ bd: 0,
1429
+ onClick: p.onClick,
1430
+ children: p.item.meta?.icon ?? /* @__PURE__ */ jsx(IconList, { size: 20 })
1431
+ }, p.item.key)
1432
+ })
1433
+ }, p.item.key);
1434
+ };
1435
+ const MenuItemFull = (p) => {
1436
+ const { listUrl } = useNavigation();
1437
+ const isSelected = p.item.key === p.selectedKey;
1438
+ const label = p.item.meta?.label ?? p.item.label ?? p.item.name;
1439
+ return /* @__PURE__ */ jsx(CanAccess, {
1440
+ resource: p.item.name,
1441
+ action: "list",
1442
+ params: { resource: p.item },
1443
+ children: /* @__PURE__ */ jsx(NavLink, {
1444
+ label,
1445
+ leftSection: p.item.meta?.icon ?? /* @__PURE__ */ jsx(IconList, { size: 18 }),
1446
+ active: isSelected,
1447
+ component: Link,
1448
+ to: listUrl(p.item.name),
1449
+ onClick: p.onClick
1450
+ }, p.item.key)
1451
+ }, p.item.key);
1452
+ };
1453
+ const Locales = (p) => {
1454
+ const { changeLocale, getLocale } = useTranslation();
1455
+ const locale = getLocale();
1456
+ return /* @__PURE__ */ jsxs(Menu, {
1457
+ shadow: "md",
1458
+ width: 200,
1459
+ children: [/* @__PURE__ */ jsx(Menu.Target, { children: p.variant === "icon" ? /* @__PURE__ */ jsx(Tooltip, {
1460
+ label: p.label,
1461
+ position: "right",
1462
+ transitionProps: { duration: 0 },
1463
+ children: /* @__PURE__ */ jsx(ActionIcon, {
1464
+ "aria-label": p.label,
1465
+ variant: "subtle",
1466
+ size: "xl",
1467
+ children: /* @__PURE__ */ jsx(IconLanguage, {})
1468
+ })
1469
+ }) : /* @__PURE__ */ jsx(NavLink, {
1470
+ label: p.label,
1471
+ leftSection: /* @__PURE__ */ jsx(IconLanguage, { size: 18 }),
1472
+ component: "button"
1473
+ }) }), /* @__PURE__ */ jsx(Menu.Dropdown, { children: p.locales.map(({ label, lang, icon }) => /* @__PURE__ */ jsx(Menu.Item, {
1474
+ leftSection: icon,
1475
+ onClick: () => changeLocale(lang),
1476
+ rightSection: lang === locale ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : void 0,
1477
+ children: label
1478
+ }, lang)) })]
1479
+ });
1480
+ };
1481
+
1199
1482
  //#endregion
1200
1483
  //#region src/components/notification/Message.tsx
1201
1484
  const Message = ({ message, description, undoLabel, undoableTime = 5, undoableTimeout = 0, notificationProps, actioniconProps, buttonProps, onUndo }) => /* @__PURE__ */ jsx(Notification, {
@@ -1408,7 +1691,7 @@ const set = (obj, path, value) => {
1408
1691
 
1409
1692
  //#endregion
1410
1693
  //#region src/hooks/useForm.ts
1411
- const useForm = ({ refineCoreProps, disableServerSideValidation: disableServerSideValidationProp = false,...rest } = {}) => {
1694
+ const useForm = ({ refineCoreProps, disableServerSideValidation: disableServerSideValidationProp = false, ...rest } = {}) => {
1412
1695
  const { options } = useRefineContext();
1413
1696
  const disableServerSideValidation = options?.disableServerSideValidation || disableServerSideValidationProp;
1414
1697
  const translate = useTranslate();
@@ -1529,6 +1812,7 @@ const DefaultTitle = () => {
1529
1812
 
1530
1813
  //#endregion
1531
1814
  //#region src/pages/auth/ForgotPasswordPage.tsx
1815
+ /** biome-ignore-all lint/correctness/useUniqueElementIds: test ids for playwright */
1532
1816
  const ForgotPasswordPage = (p) => {
1533
1817
  const translate = useTranslate();
1534
1818
  const { mutate: forgotPassword, isPending } = useForgotPassword();
@@ -1625,25 +1909,30 @@ const LoginPage = (p) => {
1625
1909
  ...p.validate
1626
1910
  }
1627
1911
  });
1628
- const handleProviderLogin = useCallback((provider) => {
1629
- login.mutate({
1912
+ const handleProviderLogin = useCallback(async (provider) => {
1913
+ const loginArgs = {
1630
1914
  providerName: provider.name,
1631
1915
  translate,
1632
1916
  ...p.mutationVariables
1633
- });
1917
+ };
1918
+ await p.onBeforeProviderLogin?.(loginArgs);
1919
+ login.mutate(loginArgs);
1634
1920
  }, [
1635
1921
  login,
1636
1922
  translate,
1637
- p.mutationVariables
1923
+ p.mutationVariables,
1924
+ p.onBeforeProviderLogin
1638
1925
  ]);
1639
- const handleLogin = onSubmit(({ email, password }) => {
1640
- login.mutate({
1926
+ const handleLogin = onSubmit(async ({ email, password }) => {
1927
+ const loginArgs = {
1641
1928
  email,
1642
1929
  password,
1643
1930
  otpHandler: p.otpHandler,
1644
1931
  translate,
1645
1932
  ...p.mutationVariables
1646
- });
1933
+ };
1934
+ await p.onBeforeLogin?.(loginArgs);
1935
+ login.mutate(loginArgs);
1647
1936
  });
1648
1937
  return /* @__PURE__ */ jsx(Stack, {
1649
1938
  h: "100vh",
@@ -1886,6 +2175,52 @@ const RegisterPage = (p) => {
1886
2175
  });
1887
2176
  };
1888
2177
 
2178
+ //#endregion
2179
+ //#region src/pages/auth/RegistrationVerificationPage.tsx
2180
+ const RegistrationVerificationPage = (p) => {
2181
+ const translate = useTranslate();
2182
+ return /* @__PURE__ */ jsx(Stack, {
2183
+ h: "100vh",
2184
+ align: "center",
2185
+ justify: "center",
2186
+ ...p.wrapperProps,
2187
+ children: /* @__PURE__ */ jsxs(ScrollArea, {
2188
+ type: "never",
2189
+ ...p.scrollAreaProps,
2190
+ children: [p.icon ?? /* @__PURE__ */ jsx(DefaultTitle, {}), /* @__PURE__ */ jsxs(Card, {
2191
+ shadow: "sm",
2192
+ padding: "lg",
2193
+ radius: "md",
2194
+ withBorder: true,
2195
+ ...p.cardProps,
2196
+ children: [
2197
+ p.title ?? /* @__PURE__ */ jsx(Title, {
2198
+ order: 5,
2199
+ mb: "sm",
2200
+ ta: "center",
2201
+ children: translate("pages.registerVerification.title", "Thank you for your registration")
2202
+ }),
2203
+ p.description ?? /* @__PURE__ */ jsx(Text, {
2204
+ size: "sm",
2205
+ ta: "center",
2206
+ c: "dimmed",
2207
+ children: translate("pages.registerVerification.description", "You will get an email where you can confirm your registration.")
2208
+ }),
2209
+ p.loginLink && /* @__PURE__ */ jsx(Button, {
2210
+ component: Link,
2211
+ to: p.loginLink,
2212
+ fullWidth: true,
2213
+ mt: "lg",
2214
+ variant: "light",
2215
+ ...p.buttonProps,
2216
+ children: translate("pages.registerVerification.login", "Back to login")
2217
+ })
2218
+ ]
2219
+ })]
2220
+ })
2221
+ });
2222
+ };
2223
+
1889
2224
  //#endregion
1890
2225
  //#region src/pages/auth/UpdatePasswordPage.tsx
1891
2226
  const UpdatePasswordPage = (p) => {
@@ -2093,4 +2428,4 @@ const notificationProvider = () => {
2093
2428
  };
2094
2429
 
2095
2430
  //#endregion
2096
- export { AutoSaveIndicator, BooleanField, Breadcrumb, CloneButton, ColorSchemeToggle, ColumnFilter, ColumnSorter, Create, CreateButton, DateField, DeleteButton, Edit, EditButton, EmailField, Empty, ExportButton, FileField, ForgotPasswordPage, ImportButton, Layout, List, ListButton, LoginPage, Message, NotFound, PhoneField, RefreshButton, RegisterPage, SaveButton, Show, ShowButton, Table, UpdatePasswordPage, UrlField, notificationProvider, useForm, useOtp };
2431
+ export { AutoSaveIndicator, BooleanField, Breadcrumb, CloneButton, ColorSchemeToggle, ColumnFilter, ColumnSorter, Create, CreateButton, DateField, DefaultTitle, DeleteButton, Edit, EditButton, EmailField, Empty, ExportButton, FileField, ForgotPasswordPage, ImportButton, Layout, LayoutMinimal, List, ListButton, LoginPage, Message, NotFound, PhoneField, RefreshButton, RegisterPage, RegistrationVerificationPage, SaveButton, Show, ShowButton, Table, UpdatePasswordPage, UrlField, notificationProvider, useForm, useOtp };
package/package.json CHANGED
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "name": "refine-mantine",
3
+ "version": "1.7.0-dev.10",
3
4
  "type": "module",
4
5
  "exports": "./dist/index.js",
5
6
  "types": "./dist/index.d.ts",
@@ -18,6 +19,9 @@
18
19
  "prepare": "husky",
19
20
  "commitlint": "commitlint --edit"
20
21
  },
22
+ "dependencies": {
23
+ "@refinedev/ui-types": "^2.0.1"
24
+ },
21
25
  "peerDependencies": {
22
26
  "@mantine/core": "^8.3.1",
23
27
  "@mantine/form": "^8.3.1",
@@ -36,44 +40,44 @@
36
40
  "react-router-dom": "^7.0.2"
37
41
  },
38
42
  "devDependencies": {
39
- "@commitlint/cli": "^20.1.0",
40
- "@commitlint/config-conventional": "^20.0.0",
43
+ "@commitlint/cli": "^20.2.0",
44
+ "@commitlint/config-conventional": "^20.2.0",
41
45
  "@mantine/core": "^8.3.10",
42
46
  "@mantine/form": "^8.3.10",
43
47
  "@mantine/hooks": "^8.3.10",
44
48
  "@mantine/notifications": "^8.3.10",
45
- "@refinedev/core": "^5.0.5",
49
+ "@refinedev/core": "^5.0.7",
46
50
  "@refinedev/react-router": "^2.0.3",
47
51
  "@refinedev/react-table": "^6.0.1",
48
52
  "@refinedev/simple-rest": "^6.0.1",
49
- "@storybook/addon-storysource": "^8.6.14",
50
- "@storybook/addon-themes": "^9.1.15",
51
- "@storybook/react": "^9.1.15",
52
- "@storybook/react-vite": "^9.1.15",
53
- "@tabler/icons-react": "^3.35.0",
54
- "@tanstack/react-query": "^5.90.5",
53
+ "@storybook/addon-docs": "^10.1.10",
54
+ "@storybook/addon-themes": "^10.1.10",
55
+ "@storybook/react": "^10.1.10",
56
+ "@storybook/react-vite": "^10.1.10",
57
+ "@tabler/icons-react": "^3.36.0",
58
+ "@tanstack/react-query": "^5.90.12",
55
59
  "@tanstack/react-table": "^8.21.3",
56
60
  "@types/node": "^24.9.1",
57
- "@types/react": "^19.2.2",
58
- "@types/react-dom": "^19.2.2",
59
- "@vitejs/plugin-react": "^5.1.0",
61
+ "@types/react": "^19.2.7",
62
+ "@types/react-dom": "^19.2.3",
63
+ "@vitejs/plugin-react": "^5.1.2",
60
64
  "dayjs": "^1.11.19",
61
65
  "husky": "^9.1.7",
62
66
  "postcss": "^8.5.6",
63
67
  "postcss-preset-mantine": "1.18.0",
64
68
  "postcss-simple-vars": "^7.0.1",
65
- "react": "^19.2.0",
66
- "react-dom": "^19.2.0",
67
- "react-router": "^7.9.4",
68
- "react-router-dom": "^7.9.4",
69
+ "react": "^19.2.3",
70
+ "react-dom": "^19.2.3",
71
+ "react-router": "^7.11.0",
72
+ "react-router-dom": "^7.11.0",
69
73
  "semantic-release": "^25.0.2",
70
- "storybook": "^9.1.15",
71
- "tsdown": "^0.15.10",
74
+ "storybook": "^10.1.10",
75
+ "tsdown": "^0.18.1",
72
76
  "typescript": "^5.9.3",
73
- "vite": "^7.1.12",
74
- "vite-tsconfig-paths": "^5.1.4"
77
+ "vite": "^7.3.0",
78
+ "vite-tsconfig-paths": "^6.0.2"
75
79
  },
76
- "packageManager": "yarn@4.9.4",
80
+ "packageManager": "yarn@4.12.0",
77
81
  "files": [
78
82
  "dist",
79
83
  "src",
@@ -89,9 +93,5 @@
89
93
  "prerelease": true
90
94
  }
91
95
  ]
92
- },
93
- "dependencies": {
94
- "@refinedev/ui-types": "^2.0.1"
95
- },
96
- "version": "1.6.1"
96
+ }
97
97
  }
@@ -82,3 +82,17 @@ export const Default = () => {
82
82
  );
83
83
  }
84
84
 
85
+ export const LongTitle = () => (
86
+ <Show
87
+ title="Show user profile details with a very long resource title that should stay on one line without pushing the layout out of place"
88
+ headerButtons={() =>
89
+ <ButtonGroup>
90
+ <RefreshButton />
91
+ <CloneButton />
92
+ <EditButton />
93
+ <DeleteButton />
94
+ </ButtonGroup>
95
+ }
96
+ >
97
+ </Show>
98
+ );
@@ -186,12 +186,12 @@ export const Show: React.FC<ShowProps> = (props) => {
186
186
  return (
187
187
  <Card p="md" {...wrapperProps}>
188
188
  <LoadingOverlay visible={loadingOverlayVisible} />
189
- <Group justify="space-between" align="center" {...headerProps}>
189
+ <Group justify="space-between" align="center" wrap="nowrap" {...headerProps}>
190
190
  <Stack gap="xs">
191
191
  {breadcrumbComponent}
192
- <Group gap="xs">
192
+ <Group gap="xs" wrap="nowrap">
193
193
  {buttonBack}
194
- <Title order={3}>
194
+ <Title order={3} lineClamp={2} textWrap="balance">
195
195
  {title ??
196
196
  translate(
197
197
  `${identifier}.titles.show`,
@@ -203,7 +203,7 @@ export const Show: React.FC<ShowProps> = (props) => {
203
203
  </Title>
204
204
  </Group>
205
205
  </Stack>
206
- <Group {...headerButtonProps}>{headerButtons}</Group>
206
+ <Group {...headerButtonProps} wrap="nowrap">{headerButtons}</Group>
207
207
  </Group>
208
208
  <Box pt="sm" {...contentProps}>
209
209
  {children}
@@ -0,0 +1,42 @@
1
+ import { Image } from "@mantine/core";
2
+ import type { Meta } from "@storybook/react";
3
+ import { LayoutMinimal } from "./LayoutMinimal";
4
+
5
+ export default {
6
+ title: "Components/LayoutMinimal",
7
+ component: LayoutMinimal,
8
+ } satisfies Meta<typeof LayoutMinimal>;
9
+
10
+ export const FullLayout = () => <LayoutMinimal>Hello World</LayoutMinimal>;
11
+
12
+ export const WithLocaleChange = () => (
13
+ <LayoutMinimal
14
+ locales={[
15
+ {
16
+ label: "English",
17
+ lang: "en",
18
+ icon: <Image h={18} src="https://flagsapi.com/US/flat/64.png" />,
19
+ },
20
+ {
21
+ label: "Deutsch",
22
+ lang: "de",
23
+ icon: <Image h={18} src="https://flagsapi.com/DE/flat/64.png" />,
24
+ },
25
+ ]}
26
+ >
27
+ Hello World
28
+ </LayoutMinimal>
29
+ );
30
+
31
+ export const WithFooter = () => (
32
+ <LayoutMinimal
33
+ footer={
34
+ <div style={{ padding: 12, textAlign: "center" }}>
35
+ Footer content
36
+ </div>
37
+ }
38
+ footerProps={{ height: 48, withBorder: true }}
39
+ >
40
+ Hello World
41
+ </LayoutMinimal>
42
+ );
@@ -0,0 +1,365 @@
1
+ import {
2
+ ActionIcon,
3
+ Anchor,
4
+ AppShell,
5
+ type AppShellFooterProps,
6
+ type AppShellHeaderProps,
7
+ type AppShellMainProps,
8
+ type AppShellNavbarConfiguration,
9
+ type AppShellNavbarProps,
10
+ type AppShellProps,
11
+ type AppShellSectionProps,
12
+ Avatar,
13
+ Burger,
14
+ Group,
15
+ Menu,
16
+ NavLink,
17
+ ScrollArea,
18
+ Stack,
19
+ Text,
20
+ Tooltip,
21
+ useComputedColorScheme,
22
+ useMantineColorScheme
23
+ } from "@mantine/core";
24
+ import { useDisclosure } from "@mantine/hooks";
25
+ import {
26
+ type BaseRecord,
27
+ CanAccess,
28
+ Link,
29
+ type TreeMenuItem,
30
+ useGetIdentity,
31
+ useLogout,
32
+ useMenu,
33
+ useNavigation,
34
+ useRefineOptions,
35
+ useTranslate,
36
+ useTranslation,
37
+ } from "@refinedev/core";
38
+ import { IconCheck, IconLanguage, IconList, IconLogout, IconMoon, IconSun } from "@tabler/icons-react";
39
+ import { type ReactNode, useCallback } from "react";
40
+ import type { LayoutLocale } from "./Layout";
41
+
42
+ interface LayoutProps {
43
+ children: ReactNode;
44
+ shellProps?: AppShellProps;
45
+ headerProps?: AppShellHeaderProps;
46
+ navbarProps?: AppShellNavbarProps;
47
+ navbarConfiguration?: Partial<AppShellNavbarConfiguration>;
48
+ navbarMenuProps?: AppShellSectionProps;
49
+ navbarFooterProps?: AppShellSectionProps;
50
+ mainProps?: AppShellMainProps;
51
+ hideNavbar?: boolean;
52
+ footer?: ReactNode;
53
+ footerProps?: AppShellFooterProps;
54
+ locales?: LayoutLocale[];
55
+ renderHeader?: (toggle: ()=> void) => ReactNode;
56
+ renderMenu?: (params: ReturnType<typeof useMenu>) => ReactNode;
57
+ renderIdentity?: <T extends BaseRecord>(identity: T, logout: () => void) => ReactNode;
58
+ }
59
+
60
+ export const LayoutMinimal: React.FC<LayoutProps> = (p) => {
61
+ const [opened, { toggle, close }] = useDisclosure();
62
+ const { title: { icon: defaultIcon, text: defaultText } = {} } =
63
+ useRefineOptions();
64
+ const { data: identity } = useGetIdentity();
65
+ const menu = useMenu();
66
+ const { mutate: logout } = useLogout();
67
+ const { setColorScheme } = useMantineColorScheme();
68
+ const computedColorScheme = useComputedColorScheme('light', { getInitialValueInEffect: true });
69
+ const translate = useTranslate();
70
+ const languageLabel = translate("layout.navbar.languageLabel", "Language");
71
+ const colorSchemeLabel = computedColorScheme === 'dark'
72
+ ? translate("layout.header.lightMode", 'Light mode')
73
+ : translate("layout.header.darkMode", 'Dark mode');
74
+
75
+ const handleLogout = useCallback(() => {
76
+ logout();
77
+ }, [logout]);
78
+
79
+ return (
80
+ <AppShell
81
+ header={{
82
+ height: { base: 60, sm: 0 },
83
+ }}
84
+ navbar={{
85
+ width: { base: 260, sm: 80 },
86
+ breakpoint: "sm",
87
+ collapsed: {
88
+ mobile: !opened || p.hideNavbar,
89
+ desktop: p.hideNavbar,
90
+ },
91
+ ...p.navbarConfiguration,
92
+ }}
93
+ padding="md"
94
+ {...p.shellProps}
95
+ >
96
+ <AppShell.Header p="md" {...p.headerProps} hiddenFrom="sm">
97
+ {p.renderHeader ? (
98
+ p.renderHeader(toggle)
99
+ ) : (
100
+ <Group justify="space-between">
101
+ <Group>
102
+ <Burger opened={opened} onClick={toggle} size="sm" hidden={p.hideNavbar} />
103
+ <Anchor
104
+ component={Link as React.FC<{ to: string, children: ReactNode }>}
105
+ to="/"
106
+ style={{all: "unset"}}
107
+ >
108
+ <Group>
109
+ {defaultIcon}
110
+ {defaultText ? <Text>{defaultText}</Text> : null}
111
+ </Group>
112
+ </Anchor>
113
+ </Group>
114
+ </Group>
115
+ )}
116
+ </AppShell.Header>
117
+
118
+ <AppShell.Navbar {...p.navbarProps}>
119
+ <AppShell.Section visibleFrom="sm" p="md">
120
+ <Stack align="center" gap="xs">
121
+ <Tooltip label={defaultText ?? translate("layout.navbar.homeLabel", "Home")} position="right" transitionProps={{ duration: 0 }}>
122
+ <Anchor
123
+ component={Link as React.FC<{ to: string, children: ReactNode }>}
124
+ to="/"
125
+ style={{all: "unset"}}
126
+ >
127
+ <Stack align="center" gap={2}>
128
+ {defaultIcon}
129
+ </Stack>
130
+ </Anchor>
131
+ </Tooltip>
132
+ </Stack>
133
+ </AppShell.Section>
134
+
135
+ <AppShell.Section
136
+ component={ScrollArea}
137
+ grow
138
+ mt="xs"
139
+ {...p.navbarMenuProps}
140
+ visibleFrom="sm"
141
+ >
142
+ {p.renderMenu ? (
143
+ p.renderMenu(menu)
144
+ ) : (
145
+ <Stack align="center" gap={0}>
146
+ {menu.menuItems.map((item) => (
147
+ <MenuItemIcon
148
+ item={item}
149
+ key={item.key}
150
+ selectedKey={menu.selectedKey}
151
+ onClick={close}
152
+ />
153
+ ))}
154
+ </Stack>
155
+ )}
156
+ </AppShell.Section>
157
+
158
+ <AppShell.Section
159
+ component={ScrollArea}
160
+ grow
161
+ mt="xs"
162
+ {...p.navbarMenuProps}
163
+ hiddenFrom="sm"
164
+ >
165
+ {p.renderMenu ? (
166
+ p.renderMenu(menu)
167
+ ) : (
168
+ <Stack gap="xs">
169
+ {menu.menuItems.map((item) => (
170
+ <MenuItemFull
171
+ item={item}
172
+ key={item.key}
173
+ selectedKey={menu.selectedKey}
174
+ onClick={close}
175
+ />
176
+ ))}
177
+ </Stack>
178
+ )}
179
+ </AppShell.Section>
180
+
181
+ <AppShell.Section pb="sm" {...p.navbarFooterProps} visibleFrom="sm">
182
+ {p.renderIdentity ? (
183
+ p.renderIdentity(identity, handleLogout)
184
+ ) : (
185
+ <Stack align="center" gap="xs">
186
+ {p.locales && (
187
+ <Locales locales={p.locales} variant="icon" label={languageLabel} />
188
+ )}
189
+ <Tooltip label={colorSchemeLabel} position="right" transitionProps={{ duration: 0 }}>
190
+ <ActionIcon
191
+ onClick={() => setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')}
192
+ aria-label={translate("layout.header.toggleColorScheme", "Toggle color scheme")}
193
+ variant="subtle"
194
+ size="xl"
195
+ >
196
+ {computedColorScheme === "light"
197
+ ? <IconSun stroke={1.5} size={22} />
198
+ : <IconMoon stroke={1.5} size={22} />}
199
+ </ActionIcon>
200
+ </Tooltip>
201
+ <Tooltip label={translate("layout.navbar.signOutLabel", "Sign out")} position="right" transitionProps={{ duration: 0 }}>
202
+ <ActionIcon
203
+ onClick={handleLogout}
204
+ aria-label={translate("layout.navbar.signOutLabel", "Sign out")}
205
+ variant="subtle"
206
+ size="xl"
207
+ >
208
+ <IconLogout size={20} />
209
+ </ActionIcon>
210
+ </Tooltip>
211
+ </Stack>
212
+ )}
213
+ </AppShell.Section>
214
+
215
+ <AppShell.Section pb="sm" {...p.navbarFooterProps} hiddenFrom="sm">
216
+ <Stack gap="xs">
217
+ {p.locales && (
218
+ <Locales locales={p.locales} variant="full" label={languageLabel} />
219
+ )}
220
+ <NavLink
221
+ onClick={() => setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')}
222
+ leftSection={computedColorScheme === "light"
223
+ ? <IconSun stroke={1.5} size={18} />
224
+ : <IconMoon stroke={1.5} size={18} />}
225
+ label={colorSchemeLabel}
226
+ variant="subtle"
227
+ />
228
+ {p.renderIdentity ? (
229
+ p.renderIdentity(identity, handleLogout)
230
+ ) : (
231
+ <NavLink
232
+ onClick={handleLogout}
233
+ leftSection={identity?.avatar ? <Avatar src={identity.avatar} /> : <IconLogout size={18} />}
234
+ label={translate("layout.navbar.signOutLabel", "Sign out")}
235
+ description={
236
+ identity?.email
237
+ ? translate("layout.navbar.signOutDescription", { email: identity.email }, `Signed in as ${identity.email}`)
238
+ : undefined
239
+ }
240
+ variant="filled"
241
+ active
242
+ />
243
+ )}
244
+ </Stack>
245
+ </AppShell.Section>
246
+ </AppShell.Navbar>
247
+
248
+ <AppShell.Main {...p.mainProps}>
249
+ {p.children}
250
+ </AppShell.Main>
251
+
252
+ <AppShell.Footer {...p.footerProps}>
253
+ {p.footer}
254
+ </AppShell.Footer>
255
+ </AppShell>
256
+ );
257
+ };
258
+
259
+ const MenuItemIcon = (p: {
260
+ item: TreeMenuItem;
261
+ selectedKey?: string;
262
+ onClick: () => void;
263
+ }) => {
264
+ const { listUrl } = useNavigation();
265
+ const isSelected = p.item.key === p.selectedKey;
266
+ const label = p.item.meta?.label ?? p.item.label ?? p.item.name;
267
+
268
+ return (
269
+ <CanAccess
270
+ key={p.item.key}
271
+ resource={p.item.name}
272
+ action="list"
273
+ params={{
274
+ resource: p.item,
275
+ }}
276
+ >
277
+ <Tooltip label={label} position="right" transitionProps={{ duration: 0 }}>
278
+ <ActionIcon
279
+ key={p.item.key}
280
+ component={Link as React.FC<{ to: string, onClick: () => void }>}
281
+ to={listUrl(p.item.name)}
282
+ variant={isSelected ? "filled" : "subtle"}
283
+ size="xl"
284
+ mb="xs"
285
+ bd={0}
286
+ onClick={p.onClick}
287
+ >
288
+ {p.item.meta?.icon ?? <IconList size={20} />}
289
+ </ActionIcon>
290
+ </Tooltip>
291
+ </CanAccess>
292
+ );
293
+ };
294
+
295
+ const MenuItemFull = (p: {
296
+ item: TreeMenuItem;
297
+ selectedKey?: string;
298
+ onClick: () => void;
299
+ }) => {
300
+ const { listUrl } = useNavigation();
301
+ const isSelected = p.item.key === p.selectedKey;
302
+ const label = p.item.meta?.label ?? p.item.label ?? p.item.name;
303
+
304
+ return (
305
+ <CanAccess
306
+ key={p.item.key}
307
+ resource={p.item.name}
308
+ action="list"
309
+ params={{
310
+ resource: p.item,
311
+ }}
312
+ >
313
+ <NavLink
314
+ key={p.item.key}
315
+ label={label}
316
+ leftSection={p.item.meta?.icon ?? <IconList size={18} />}
317
+ active={isSelected}
318
+ component={Link as React.FC<{ to: string, onClick: () => void }>}
319
+ to={listUrl(p.item.name)}
320
+ onClick={p.onClick}
321
+ />
322
+ </CanAccess>
323
+ );
324
+ };
325
+
326
+ const Locales = (p: {
327
+ locales: LayoutLocale[];
328
+ variant: "icon" | "full";
329
+ label: string;
330
+ }) => {
331
+ const { changeLocale, getLocale } = useTranslation();
332
+ const locale = getLocale();
333
+
334
+ return (
335
+ <Menu shadow="md" width={200}>
336
+ <Menu.Target>
337
+ {p.variant === "icon" ? (
338
+ <Tooltip label={p.label} position="right" transitionProps={{ duration: 0 }}>
339
+ <ActionIcon aria-label={p.label} variant="subtle" size="xl">
340
+ <IconLanguage />
341
+ </ActionIcon>
342
+ </Tooltip>
343
+ ) : (
344
+ <NavLink
345
+ label={p.label}
346
+ leftSection={<IconLanguage size={18} />}
347
+ component="button"
348
+ />
349
+ )}
350
+ </Menu.Target>
351
+ <Menu.Dropdown>
352
+ {p.locales.map(({label, lang, icon}) => (
353
+ <Menu.Item
354
+ key={lang}
355
+ leftSection={icon}
356
+ onClick={() => changeLocale(lang)}
357
+ rightSection={lang === locale ? <IconCheck size={14} /> : undefined}
358
+ >
359
+ {label}
360
+ </Menu.Item>
361
+ ))}
362
+ </Menu.Dropdown>
363
+ </Menu>
364
+ );
365
+ }
package/src/index.ts CHANGED
@@ -28,6 +28,7 @@ export * from "@/components/fields/PhoneField";
28
28
  export * from "@/components/fields/UrlField";
29
29
  // layout
30
30
  export * from "@/components/layout/Layout";
31
+ export * from "@/components/layout/LayoutMinimal";
31
32
  // notification
32
33
  export * from "@/components/notification/AutoSaveIndicator";
33
34
  export * from "@/components/notification/Message";
@@ -42,7 +43,9 @@ export * from "@/hooks/useOtp";
42
43
  export * from "@/pages/auth/ForgotPasswordPage";
43
44
  export * from "@/pages/auth/LoginPage";
44
45
  export * from "@/pages/auth/RegisterPage";
46
+ export * from "@/pages/auth/RegistrationVerificationPage";
45
47
  export * from "@/pages/auth/UpdatePasswordPage";
48
+ export * from "@/pages/auth/DefaultTitle";
46
49
  export * from "@/pages/NotFound";
47
50
  // providers
48
51
  export type * from "@/providers/authProvider";
@@ -0,0 +1,15 @@
1
+ import { Stack } from "@mantine/core";
2
+ import type { Meta } from "@storybook/react";
3
+ import { DefaultTitle } from "./DefaultTitle";
4
+
5
+ export default {
6
+ title: "Auth/DefaultTitle",
7
+ component: DefaultTitle,
8
+ decorators: (Story) => (
9
+ <Stack h="100vh" align="center" justify="center">
10
+ <Story />
11
+ </Stack>
12
+ ),
13
+ } satisfies Meta<typeof DefaultTitle>;
14
+
15
+ export const Basic = () => <DefaultTitle />;
@@ -49,6 +49,8 @@ export type LoginPageProps = {
49
49
  registerLink?: string;
50
50
  forgotPasswordLink?: string;
51
51
  validate?: FormValidateInput<LoginForm>;
52
+ onBeforeLogin?: (args: LoginArgs) => void | Promise<void>;
53
+ onBeforeProviderLogin?: (args: LoginArgs) => void | Promise<void>;
52
54
  // customization
53
55
  wrapperProps?: StackProps;
54
56
  scrollAreaProps?: ScrollAreaProps;
@@ -98,22 +100,26 @@ export const LoginPage: React.FC<LoginPageProps> = (p) => {
98
100
  },
99
101
  });
100
102
 
101
- const handleProviderLogin = useCallback((provider: OAuthProvider) => {
102
- login.mutate({
103
+ const handleProviderLogin = useCallback(async (provider: OAuthProvider) => {
104
+ const loginArgs = {
103
105
  providerName: provider.name,
104
106
  translate,
105
107
  ...p.mutationVariables
106
- });
107
- }, [login, translate, p.mutationVariables]);
108
+ };
109
+ await p.onBeforeProviderLogin?.(loginArgs);
110
+ login.mutate(loginArgs);
111
+ }, [login, translate, p.mutationVariables, p.onBeforeProviderLogin]);
108
112
 
109
- const handleLogin = onSubmit(({ email, password }) => {
110
- login.mutate({
113
+ const handleLogin = onSubmit(async ({ email, password }) => {
114
+ const loginArgs = {
111
115
  email,
112
116
  password,
113
117
  otpHandler: p.otpHandler,
114
118
  translate,
115
119
  ...p.mutationVariables,
116
- });
120
+ };
121
+ await p.onBeforeLogin?.(loginArgs);
122
+ login.mutate(loginArgs);
117
123
  });
118
124
 
119
125
  return (
@@ -0,0 +1,12 @@
1
+ import { RegistrationVerificationPage } from "./RegistrationVerificationPage";
2
+
3
+ export default {
4
+ title: "Auth/RegistrationVerificationPage",
5
+ component: RegistrationVerificationPage,
6
+ };
7
+
8
+ export const Default = () => <RegistrationVerificationPage />;
9
+
10
+ export const WithLoginLink = () => (
11
+ <RegistrationVerificationPage loginLink="/login" />
12
+ );
@@ -0,0 +1,72 @@
1
+ import {
2
+ Button,
3
+ type ButtonProps,
4
+ Card,
5
+ type CardProps,
6
+ ScrollArea,
7
+ type ScrollAreaProps,
8
+ Stack,
9
+ type StackProps,
10
+ Text,
11
+ Title,
12
+ } from "@mantine/core";
13
+ import { Link, useTranslate } from "@refinedev/core";
14
+ import type { ReactNode } from "react";
15
+ import { DefaultTitle } from "./DefaultTitle";
16
+
17
+ export interface RegistrationVerificationPageProps {
18
+ loginLink?: string;
19
+ // props
20
+ wrapperProps?: StackProps;
21
+ scrollAreaProps?: ScrollAreaProps;
22
+ cardProps?: CardProps;
23
+ buttonProps?: ButtonProps;
24
+ // components
25
+ icon?: ReactNode;
26
+ title?: ReactNode;
27
+ description?: ReactNode;
28
+ }
29
+
30
+ export const RegistrationVerificationPage: React.FC<
31
+ RegistrationVerificationPageProps
32
+ > = (p) => {
33
+ const translate = useTranslate();
34
+
35
+ return (
36
+ <Stack h="100vh" align="center" justify="center" {...p.wrapperProps}>
37
+ <ScrollArea type="never" {...p.scrollAreaProps}>
38
+ {p.icon ?? <DefaultTitle />}
39
+ <Card shadow="sm" padding="lg" radius="md" withBorder {...p.cardProps}>
40
+ {p.title ?? (
41
+ <Title order={5} mb="sm" ta="center">
42
+ {translate(
43
+ "pages.registerVerification.title",
44
+ "Thank you for your registration",
45
+ )}
46
+ </Title>
47
+ )}
48
+ {p.description ?? (
49
+ <Text size="sm" ta="center" c="dimmed">
50
+ {translate(
51
+ "pages.registerVerification.description",
52
+ "You will get an email where you can confirm your registration.",
53
+ )}
54
+ </Text>
55
+ )}
56
+ {p.loginLink && (
57
+ <Button
58
+ component={Link as React.FC<{ to: string }>}
59
+ to={p.loginLink}
60
+ fullWidth
61
+ mt="lg"
62
+ variant="light"
63
+ {...p.buttonProps}
64
+ >
65
+ {translate("pages.registerVerification.login", "Back to login")}
66
+ </Button>
67
+ )}
68
+ </Card>
69
+ </ScrollArea>
70
+ </Stack>
71
+ );
72
+ };