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 +28 -3
- package/dist/index.js +298 -10
- package/package.json +1 -1
- package/src/components/crud/Show.story.tsx +14 -0
- package/src/components/crud/Show.tsx +4 -4
- package/src/components/layout/LayoutMinimal.story.tsx +42 -0
- package/src/components/layout/LayoutMinimal.tsx +365 -0
- package/src/index.ts +2 -0
- package/src/pages/auth/DefaultTitle.story.tsx +15 -0
- package/src/pages/auth/LoginPage.tsx +13 -7
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
|
-
|
|
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
|
-
|
|
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
|
@@ -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
|
-
|
|
103
|
+
const handleProviderLogin = useCallback(async (provider: OAuthProvider) => {
|
|
104
|
+
const loginArgs = {
|
|
103
105
|
providerName: provider.name,
|
|
104
106
|
translate,
|
|
105
107
|
...p.mutationVariables
|
|
106
|
-
}
|
|
107
|
-
|
|
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
|
-
|
|
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 (
|