@nocobase/portal-template-default 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/.env.example +10 -0
  2. package/Dockerfile +36 -0
  3. package/README.MD +50 -0
  4. package/components.json +25 -0
  5. package/eslint.config.js +28 -0
  6. package/index.html +39 -0
  7. package/package.json +84 -0
  8. package/pnpm-workspace.yaml +2 -0
  9. package/public/favicon.ico +0 -0
  10. package/public/logo-dark.png +0 -0
  11. package/public/logo-mark-dark.png +0 -0
  12. package/public/logo-mark.png +0 -0
  13. package/public/logo.png +0 -0
  14. package/src/App.css +158 -0
  15. package/src/App.tsx +151 -0
  16. package/src/components/app-shell/brand.tsx +70 -0
  17. package/src/components/app-shell/breadcrumb.tsx +81 -0
  18. package/src/components/app-shell/document-title-handler.tsx +169 -0
  19. package/src/components/app-shell/error-component.tsx +134 -0
  20. package/src/components/app-shell/header.tsx +127 -0
  21. package/src/components/app-shell/layout.tsx +39 -0
  22. package/src/components/app-shell/loading-overlay.tsx +36 -0
  23. package/src/components/app-shell/sidebar.tsx +351 -0
  24. package/src/components/app-shell/user-avatar.tsx +42 -0
  25. package/src/components/app-shell/user-info.tsx +53 -0
  26. package/src/components/auth/auth-layout.tsx +82 -0
  27. package/src/components/auth/forgot-password-form.tsx +58 -0
  28. package/src/components/auth/input-password.tsx +38 -0
  29. package/src/components/auth/sign-in-form.tsx +111 -0
  30. package/src/components/auth/sign-up-form.tsx +127 -0
  31. package/src/components/data-table/data-table-filter.tsx +994 -0
  32. package/src/components/data-table/data-table-pagination.tsx +146 -0
  33. package/src/components/data-table/data-table-sorter.tsx +47 -0
  34. package/src/components/data-table/data-table.tsx +321 -0
  35. package/src/components/notifications/toaster.tsx +23 -0
  36. package/src/components/notifications/undoable-notification.tsx +84 -0
  37. package/src/components/notifications/use-notification-provider.tsx +62 -0
  38. package/src/components/resources/buttons/clone.tsx +81 -0
  39. package/src/components/resources/buttons/create.tsx +70 -0
  40. package/src/components/resources/buttons/delete.tsx +113 -0
  41. package/src/components/resources/buttons/edit.tsx +81 -0
  42. package/src/components/resources/buttons/list.tsx +70 -0
  43. package/src/components/resources/buttons/refresh.tsx +81 -0
  44. package/src/components/resources/buttons/show.tsx +81 -0
  45. package/src/components/resources/status-badge.tsx +37 -0
  46. package/src/components/resources/views/create-view.tsx +81 -0
  47. package/src/components/resources/views/edit-view.tsx +104 -0
  48. package/src/components/resources/views/list-view.tsx +86 -0
  49. package/src/components/resources/views/show-view.tsx +108 -0
  50. package/src/components/theme/theme-provider.tsx +71 -0
  51. package/src/components/theme/theme-select.tsx +97 -0
  52. package/src/components/theme/theme-toggle.tsx +66 -0
  53. package/src/components/ui/accordion.tsx +72 -0
  54. package/src/components/ui/alert-dialog.tsx +185 -0
  55. package/src/components/ui/alert.tsx +76 -0
  56. package/src/components/ui/aspect-ratio.tsx +22 -0
  57. package/src/components/ui/avatar.tsx +107 -0
  58. package/src/components/ui/badge.tsx +52 -0
  59. package/src/components/ui/breadcrumb.tsx +125 -0
  60. package/src/components/ui/button.tsx +58 -0
  61. package/src/components/ui/calendar.tsx +221 -0
  62. package/src/components/ui/card.tsx +103 -0
  63. package/src/components/ui/carousel.tsx +240 -0
  64. package/src/components/ui/chart.tsx +373 -0
  65. package/src/components/ui/checkbox.tsx +29 -0
  66. package/src/components/ui/collapsible.tsx +19 -0
  67. package/src/components/ui/command.tsx +192 -0
  68. package/src/components/ui/context-menu.tsx +271 -0
  69. package/src/components/ui/dialog.tsx +160 -0
  70. package/src/components/ui/drawer.tsx +226 -0
  71. package/src/components/ui/dropdown-menu.tsx +268 -0
  72. package/src/components/ui/form.tsx +174 -0
  73. package/src/components/ui/hover-card.tsx +49 -0
  74. package/src/components/ui/input-group.tsx +158 -0
  75. package/src/components/ui/input-otp.tsx +87 -0
  76. package/src/components/ui/input.tsx +20 -0
  77. package/src/components/ui/label.tsx +18 -0
  78. package/src/components/ui/menubar.tsx +278 -0
  79. package/src/components/ui/navigation-menu.tsx +168 -0
  80. package/src/components/ui/pagination.tsx +130 -0
  81. package/src/components/ui/popover.tsx +91 -0
  82. package/src/components/ui/progress.tsx +81 -0
  83. package/src/components/ui/radio-group.tsx +38 -0
  84. package/src/components/ui/resizable.tsx +48 -0
  85. package/src/components/ui/scroll-area.tsx +55 -0
  86. package/src/components/ui/select.tsx +199 -0
  87. package/src/components/ui/separator.tsx +25 -0
  88. package/src/components/ui/sheet.tsx +136 -0
  89. package/src/components/ui/sidebar.tsx +723 -0
  90. package/src/components/ui/skeleton.tsx +13 -0
  91. package/src/components/ui/slider.tsx +52 -0
  92. package/src/components/ui/sonner.tsx +47 -0
  93. package/src/components/ui/switch.tsx +32 -0
  94. package/src/components/ui/table.tsx +114 -0
  95. package/src/components/ui/tabs.tsx +82 -0
  96. package/src/components/ui/textarea.tsx +18 -0
  97. package/src/components/ui/toggle-group.tsx +87 -0
  98. package/src/components/ui/toggle.tsx +43 -0
  99. package/src/components/ui/tooltip.tsx +66 -0
  100. package/src/hooks/use-mobile.ts +19 -0
  101. package/src/index.tsx +17 -0
  102. package/src/lib/utils.ts +19 -0
  103. package/src/pages/blog-posts/create.tsx +177 -0
  104. package/src/pages/blog-posts/edit.tsx +197 -0
  105. package/src/pages/blog-posts/index.ts +4 -0
  106. package/src/pages/blog-posts/list.tsx +131 -0
  107. package/src/pages/blog-posts/show.tsx +81 -0
  108. package/src/pages/categories/create.tsx +73 -0
  109. package/src/pages/categories/edit.tsx +73 -0
  110. package/src/pages/categories/index.ts +4 -0
  111. package/src/pages/categories/list.tsx +86 -0
  112. package/src/pages/categories/show.tsx +37 -0
  113. package/src/pages/forgot-password/index.tsx +6 -0
  114. package/src/pages/forgotPassword/index.tsx +5 -0
  115. package/src/pages/login/index.tsx +5 -0
  116. package/src/pages/register/index.tsx +5 -0
  117. package/src/providers/auth.ts +204 -0
  118. package/src/providers/constants.ts +75 -0
  119. package/src/providers/data.ts +232 -0
  120. package/src/vite-env.d.ts +1 -0
  121. package/tsconfig.json +25 -0
  122. package/tsconfig.node.json +21 -0
  123. package/vite.config.ts +74 -0
