refine-mantine 1.7.0-dev.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/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;
@@ -411,6 +433,9 @@ interface UpdatePasswordPageProps {
411
433
  }
412
434
  declare const UpdatePasswordPage: React.FC<UpdatePasswordPageProps>;
413
435
  //#endregion
436
+ //#region src/pages/auth/DefaultTitle.d.ts
437
+ declare const DefaultTitle: () => react_jsx_runtime0.JSX.Element;
438
+ //#endregion
414
439
  //#region src/pages/NotFound.d.ts
415
440
  interface NotFoundProps {
416
441
  returnTo: string;
@@ -423,4 +448,4 @@ declare const authProvider: AuthProvider;
423
448
  //#region src/providers/notificationProvider.d.ts
424
449
  declare const notificationProvider: () => NotificationProvider;
425
450
  //#endregion
426
- 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, RegistrationVerificationPage, RegistrationVerificationPageProps, 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, {
@@ -1626,25 +1909,30 @@ const LoginPage = (p) => {
1626
1909
  ...p.validate
1627
1910
  }
1628
1911
  });
1629
- const handleProviderLogin = useCallback((provider) => {
1630
- login.mutate({
1912
+ const handleProviderLogin = useCallback(async (provider) => {
1913
+ const loginArgs = {
1631
1914
  providerName: provider.name,
1632
1915
  translate,
1633
1916
  ...p.mutationVariables
1634
- });
1917
+ };
1918
+ await p.onBeforeProviderLogin?.(loginArgs);
1919
+ login.mutate(loginArgs);
1635
1920
  }, [
1636
1921
  login,
1637
1922
  translate,
1638
- p.mutationVariables
1923
+ p.mutationVariables,
1924
+ p.onBeforeProviderLogin
1639
1925
  ]);
1640
- const handleLogin = onSubmit(({ email, password }) => {
1641
- login.mutate({
1926
+ const handleLogin = onSubmit(async ({ email, password }) => {
1927
+ const loginArgs = {
1642
1928
  email,
1643
1929
  password,
1644
1930
  otpHandler: p.otpHandler,
1645
1931
  translate,
1646
1932
  ...p.mutationVariables
1647
- });
1933
+ };
1934
+ await p.onBeforeLogin?.(loginArgs);
1935
+ login.mutate(loginArgs);
1648
1936
  });
1649
1937
  return /* @__PURE__ */ jsx(Stack, {
1650
1938
  h: "100vh",
@@ -2140,4 +2428,4 @@ const notificationProvider = () => {
2140
2428
  };
2141
2429
 
2142
2430
  //#endregion
2143
- 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, RegistrationVerificationPage, 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,6 +1,6 @@
1
1
  {
2
2
  "name": "refine-mantine",
3
- "version": "1.7.0-dev.1",
3
+ "version": "1.7.0-dev.10",
4
4
  "type": "module",
5
5
  "exports": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -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";
@@ -44,6 +45,7 @@ export * from "@/pages/auth/LoginPage";
44
45
  export * from "@/pages/auth/RegisterPage";
45
46
  export * from "@/pages/auth/RegistrationVerificationPage";
46
47
  export * from "@/pages/auth/UpdatePasswordPage";
48
+ export * from "@/pages/auth/DefaultTitle";
47
49
  export * from "@/pages/NotFound";
48
50
  // providers
49
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 (