@@ -0,0 +1,81 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { type BaseKey, useCloneButton } from "@refinedev/core";
5
+ import { Copy } from "lucide-react";
6
+ import { Button } from "@/components/ui/button";
7
+
8
+ type CloneButtonProps = {
9
+ /**
10
+ * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.
11
+ * @default Inferred resource name from the route
12
+ */
13
+ resource?: string;
14
+ /**
15
+ * Data item identifier for the actions with the API
16
+ * @default Reads `:id` from the URL
17
+ */
18
+ recordItemId?: BaseKey;
19
+ /**
20
+ * Access Control configuration for the button
21
+ * @default `{ enabled: true, hideIfUnauthorized: false }`
22
+ */
23
+ accessControl?: {
24
+ enabled?: boolean;
25
+ hideIfUnauthorized?: boolean;
26
+ };
27
+ /**
28
+ * `meta` property is used when creating the URL for the related action and path.
29
+ */
30
+ meta?: Record<string, unknown>;
31
+ } & React.ComponentProps<typeof Button>;
32
+
33
+ export const CloneButton = React.forwardRef<
34
+ React.ComponentRef<typeof Button>,
35
+ CloneButtonProps
36
+ >(
37
+ (
38
+ { resource, recordItemId, accessControl, meta, children, onClick, ...rest },
39
+ ref
40
+ ) => {
41
+ const { hidden, disabled, LinkComponent, to, label } = useCloneButton({
42
+ accessControl,
43
+ resource,
44
+ id: recordItemId,
45
+ meta,
46
+ });
47
+
48
+ const isDisabled = disabled || rest.disabled;
49
+ const isHidden = hidden || rest.hidden;
50
+
51
+ if (isHidden) return null;
52
+
53
+ return (
54
+ <Button
55
+ {...rest}
56
+ ref={ref}
57
+ disabled={isDisabled}
58
+ render={<LinkComponent to={to} replace={false} />}
59
+ onClick={(event) => {
60
+ if (isDisabled) {
61
+ event.preventDefault();
62
+ return;
63
+ }
64
+ if (onClick) {
65
+ event.preventDefault();
66
+ onClick(event);
67
+ }
68
+ }}
69
+ >
70
+ {children ?? (
71
+ <div className="flex items-center gap-2 font-semibold">
72
+ <Copy className="h-4 w-4" />
73
+ <span>{label}</span>
74
+ </div>
75
+ )}
76
+ </Button>
77
+ );
78
+ }
79
+ );
80
+
81
+ CloneButton.displayName = "CloneButton";
@@ -0,0 +1,70 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { type BaseKey, useCreateButton } from "@refinedev/core";
5
+ import { Plus } from "lucide-react";
6
+ import { Button } from "@/components/ui/button";
7
+
8
+ type CreateButtonProps = {
9
+ /**
10
+ * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.
11
+ * @default Inferred resource name from the route
12
+ */
13
+ resource?: BaseKey;
14
+ /**
15
+ * Access Control configuration for the button
16
+ * @default `{ enabled: true, hideIfUnauthorized: false }`
17
+ */
18
+ accessControl?: {
19
+ enabled?: boolean;
20
+ hideIfUnauthorized?: boolean;
21
+ };
22
+ /**
23
+ * `meta` property is used when creating the URL for the related action and path.
24
+ */
25
+ meta?: Record<string, unknown>;
26
+ } & React.ComponentProps<typeof Button>;
27
+
28
+ export const CreateButton = React.forwardRef<
29
+ React.ComponentRef<typeof Button>,
30
+ CreateButtonProps
31
+ >(({ resource, accessControl, meta, children, onClick, ...rest }, ref) => {
32
+ const { hidden, disabled, LinkComponent, to, label } = useCreateButton({
33
+ resource,
34
+ accessControl,
35
+ meta,
36
+ });
37
+
38
+ const isDisabled = disabled || rest.disabled;
39
+ const isHidden = hidden || rest.hidden;
40
+
41
+ if (isHidden) return null;
42
+
43
+ return (
44
+ <Button
45
+ {...rest}
46
+ ref={ref}
47
+ disabled={isDisabled}
48
+ render={<LinkComponent to={to} replace={false} />}
49
+ onClick={(event) => {
50
+ if (isDisabled) {
51
+ event.preventDefault();
52
+ return;
53
+ }
54
+ if (onClick) {
55
+ event.preventDefault();
56
+ onClick(event);
57
+ }
58
+ }}
59
+ >
60
+ {children ?? (
61
+ <div className="flex items-center gap-2 font-semibold">
62
+ <Plus className="w-4 h-4" />
63
+ <span>{label ?? "Create"}</span>
64
+ </div>
65
+ )}
66
+ </Button>
67
+ );
68
+ });
69
+
70
+ CreateButton.displayName = "CreateButton";
@@ -0,0 +1,113 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { type BaseKey, useDeleteButton } from "@refinedev/core";
5
+ import { Loader2, Trash } from "lucide-react";
6
+ import { Button } from "@/components/ui/button";
7
+ import {
8
+ Popover,
9
+ PopoverContent,
10
+ PopoverTrigger,
11
+ } from "@/components/ui/popover";
12
+
13
+ type DeleteButtonProps = {
14
+ /**
15
+ * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.
16
+ * @default Inferred resource name from the route
17
+ */
18
+ resource?: string;
19
+ /**
20
+ * Data item identifier for the actions with the API
21
+ * @default Reads `:id` from the URL
22
+ */
23
+ recordItemId?: BaseKey;
24
+ /**
25
+ * Access Control configuration for the button
26
+ * @default `{ enabled: true, hideIfUnauthorized: false }`
27
+ */
28
+ accessControl?: {
29
+ enabled?: boolean;
30
+ hideIfUnauthorized?: boolean;
31
+ };
32
+ /**
33
+ * `meta` property is used when creating the URL for the related action and path.
34
+ */
35
+ meta?: Record<string, unknown>;
36
+ } & React.ComponentProps<typeof Button>;
37
+
38
+ export const DeleteButton = React.forwardRef<
39
+ React.ComponentRef<typeof Button>,
40
+ DeleteButtonProps
41
+ >(({ resource, recordItemId, accessControl, meta, children, ...rest }, ref) => {
42
+ const {
43
+ hidden,
44
+ disabled,
45
+ loading,
46
+ onConfirm,
47
+ label,
48
+ confirmTitle: defaultConfirmTitle,
49
+ confirmOkLabel: defaultConfirmOkLabel,
50
+ cancelLabel: defaultCancelLabel,
51
+ } = useDeleteButton({
52
+ resource,
53
+ id: recordItemId,
54
+ accessControl,
55
+ meta,
56
+ });
57
+ const [open, setOpen] = React.useState(false);
58
+
59
+ const isDisabled = disabled || rest.disabled || loading;
60
+ const isHidden = hidden || rest.hidden;
61
+
62
+ if (isHidden) return null;
63
+
64
+ const confirmCancelText = defaultCancelLabel;
65
+ const confirmOkText = defaultConfirmOkLabel;
66
+ const confirmTitle = defaultConfirmTitle;
67
+
68
+ return (
69
+ <Popover open={open} onOpenChange={setOpen}>
70
+ <PopoverTrigger render={<span className="inline-flex" />}>
71
+ <Button
72
+ variant="destructive"
73
+ {...rest}
74
+ ref={ref}
75
+ disabled={isDisabled}
76
+ >
77
+ {loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
78
+ {children ?? (
79
+ <div className="flex items-center gap-2 font-semibold">
80
+ <Trash className="h-4 w-4" />
81
+ <span>{label}</span>
82
+ </div>
83
+ )}
84
+ </Button>
85
+ </PopoverTrigger>
86
+ <PopoverContent className="w-auto" align="start">
87
+ <div className="flex flex-col gap-2">
88
+ <p className="text-sm">{confirmTitle}</p>
89
+ <div className="flex justify-end gap-2">
90
+ <Button variant="outline" size="sm" onClick={() => setOpen(false)}>
91
+ {confirmCancelText}
92
+ </Button>
93
+ <Button
94
+ variant="destructive"
95
+ size="sm"
96
+ disabled={loading}
97
+ onClick={() => {
98
+ if (typeof onConfirm === "function") {
99
+ onConfirm();
100
+ }
101
+ setOpen(false);
102
+ }}
103
+ >
104
+ {confirmOkText}
105
+ </Button>
106
+ </div>
107
+ </div>
108
+ </PopoverContent>
109
+ </Popover>
110
+ );
111
+ });
112
+
113
+ DeleteButton.displayName = "DeleteButton";
@@ -0,0 +1,81 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { type BaseKey, useEditButton } from "@refinedev/core";
5
+ import { Button } from "@/components/ui/button";
6
+ import { Pencil } from "lucide-react";
7
+
8
+ type EditButtonProps = {
9
+ /**
10
+ * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.
11
+ * @default Inferred resource name from the route
12
+ */
13
+ resource?: string;
14
+ /**
15
+ * Data item identifier for the actions with the API
16
+ * @default Reads `:id` from the URL
17
+ */
18
+ recordItemId?: BaseKey;
19
+ /**
20
+ * Access Control configuration for the button
21
+ * @default `{ enabled: true, hideIfUnauthorized: false }`
22
+ */
23
+ accessControl?: {
24
+ enabled?: boolean;
25
+ hideIfUnauthorized?: boolean;
26
+ };
27
+ /**
28
+ * `meta` property is used when creating the URL for the related action and path.
29
+ */
30
+ meta?: Record<string, unknown>;
31
+ } & React.ComponentProps<typeof Button>;
32
+
33
+ export const EditButton = React.forwardRef<
34
+ React.ComponentRef<typeof Button>,
35
+ EditButtonProps
36
+ >(
37
+ (
38
+ { resource, recordItemId, accessControl, meta, children, onClick, ...rest },
39
+ ref
40
+ ) => {
41
+ const { hidden, disabled, LinkComponent, to, label } = useEditButton({
42
+ resource,
43
+ id: recordItemId,
44
+ accessControl,
45
+ meta,
46
+ });
47
+
48
+ const isDisabled = disabled || rest.disabled;
49
+ const isHidden = hidden || rest.hidden;
50
+
51
+ if (isHidden) return null;
52
+
53
+ return (
54
+ <Button
55
+ {...rest}
56
+ ref={ref}
57
+ disabled={isDisabled}
58
+ render={<LinkComponent to={to} replace={false} />}
59
+ onClick={(event) => {
60
+ if (isDisabled) {
61
+ event.preventDefault();
62
+ return;
63
+ }
64
+ if (onClick) {
65
+ event.preventDefault();
66
+ onClick(event);
67
+ }
68
+ }}
69
+ >
70
+ {children ?? (
71
+ <div className="flex items-center gap-2 font-semibold">
72
+ <Pencil className="h-4 w-4" />
73
+ <span>{label}</span>
74
+ </div>
75
+ )}
76
+ </Button>
77
+ );
78
+ }
79
+ );
80
+
81
+ EditButton.displayName = "EditButton";
@@ -0,0 +1,70 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { type BaseKey, useListButton } from "@refinedev/core";
5
+ import { Button } from "@/components/ui/button";
6
+ import { List } from "lucide-react";
7
+
8
+ type ListButtonProps = {
9
+ /**
10
+ * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.
11
+ * @default Inferred resource name from the route
12
+ */
13
+ resource?: BaseKey;
14
+ /**
15
+ * Access Control configuration for the button
16
+ * @default `{ enabled: true, hideIfUnauthorized: false }`
17
+ */
18
+ accessControl?: {
19
+ enabled?: boolean;
20
+ hideIfUnauthorized?: boolean;
21
+ };
22
+ /**
23
+ * `meta` property is used when creating the URL for the related action and path.
24
+ */
25
+ meta?: Record<string, unknown>;
26
+ } & React.ComponentProps<typeof Button>;
27
+
28
+ export const ListButton = React.forwardRef<
29
+ React.ComponentRef<typeof Button>,
30
+ ListButtonProps
31
+ >(({ resource, accessControl, meta, children, onClick, ...rest }, ref) => {
32
+ const { hidden, disabled, LinkComponent, to, label } = useListButton({
33
+ resource,
34
+ accessControl,
35
+ meta,
36
+ });
37
+
38
+ const isDisabled = disabled || rest.disabled;
39
+ const isHidden = hidden || rest.hidden;
40
+
41
+ if (isHidden) return null;
42
+
43
+ return (
44
+ <Button
45
+ {...rest}
46
+ ref={ref}
47
+ disabled={isDisabled}
48
+ render={<LinkComponent to={to} replace={false} />}
49
+ onClick={(event) => {
50
+ if (isDisabled) {
51
+ event.preventDefault();
52
+ return;
53
+ }
54
+ if (onClick) {
55
+ event.preventDefault();
56
+ onClick(event);
57
+ }
58
+ }}
59
+ >
60
+ {children ?? (
61
+ <div className="flex items-center gap-2 font-semibold">
62
+ <List className="w-4 h-4" />
63
+ <span>{label}</span>
64
+ </div>
65
+ )}
66
+ </Button>
67
+ );
68
+ });
69
+
70
+ ListButton.displayName = "ListButton";
@@ -0,0 +1,81 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { type BaseKey, useRefreshButton } from "@refinedev/core";
5
+ import { RefreshCcw } from "lucide-react";
6
+ import { Button } from "@/components/ui/button";
7
+ import { cn } from "@/lib/utils";
8
+
9
+ type RefreshButtonProps = {
10
+ /**
11
+ * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.
12
+ * @default Inferred resource name from the route
13
+ */
14
+ resource?: string;
15
+ /**
16
+ * Data item identifier for the actions with the API
17
+ * @default Reads `:id` from the URL
18
+ */
19
+ recordItemId?: BaseKey;
20
+ /**
21
+ * Target data provider name for API call to be made
22
+ * @default `"default"`
23
+ */
24
+ dataProviderName?: string;
25
+ /**
26
+ * `meta` property is used when creating the URL for the related action and path.
27
+ */
28
+ meta?: Record<string, unknown>;
29
+ } & React.ComponentProps<typeof Button>;
30
+
31
+ export const RefreshButton = React.forwardRef<
32
+ React.ComponentRef<typeof Button>,
33
+ RefreshButtonProps
34
+ >(
35
+ (
36
+ { resource, recordItemId, dataProviderName, meta, children, onClick, ...rest },
37
+ ref
38
+ ) => {
39
+ const {
40
+ onClick: refresh,
41
+ loading,
42
+ label,
43
+ } = useRefreshButton({
44
+ resource,
45
+ id: recordItemId,
46
+ dataProviderName,
47
+ meta,
48
+ });
49
+
50
+ const isDisabled = rest.disabled || loading;
51
+
52
+ return (
53
+ <Button
54
+ {...rest}
55
+ onClick={(event) => {
56
+ if (isDisabled) {
57
+ event.preventDefault();
58
+ return;
59
+ }
60
+ refresh();
61
+ onClick?.(event);
62
+ }}
63
+ ref={ref}
64
+ disabled={isDisabled}
65
+ >
66
+ {children ?? (
67
+ <div className="flex items-center gap-2">
68
+ <RefreshCcw
69
+ className={cn("h-4 w-4", {
70
+ "animate-spin": loading,
71
+ })}
72
+ />
73
+ <span>{label ?? "Refresh"}</span>
74
+ </div>
75
+ )}
76
+ </Button>
77
+ );
78
+ }
79
+ );
80
+
81
+ RefreshButton.displayName = "RefreshButton";
@@ -0,0 +1,81 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { type BaseKey, useShowButton } from "@refinedev/core";
5
+ import { Eye } from "lucide-react";
6
+ import { Button } from "@/components/ui/button";
7
+
8
+ type ShowButtonProps = {
9
+ /**
10
+ * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource.
11
+ * @default Inferred resource name from the route
12
+ */
13
+ resource?: string;
14
+ /**
15
+ * Data item identifier for the actions with the API
16
+ * @default Reads `:id` from the URL
17
+ */
18
+ recordItemId?: BaseKey;
19
+ /**
20
+ * Access Control configuration for the button
21
+ * @default `{ enabled: true, hideIfUnauthorized: false }`
22
+ */
23
+ accessControl?: {
24
+ enabled?: boolean;
25
+ hideIfUnauthorized?: boolean;
26
+ };
27
+ /**
28
+ * `meta` property is used when creating the URL for the related action and path.
29
+ */
30
+ meta?: Record<string, unknown>;
31
+ } & React.ComponentProps<typeof Button>;
32
+
33
+ export const ShowButton = React.forwardRef<
34
+ React.ComponentRef<typeof Button>,
35
+ ShowButtonProps
36
+ >(
37
+ (
38
+ { resource, recordItemId, accessControl, meta, children, onClick, ...rest },
39
+ ref
40
+ ) => {
41
+ const { hidden, disabled, LinkComponent, to, label } = useShowButton({
42
+ resource,
43
+ id: recordItemId,
44
+ accessControl,
45
+ meta,
46
+ });
47
+
48
+ const isDisabled = disabled || rest.disabled;
49
+ const isHidden = hidden || rest.hidden;
50
+
51
+ if (isHidden) return null;
52
+
53
+ return (
54
+ <Button
55
+ {...rest}
56
+ ref={ref}
57
+ disabled={isDisabled}
58
+ render={<LinkComponent to={to} replace={false} />}
59
+ onClick={(event) => {
60
+ if (isDisabled) {
61
+ event.preventDefault();
62
+ return;
63
+ }
64
+ if (onClick) {
65
+ event.preventDefault();
66
+ onClick(event);
67
+ }
68
+ }}
69
+ >
70
+ {children ?? (
71
+ <div className="flex items-center gap-2 font-semibold">
72
+ <Eye className="h-4 w-4" />
73
+ <span>{label}</span>
74
+ </div>
75
+ )}
76
+ </Button>
77
+ );
78
+ }
79
+ );
80
+
81
+ ShowButton.displayName = "ShowButton";
@@ -0,0 +1,37 @@
1
+ import { Badge } from "@/components/ui/badge";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ type StatusBadgeProps = {
5
+ status?: string | null;
6
+ className?: string;
7
+ };
8
+
9
+ const dotStyles: Record<string, string> = {
10
+ published: "bg-foreground",
11
+ draft: "bg-neutral-400 dark:bg-neutral-500",
12
+ rejected: "border border-current bg-transparent",
13
+ };
14
+
15
+ export function StatusBadge({ status, className }: StatusBadgeProps) {
16
+ const normalizedStatus = status?.toLowerCase() || "unknown";
17
+
18
+ return (
19
+ <Badge
20
+ variant="outline"
21
+ className={cn(
22
+ "h-6 gap-1.5 rounded-md border-border/80 bg-card px-2 text-[11px] font-medium capitalize text-foreground shadow-none",
23
+ normalizedStatus === "rejected" && "text-muted-foreground",
24
+ className
25
+ )}
26
+ >
27
+ <span
28
+ aria-hidden="true"
29
+ className={cn(
30
+ "size-1.5 rounded-full",
31
+ dotStyles[normalizedStatus] ?? "bg-muted-foreground"
32
+ )}
33
+ />
34
+ {normalizedStatus}
35
+ </Badge>
36
+ );
37
+ }
@@ -0,0 +1,81 @@
1
+ "use client";
2
+
3
+ import { cn } from "@/lib/utils";
4
+ import {
5
+ useBack,
6
+ useResourceParams,
7
+ useUserFriendlyName,
8
+ } from "@refinedev/core";
9
+ import type { PropsWithChildren } from "react";
10
+ import { Breadcrumb } from "@/components/app-shell/breadcrumb";
11
+ import { Button } from "@/components/ui/button";
12
+ import { ArrowLeftIcon } from "lucide-react";
13
+
14
+ type CreateViewProps = PropsWithChildren<{
15
+ className?: string;
16
+ }>;
17
+
18
+ export function CreateView({ children, className }: CreateViewProps) {
19
+ return (
20
+ <div className={cn("flex flex-col", "gap-6", className)}>
21
+ <CreateViewHeader />
22
+ {children}
23
+ </div>
24
+ );
25
+ }
26
+
27
+ type CreateHeaderProps = PropsWithChildren<{
28
+ resource?: string;
29
+ title?: string;
30
+ wrapperClassName?: string;
31
+ headerClassName?: string;
32
+ }>;
33
+
34
+ export const CreateViewHeader = ({
35
+ resource: resourceFromProps,
36
+ title: titleFromProps,
37
+ wrapperClassName,
38
+ headerClassName,
39
+ }: CreateHeaderProps) => {
40
+ const back = useBack();
41
+
42
+ const getUserFriendlyName = useUserFriendlyName();
43
+
44
+ const { resource, identifier } = useResourceParams({
45
+ resource: resourceFromProps,
46
+ });
47
+
48
+ const resourceTitle = getUserFriendlyName(
49
+ resource?.meta?.label ?? identifier ?? resource?.name,
50
+ "singular"
51
+ );
52
+ const title = titleFromProps ?? `Create ${resourceTitle}`;
53
+
54
+ return (
55
+ <div className={cn("flex flex-col", "gap-3", wrapperClassName)}>
56
+ <div className="flex items-center text-muted-foreground">
57
+ <Breadcrumb />
58
+ </div>
59
+ <div className={cn("flex items-start gap-2", headerClassName)}>
60
+ <Button
61
+ variant="ghost"
62
+ size="icon"
63
+ className="mt-0.5 rounded-lg"
64
+ onClick={back}
65
+ >
66
+ <ArrowLeftIcon className="h-4 w-4" />
67
+ </Button>
68
+ <div>
69
+ <h2 className="text-3xl font-semibold tracking-[-0.035em]">
70
+ {title}
71
+ </h2>
72
+ <p className="mt-2 text-sm text-muted-foreground">
73
+ Add a new {resourceTitle.toLowerCase()} to your NocoBase workspace.
74
+ </p>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ );
79
+ };
80
+
81
+ CreateView.displayName = "CreateView";