@js-empire/emperor-ui 1.0.1 → 1.2.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 (189) hide show
  1. package/.husky/pre-commit +4 -0
  2. package/.storybook/main.ts +2 -11
  3. package/.storybook/preview.ts +1 -1
  4. package/.storybook/vitest.setup.ts +3 -3
  5. package/.vscode/extensions.json +1 -0
  6. package/README.md +47 -1
  7. package/dist/emperor-ui.js +69 -3169
  8. package/dist/emperor-ui.umd.cjs +45 -2
  9. package/dist/features-animation-D_Ss-HYx.js +1938 -0
  10. package/dist/globals.css +1 -0
  11. package/dist/icons/emperor-ui-logo.ico +0 -0
  12. package/dist/images/avatar-female.jpg +0 -0
  13. package/dist/images/avatar-male.jpg +0 -0
  14. package/dist/images/emperor-ui-logo.png +0 -0
  15. package/dist/index-C3mfrNCk.js +1630 -0
  16. package/dist/index-CZpTSGZs.js +5 -0
  17. package/dist/index-SRvFgjzo.js +26257 -0
  18. package/dist/index.d.ts +552 -46
  19. package/dist/src-UW24ZMRV-Ducut0ty.js +5 -0
  20. package/eslint.config.js +5 -14
  21. package/package.json +19 -7
  22. package/public/icons/emperor-ui-logo.ico +0 -0
  23. package/public/images/avatar-female.jpg +0 -0
  24. package/public/images/avatar-male.jpg +0 -0
  25. package/public/images/emperor-ui-logo.png +0 -0
  26. package/src/components/atoms/brand/brand.stories.tsx +27 -0
  27. package/src/components/atoms/brand/brand.tsx +56 -0
  28. package/src/components/atoms/brand/index.ts +1 -0
  29. package/src/components/atoms/brand/styles/classes.ts +9 -0
  30. package/src/components/atoms/brand/styles/index.ts +2 -0
  31. package/src/components/atoms/brand/styles/styles.ts +0 -0
  32. package/src/components/atoms/column/column.stories.tsx +37 -0
  33. package/src/components/atoms/column/column.tsx +21 -0
  34. package/src/components/atoms/column/index.ts +1 -0
  35. package/src/components/atoms/container/column.stories.tsx +37 -0
  36. package/src/components/atoms/container/container.tsx +28 -0
  37. package/src/components/atoms/container/index.ts +1 -0
  38. package/src/components/atoms/index.ts +6 -0
  39. package/src/components/atoms/portal/index.ts +1 -0
  40. package/src/components/atoms/portal/portal.stories.tsx +43 -0
  41. package/src/components/atoms/portal/portal.tsx +25 -0
  42. package/src/components/atoms/row/index.ts +1 -0
  43. package/src/components/atoms/row/row.stories.tsx +37 -0
  44. package/src/components/atoms/row/row.tsx +26 -0
  45. package/src/components/atoms/uploader/avatar-label.tsx +83 -0
  46. package/src/components/atoms/uploader/index.ts +7 -0
  47. package/src/components/atoms/uploader/stories/uploader.stories.tsx +160 -0
  48. package/src/components/atoms/uploader/upload-file-error-box.tsx +29 -0
  49. package/src/components/atoms/uploader/upload-file-input.tsx +36 -0
  50. package/src/components/atoms/uploader/upload-file-label.tsx +74 -0
  51. package/src/components/atoms/uploader/upload-file-listing.tsx +55 -0
  52. package/src/components/atoms/uploader/uploader.tsx +55 -0
  53. package/src/components/atoms/uploader/view-image-modal.tsx +40 -0
  54. package/src/components/index.ts +4 -8
  55. package/src/components/{filter → molecules/filter}/filter.tsx +2 -2
  56. package/src/components/molecules/index.ts +5 -0
  57. package/src/components/molecules/item-card/item-card.tsx +6 -0
  58. package/src/components/molecules/nav-bar/index.ts +3 -0
  59. package/src/components/molecules/nav-bar/nav-bar-item.tsx +70 -0
  60. package/src/components/molecules/nav-bar/nav-bar.tsx +65 -0
  61. package/src/components/molecules/nav-bar/stories/hover-effect/nav-bar-hover-effect.stories.tsx +52 -0
  62. package/src/components/molecules/nav-bar/stories/nav-bar.stories.tsx +50 -0
  63. package/src/components/molecules/nav-bar/styles/classes.ts +68 -0
  64. package/src/components/molecules/nav-bar/styles/index.ts +2 -0
  65. package/src/components/molecules/nav-bar/styles/styles.ts +85 -0
  66. package/src/components/molecules/nav-bar/sub-items-box.tsx +57 -0
  67. package/src/components/molecules/scaffold/scaffold.stories.tsx +21 -0
  68. package/src/components/molecules/scaffold/scaffold.tsx +36 -0
  69. package/src/components/molecules/side-bar/compact-side-bar.tsx +75 -0
  70. package/src/components/molecules/side-bar/index.ts +1 -0
  71. package/src/components/molecules/side-bar/side-bar-drawer.tsx +124 -0
  72. package/src/components/molecules/side-bar/side-bar.stories.tsx +111 -0
  73. package/src/components/molecules/side-bar/side-bar.tsx +31 -0
  74. package/src/components/molecules/side-bar/styles/classes.ts +28 -0
  75. package/src/components/molecules/side-bar/styles/index.ts +2 -0
  76. package/src/components/molecules/side-bar/styles/styles.ts +13 -0
  77. package/src/components/organisms/footer/copy-rights-box.tsx +27 -0
  78. package/src/components/organisms/footer/footer.tsx +75 -0
  79. package/src/components/organisms/footer/index.ts +5 -0
  80. package/src/components/organisms/footer/policies-box.tsx +26 -0
  81. package/src/components/organisms/footer/quick-links-box.tsx +45 -0
  82. package/src/components/organisms/footer/social-links-box.tsx +32 -0
  83. package/src/components/organisms/footer/stories/footer.stories.tsx +61 -0
  84. package/src/components/organisms/footer/styles/classes.ts +71 -0
  85. package/src/components/organisms/footer/styles/index.ts +2 -0
  86. package/src/components/organisms/footer/styles/styles.ts +6 -0
  87. package/src/components/organisms/header/header.tsx +94 -0
  88. package/src/components/organisms/header/segmented-header-content.tsx +37 -0
  89. package/src/components/organisms/header/stories/header.stories.tsx +144 -0
  90. package/src/components/organisms/header/styles/classes.ts +22 -0
  91. package/src/components/organisms/header/styles/index.ts +2 -0
  92. package/src/components/organisms/header/styles/styles.ts +39 -0
  93. package/src/components/organisms/index.ts +4 -0
  94. package/src/components/{item-details → organisms/item-details}/item-details.tsx +2 -2
  95. package/src/components/{listings → organisms/listings}/listings.tsx +2 -2
  96. package/src/components/templates/index.ts +1 -0
  97. package/src/components/templates/landing-page/index.ts +1 -0
  98. package/src/components/templates/landing-page/landing-page.stories.tsx +21 -0
  99. package/src/components/templates/landing-page/landing-page.tsx +57 -0
  100. package/src/components/templates/landing-page/styles/classes.ts +11 -0
  101. package/src/components/templates/landing-page/styles/index.ts +1 -0
  102. package/src/constants/defaults.ts +43 -8
  103. package/src/constants/fake.ts +5 -0
  104. package/src/constants/footer.tsx +157 -0
  105. package/src/constants/index.ts +3 -0
  106. package/src/constants/uploader.ts +27 -0
  107. package/src/context/emperor-ui-context.ts +4 -4
  108. package/src/context/index.ts +2 -0
  109. package/src/context/navigation-context.ts +6 -0
  110. package/src/context/uploader-context.ts +6 -0
  111. package/src/enums/index.ts +2 -0
  112. package/src/enums/placeholders.ts +4 -0
  113. package/src/enums/preserved-keys.ts +3 -0
  114. package/src/hooks/index.ts +3 -0
  115. package/src/hooks/use-emperor-ui.ts +1 -1
  116. package/src/hooks/use-navigation.ts +12 -0
  117. package/src/hooks/use-uploader-context.ts +14 -0
  118. package/src/hooks/use-uploader.tsx +151 -0
  119. package/src/index.ts +9 -5
  120. package/src/main.tsx +3 -0
  121. package/src/mocks/header.tsx +118 -0
  122. package/src/mocks/index.ts +1 -0
  123. package/src/providers/config-provider.tsx +54 -0
  124. package/src/providers/emperor-ui-provider.tsx +17 -24
  125. package/src/providers/index.ts +3 -0
  126. package/src/providers/navigation-provider.tsx +42 -0
  127. package/src/providers/uploader-provider.tsx +53 -0
  128. package/src/styles/globals.css +13 -0
  129. package/src/styles/hero.ts +2 -0
  130. package/src/types/components/atoms/brand.ts +13 -0
  131. package/src/types/components/atoms/column.ts +3 -0
  132. package/src/types/components/atoms/container.ts +3 -0
  133. package/src/types/components/atoms/index.ts +6 -0
  134. package/src/types/components/atoms/portal.ts +6 -0
  135. package/src/types/components/atoms/row.ts +3 -0
  136. package/src/types/components/atoms/uploader.ts +97 -0
  137. package/src/types/components/index.ts +3 -8
  138. package/src/types/components/{filter → molecules/filter}/filter.ts +1 -1
  139. package/src/types/components/molecules/footer/footer.ts +68 -0
  140. package/src/types/components/molecules/header/header.ts +51 -0
  141. package/src/types/components/molecules/index.ts +9 -0
  142. package/src/types/components/{item-card → molecules/item-card}/item-card.ts +1 -1
  143. package/src/types/components/{item-details → molecules/item-details}/item-details.ts +1 -1
  144. package/src/types/components/{listings → molecules/listings}/listings.ts +1 -1
  145. package/src/types/components/molecules/nav-bar/nav-bar.ts +66 -0
  146. package/src/types/components/{scaffold → molecules/scaffold}/scaffold.ts +1 -1
  147. package/src/types/components/molecules/side-bar/index.ts +1 -0
  148. package/src/types/components/molecules/side-bar/side-bar.ts +40 -0
  149. package/src/types/components/templates/index.ts +1 -0
  150. package/src/types/components/templates/landing-page.ts +10 -0
  151. package/src/types/context/config.ts +54 -0
  152. package/src/types/context/index.ts +2 -1
  153. package/src/types/context/navigation.ts +17 -0
  154. package/src/types/shared/components.ts +4 -0
  155. package/src/utils/compress-images.ts +36 -0
  156. package/src/utils/index.ts +3 -0
  157. package/src/utils/storybook.tsx +15 -0
  158. package/src/utils/uploader.ts +148 -0
  159. package/tsconfig.app.json +1 -9
  160. package/tsconfig.node.json +0 -1
  161. package/vite.config.ts +3 -8
  162. package/vitest.shims.d.ts +1 -1
  163. package/src/components/footer/footer.tsx +0 -6
  164. package/src/components/footer/index.ts +0 -1
  165. package/src/components/header/header.tsx +0 -49
  166. package/src/components/item-card/item-card.tsx +0 -6
  167. package/src/components/nav-bar/index.ts +0 -1
  168. package/src/components/nav-bar/nav-bar.tsx +0 -6
  169. package/src/components/scaffold/scaffold.tsx +0 -15
  170. package/src/index.css +0 -1
  171. package/src/types/components/footer/footer.ts +0 -9
  172. package/src/types/components/header/header.ts +0 -21
  173. package/src/types/components/nav-bar/nav-bar.ts +0 -9
  174. package/src/types/context/emperor-ui.ts +0 -37
  175. package/tailwind.config.js +0 -6
  176. /package/src/components/{filter → molecules/filter}/index.ts +0 -0
  177. /package/src/components/{item-card → molecules/item-card}/index.ts +0 -0
  178. /package/src/components/{scaffold → molecules/scaffold}/index.ts +0 -0
  179. /package/src/components/{header → organisms/header}/index.ts +0 -0
  180. /package/src/components/{item-details → organisms/item-details}/index.ts +0 -0
  181. /package/src/components/{listings → organisms/listings}/index.ts +0 -0
  182. /package/src/types/components/{filter → molecules/filter}/index.ts +0 -0
  183. /package/src/types/components/{footer → molecules/footer}/index.ts +0 -0
  184. /package/src/types/components/{header → molecules/header}/index.ts +0 -0
  185. /package/src/types/components/{item-card → molecules/item-card}/index.ts +0 -0
  186. /package/src/types/components/{item-details → molecules/item-details}/index.ts +0 -0
  187. /package/src/types/components/{listings → molecules/listings}/index.ts +0 -0
  188. /package/src/types/components/{nav-bar → molecules/nav-bar}/index.ts +0 -0
  189. /package/src/types/components/{scaffold → molecules/scaffold}/index.ts +0 -0
@@ -0,0 +1,160 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { Uploader } from "@/components";
3
+ import { getStorybookDecorators } from "@/utils";
4
+ import { UploaderProps } from "@/types";
5
+ import { useUploader } from "@/hooks";
6
+ import { useDisclosure } from "@heroui/react";
7
+
8
+ const meta: Meta<typeof Uploader> = {
9
+ title: "Atoms/Uploader",
10
+ component: Uploader,
11
+ parameters: {
12
+ layout: "centered",
13
+ },
14
+ tags: ["autodocs"],
15
+ decorators: getStorybookDecorators({
16
+ config: {
17
+ layout: {
18
+ withScaffold: false,
19
+ },
20
+ },
21
+ }),
22
+ };
23
+
24
+ export default meta;
25
+
26
+ type Story = StoryObj<typeof meta>;
27
+
28
+ export const Default: Story = {
29
+ args: {},
30
+ render: (args: UploaderProps) => {
31
+ const uploadProps = useUploader({
32
+ fileTypes: ["image"],
33
+ labelId: "image",
34
+ });
35
+
36
+ return <Uploader {...uploadProps} {...args} />;
37
+ },
38
+ };
39
+
40
+ export const MultiFiles: Story = {
41
+ args: {},
42
+ render: (args: UploaderProps) => {
43
+ const uploadProps = useUploader({
44
+ fileTypes: ["image"],
45
+ labelId: "image",
46
+ isMulti: true,
47
+ });
48
+
49
+ return <Uploader {...uploadProps} {...args} />;
50
+ },
51
+ };
52
+
53
+ export const WithMaxCount: Story = {
54
+ args: {},
55
+ render: (args: UploaderProps) => {
56
+ const uploadProps = useUploader({
57
+ fileTypes: ["image"],
58
+ labelId: "image",
59
+ isMulti: true,
60
+ maxCount: 2,
61
+ });
62
+
63
+ return <Uploader {...uploadProps} {...args} />;
64
+ },
65
+ };
66
+
67
+ export const Required: Story = {
68
+ args: {},
69
+ render: (args: UploaderProps) => {
70
+ const uploadProps = useUploader({
71
+ fileTypes: ["image"],
72
+ labelId: "image",
73
+ isRequired: true,
74
+ });
75
+
76
+ return <Uploader {...uploadProps} {...args} />;
77
+ },
78
+ };
79
+
80
+ export const HideListings: Story = {
81
+ args: {},
82
+ render: (args: UploaderProps) => {
83
+ const uploadProps = useUploader({
84
+ fileTypes: ["image"],
85
+ labelId: "image",
86
+ });
87
+
88
+ return <Uploader hideListing {...uploadProps} {...args} />;
89
+ },
90
+ };
91
+
92
+ export const ViewableImages: Story = {
93
+ args: {},
94
+ render: (args: UploaderProps) => {
95
+ const uploadProps = useUploader({
96
+ fileTypes: ["image"],
97
+ labelId: "image",
98
+ });
99
+
100
+ const modalProps = useDisclosure();
101
+
102
+ return (
103
+ <Uploader
104
+ isFileViewable
105
+ modal={{
106
+ ...modalProps,
107
+ }}
108
+ {...uploadProps}
109
+ {...args}
110
+ />
111
+ );
112
+ },
113
+ };
114
+
115
+ export const AllowDuplicates: Story = {
116
+ args: {},
117
+ render: (args: UploaderProps) => {
118
+ const uploadProps = useUploader({
119
+ fileTypes: ["image"],
120
+ labelId: "image",
121
+ preventDuplicates: false,
122
+ });
123
+
124
+ return <Uploader {...uploadProps} {...args} />;
125
+ },
126
+ };
127
+
128
+ export const CompressFiles: Story = {
129
+ args: {},
130
+ render: (args: UploaderProps) => {
131
+ const uploadProps = useUploader({
132
+ fileTypes: ["image"],
133
+ labelId: "uncompressed-image",
134
+ compressFiles: false,
135
+ });
136
+
137
+ const compressedUploadProps = useUploader({
138
+ fileTypes: ["image"],
139
+ labelId: "compressed-image",
140
+ compressFiles: true,
141
+ });
142
+
143
+ return (
144
+ <div className="grid grid-cols-2 gap-4">
145
+ <h3 className="text-lg font-bold">Uncompressed</h3>
146
+ <h3 className="text-lg font-bold">Compressed</h3>
147
+
148
+ <Uploader {...uploadProps} {...args} />
149
+ <Uploader {...compressedUploadProps} {...args} />
150
+
151
+ <p className="text-sm text-gray-500">
152
+ File size: {uploadProps.files[0]?.file?.size || "---"}
153
+ </p>
154
+ <p className="text-sm text-gray-500">
155
+ File size: {compressedUploadProps.files[0]?.file?.size || "---"}
156
+ </p>
157
+ </div>
158
+ );
159
+ },
160
+ };
@@ -0,0 +1,29 @@
1
+ "use client";
2
+
3
+ import { cn } from "@heroui/react";
4
+ import { useEmperorUI, useUploaderContext } from "@/hooks";
5
+ import { useMemo } from "react";
6
+
7
+ export function UploadFileErrorBox() {
8
+ const { config } = useEmperorUI();
9
+ const { files, isRequired, classNames } = useUploaderContext();
10
+
11
+ const locales = config?.interLocalization?.locales;
12
+ const lang = config?.interLocalization?.lang;
13
+
14
+ const locale = locales?.[lang || "en"];
15
+
16
+ const isError = useMemo(
17
+ () => files?.length === 0 && isRequired,
18
+ [files, isRequired],
19
+ );
20
+
21
+ if (isError)
22
+ return (
23
+ <p className={cn("text-[14px] text-danger", classNames?.error)}>
24
+ {locale?.errorUploadingFile}
25
+ </p>
26
+ );
27
+
28
+ return null;
29
+ }
@@ -0,0 +1,36 @@
1
+ "use client";
2
+
3
+ import { cn } from "@heroui/react";
4
+ import { useUploaderContext } from "@/hooks";
5
+ import { fileTypesMapping } from "@/constants";
6
+
7
+ export function UploadFileInput() {
8
+ const {
9
+ fileTypes = [],
10
+ isMulti,
11
+ labelId,
12
+ onInputChange,
13
+ classNames,
14
+ } = useUploaderContext();
15
+
16
+ const getFileAccepts = (): string => {
17
+ const acceptedTypes: string[] = [];
18
+
19
+ fileTypes?.forEach((fileType) => {
20
+ acceptedTypes.push(...(fileTypesMapping?.[fileType] || []));
21
+ });
22
+
23
+ return acceptedTypes.join(", ");
24
+ };
25
+
26
+ return (
27
+ <input
28
+ id={labelId}
29
+ type="file"
30
+ accept={getFileAccepts()}
31
+ className={cn("hidden", classNames?.input)}
32
+ onChange={onInputChange}
33
+ multiple={isMulti}
34
+ />
35
+ );
36
+ }
@@ -0,0 +1,74 @@
1
+ "use client";
2
+
3
+ import { Spinner, cn } from "@heroui/react";
4
+ import { useEmperorUI, useUploaderContext } from "@/hooks";
5
+ import { UploadCloud } from "lucide-react";
6
+ import { useState } from "react";
7
+
8
+ export function UploadFileLabel() {
9
+ const { config } = useEmperorUI();
10
+ const [draggableMessage, setDraggableMessage] = useState("");
11
+ const {
12
+ labelId,
13
+ classNames,
14
+ labelContent,
15
+ isDraggable,
16
+ onInputChange,
17
+ isLoading,
18
+ } = useUploaderContext();
19
+
20
+ const locales = config?.interLocalization?.locales;
21
+ const lang = config?.interLocalization?.lang;
22
+
23
+ const locale = locales?.[lang || "en"];
24
+
25
+ const handleDrop = (
26
+ event: React.ChangeEvent<HTMLInputElement> &
27
+ React.DragEvent<HTMLLabelElement>,
28
+ ) => {
29
+ event.preventDefault();
30
+ if (onInputChange) onInputChange(event);
31
+ };
32
+
33
+ const handleDragOver = (
34
+ event: React.ChangeEvent<HTMLInputElement> &
35
+ React.DragEvent<HTMLLabelElement>,
36
+ ) => {
37
+ event.preventDefault();
38
+ setDraggableMessage(locale?.dropHere || "");
39
+ };
40
+
41
+ const handleDragLeave = () => {
42
+ setDraggableMessage("");
43
+ };
44
+
45
+ if (isLoading)
46
+ return (
47
+ <div className={cn("w-full flex mx-auto", classNames?.label)}>
48
+ <Spinner className="mx-auto" size="lg" />;
49
+ </div>
50
+ );
51
+
52
+ return (
53
+ <label
54
+ className={cn("w-full cursor-pointer mx-auto", classNames?.label)}
55
+ htmlFor={labelId}
56
+ onDrop={isDraggable ? handleDrop : () => {}}
57
+ onDragOver={isDraggable ? handleDragOver : () => {}}
58
+ onDragLeave={isDraggable ? handleDragLeave : () => {}}
59
+ >
60
+ {labelContent || (
61
+ <div className="pointer-events-none flex size-full flex-col items-center justify-center gap-2 rounded-md border border-dashed bg-primary/10 px-2 py-8 text-xs">
62
+ <UploadCloud className="size-10 text-primary" />
63
+
64
+ <p className="font-bold">{locale?.selectFile || ""}</p>
65
+ <p className="opacity-70">{locale?.selectionTypes || ""}</p>
66
+
67
+ {draggableMessage && (
68
+ <p className="text-sm font-bold">{draggableMessage}</p>
69
+ )}
70
+ </div>
71
+ )}
72
+ </label>
73
+ );
74
+ }
@@ -0,0 +1,55 @@
1
+ "use client";
2
+
3
+ import { Button, cn } from "@heroui/react";
4
+ import { useUploaderContext } from "@/hooks";
5
+ import { Eye, Trash2 } from "lucide-react";
6
+
7
+ export function UploadFileListing() {
8
+ const { files, setSelectedFile, handleClearFile, classNames, modal } =
9
+ useUploaderContext();
10
+
11
+ return files?.map((file) => {
12
+ const isFileViewable =
13
+ modal?.onOpen && file?.view && file?.file?.name && file?.type === "image";
14
+
15
+ if (file)
16
+ return (
17
+ <div
18
+ key={file?.file?.name}
19
+ className={cn(
20
+ "flex justify-between items-center p-2 gap-2 w-full border border-black/30 rounded-lg",
21
+ classNames?.listing,
22
+ )}
23
+ >
24
+ <p className="w-full line-clamp-1 text-xs max-w-60">
25
+ {file?.file?.name}
26
+ </p>
27
+
28
+ <Button
29
+ isIconOnly
30
+ variant="flat"
31
+ className="size-8 min-w-8 rounded-full"
32
+ color="danger"
33
+ onPress={() => handleClearFile(file?.file?.name)}
34
+ startContent={<Trash2 className="rounded-lg size-4" />}
35
+ />
36
+
37
+ {isFileViewable && (
38
+ <Button
39
+ isIconOnly
40
+ variant="flat"
41
+ className="size-8 min-w-8 rounded-full"
42
+ color="primary"
43
+ onPress={() => {
44
+ setSelectedFile?.(file);
45
+ modal?.onOpen?.();
46
+ }}
47
+ startContent={<Eye className="rounded-lg size-4" />}
48
+ />
49
+ )}
50
+ </div>
51
+ );
52
+
53
+ return null;
54
+ });
55
+ }
@@ -0,0 +1,55 @@
1
+ import type { UploaderProps } from "@/types";
2
+ import { cn } from "@/utils";
3
+ import { cva, VariantProps } from "class-variance-authority";
4
+ import { forwardRef, ComponentProps } from "react";
5
+ import { UploaderProvider } from "@/providers";
6
+ import {
7
+ AvatarLabel,
8
+ UploadFileLabel,
9
+ ViewImageModal,
10
+ UploadFileListing,
11
+ UploadFileErrorBox,
12
+ UploadFileInput,
13
+ } from "@/components";
14
+
15
+ const uploaderStyles = cva(["w-full flex flex-col gap-2"], {
16
+ variants: {},
17
+ defaultVariants: {},
18
+ compoundVariants: [],
19
+ });
20
+
21
+ /**
22
+ * @usage
23
+ * ```
24
+ * const uploadProps = useUpload({
25
+ labelId: "uploaded-file",
26
+ fileTypes: ["image", "pdf"],
27
+ isRequired: true,
28
+ isMulti: true,
29
+ });
30
+
31
+ <Uploader {...uploadProps} />
32
+ * ```
33
+ */
34
+ export const Uploader = forwardRef<
35
+ HTMLDivElement,
36
+ ComponentProps<"div"> & VariantProps<typeof uploaderStyles> & UploaderProps
37
+ >(({ className, ...props }, ref) => {
38
+ const { isAvatar, hideListing, isFileViewable, hideErrorMessage } = props;
39
+
40
+ return (
41
+ <UploaderProvider {...props}>
42
+ <div ref={ref} className={cn(uploaderStyles({ className }))} {...props}>
43
+ {isAvatar ? <AvatarLabel /> : <UploadFileLabel />}
44
+
45
+ {!hideListing && <UploadFileListing />}
46
+
47
+ {!hideErrorMessage && <UploadFileErrorBox />}
48
+
49
+ <UploadFileInput />
50
+
51
+ {isFileViewable && <ViewImageModal />}
52
+ </div>
53
+ </UploaderProvider>
54
+ );
55
+ });
@@ -0,0 +1,40 @@
1
+ "use client";
2
+
3
+ import { Image, Modal, ModalBody, ModalContent } from "@heroui/react";
4
+ import { useEmperorUI, useUploaderContext } from "@/hooks";
5
+
6
+ export function ViewImageModal() {
7
+ const { config } = useEmperorUI();
8
+ const { modal, selectedFile } = useUploaderContext();
9
+
10
+ const lang = config?.interLocalization?.lang || "en";
11
+
12
+ const src = selectedFile?.view;
13
+ const alt = selectedFile?.file?.name;
14
+
15
+ if (!src || !alt) return null;
16
+
17
+ return (
18
+ <Modal
19
+ placement="center"
20
+ isOpen={modal?.isOpen}
21
+ dir={lang === "ar" ? "rtl" : "ltr"}
22
+ onClose={modal?.onClose}
23
+ onOpenChange={modal?.onOpenChange}
24
+ size="xl"
25
+ {...modal}
26
+ >
27
+ <ModalContent className="px-5">
28
+ <ModalBody className="h-[60vh]">
29
+ <Image
30
+ className="size-full rounded-md object-cover"
31
+ src={src}
32
+ alt={alt}
33
+ width={500}
34
+ height={500}
35
+ />
36
+ </ModalBody>
37
+ </ModalContent>
38
+ </Modal>
39
+ );
40
+ }
@@ -1,8 +1,4 @@
1
- export * from "./filter";
2
- export * from "./footer";
3
- export * from "./header";
4
- export * from "./item-card";
5
- export * from "./item-details";
6
- export * from "./listings";
7
- export * from "./nav-bar";
8
- export * from "./scaffold";
1
+ export * from "./templates";
2
+ export * from "./molecules";
3
+ export * from "./atoms";
4
+ export * from "./organisms";
@@ -1,5 +1,5 @@
1
- import type { FilterProps } from "@types";
2
- import { cn } from "@utils";
1
+ import type { FilterProps } from "@/types";
2
+ import { cn } from "@/utils";
3
3
 
4
4
  export function Filter({ className }: FilterProps) {
5
5
  return <div className={cn("", className)}>Filter Component</div>;
@@ -0,0 +1,5 @@
1
+ export * from "./filter";
2
+ export * from "./item-card";
3
+ export * from "./nav-bar";
4
+ export * from "./side-bar";
5
+ export * from "./scaffold";
@@ -0,0 +1,6 @@
1
+ import type { ItemCardProps } from "@/types";
2
+ import { cn } from "@/utils";
3
+
4
+ export function ItemCard({ className }: ItemCardProps) {
5
+ return <div className={cn("", className)}>Item Card Component</div>;
6
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./nav-bar";
2
+ export * from "./sub-items-box";
3
+ export * from "./nav-bar-item";
@@ -0,0 +1,70 @@
1
+ import { cn } from "@/utils";
2
+ import { VariantProps } from "class-variance-authority";
3
+ import { forwardRef, ComponentProps } from "react";
4
+ import type { NavBarItemProps } from "@/types";
5
+ import { navBarItemClasses, navBarItemStyles } from "./styles";
6
+ import { useEmperorUI, useNavigation } from "@/hooks";
7
+
8
+ export const NavBarItem = forwardRef<
9
+ HTMLLIElement,
10
+ ComponentProps<"li"> &
11
+ VariantProps<typeof navBarItemClasses> &
12
+ NavBarItemProps
13
+ >(({ className, item, variant, hoverEffect, ...props }, ref) => {
14
+ const { config } = useEmperorUI();
15
+ const {
16
+ hoveredItemId,
17
+ subItemsBoxIsHovered,
18
+ setSubItems,
19
+ setHoveredItemId,
20
+ setIsSubItemsBoxOpen,
21
+ } = useNavigation();
22
+
23
+ const primaryColor = config?.theme?.colors?.primary;
24
+ const foregroundColor = config?.theme?.colors?.foreground;
25
+
26
+ const { id, label, Icon, subItems } = item;
27
+
28
+ const isHovered = hoveredItemId === id;
29
+
30
+ const handleHover = (id: string | null) => {
31
+ if (id) {
32
+ setHoveredItemId(id);
33
+
34
+ if (subItems && subItems.length > 0) {
35
+ setSubItems(subItems);
36
+ setIsSubItemsBoxOpen(true);
37
+ } else {
38
+ setIsSubItemsBoxOpen(false);
39
+ }
40
+ return;
41
+ }
42
+
43
+ setHoveredItemId(null);
44
+
45
+ if (!subItemsBoxIsHovered) {
46
+ setIsSubItemsBoxOpen(false);
47
+ }
48
+ };
49
+
50
+ return (
51
+ <li
52
+ ref={ref}
53
+ data-slot="emperor-nav-bar-item"
54
+ style={navBarItemStyles({
55
+ foregroundColor,
56
+ primaryColor,
57
+ hoverEffect,
58
+ isHovered,
59
+ variant,
60
+ })}
61
+ className={cn(navBarItemClasses({ hoverEffect, variant }))}
62
+ onMouseEnter={() => handleHover(id)}
63
+ onMouseLeave={() => handleHover(null)}
64
+ {...props}
65
+ >
66
+ {Icon && <Icon className="size-4" />}
67
+ {label && <p>{label}</p>}
68
+ </li>
69
+ );
70
+ });
@@ -0,0 +1,65 @@
1
+ import { cn } from "@/utils";
2
+ import { VariantProps } from "class-variance-authority";
3
+ import { forwardRef, ComponentProps } from "react";
4
+ import type { NavBarProps } from "@/types";
5
+ import { useEmperorUI } from "@/hooks";
6
+ import {
7
+ navBarClasses,
8
+ navBarMenuClasses,
9
+ navBarMenuStyles,
10
+ navBarStyles,
11
+ } from "./styles";
12
+ import { NavBarItem, SubItemsBox } from "@/components";
13
+
14
+ export const NavBar = forwardRef<
15
+ HTMLDivElement,
16
+ ComponentProps<"nav"> & VariantProps<typeof navBarClasses> & NavBarProps
17
+ >(
18
+ (
19
+ {
20
+ className,
21
+ hoverEffect = "default",
22
+ variant = "default",
23
+ items = [],
24
+ subItemsColumns = 3,
25
+ ...props
26
+ },
27
+ ref,
28
+ ) => {
29
+ const { config } = useEmperorUI();
30
+
31
+ const primaryColor = config?.theme?.colors?.primary;
32
+ const foregroundColor = config?.theme?.colors?.foreground;
33
+
34
+ return (
35
+ <nav
36
+ ref={ref}
37
+ data-slot="emperor-nav-bar"
38
+ className={cn(navBarClasses({ hoverEffect, variant, className }))}
39
+ style={navBarStyles({
40
+ foregroundColor,
41
+ primaryColor,
42
+ variant,
43
+ })}
44
+ {...props}
45
+ >
46
+ <ul
47
+ className={cn(navBarMenuClasses({ className }))}
48
+ style={navBarMenuStyles({ hoverEffect, variant })}
49
+ data-slot="emperor-nav-bar-menu"
50
+ >
51
+ {items?.map((item) => (
52
+ <NavBarItem
53
+ key={item.id}
54
+ item={item}
55
+ variant={variant}
56
+ hoverEffect={hoverEffect}
57
+ />
58
+ ))}
59
+ </ul>
60
+
61
+ <SubItemsBox subItemsColumns={subItemsColumns} />
62
+ </nav>
63
+ );
64
+ },
65
+ );
@@ -0,0 +1,52 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { NavBar } from "@/components";
3
+ import { getStorybookDecorators } from "@/utils";
4
+ import { MOCK_HEADER_ITEMS } from "@/mocks";
5
+
6
+ const meta: Meta<typeof NavBar> = {
7
+ title: "Molecules/NavBar/HoverEffect",
8
+ component: NavBar,
9
+ parameters: {
10
+ layout: "centered",
11
+ },
12
+ tags: ["autodocs"],
13
+ decorators: getStorybookDecorators({
14
+ config: {
15
+ layout: {
16
+ withScaffold: false,
17
+ },
18
+ },
19
+ }),
20
+ };
21
+
22
+ export default meta;
23
+
24
+ type Story = StoryObj<typeof meta>;
25
+
26
+ export const SolidHover: Story = {
27
+ args: {
28
+ items: MOCK_HEADER_ITEMS,
29
+ hoverEffect: "solid",
30
+ },
31
+ };
32
+
33
+ export const GhostHover: Story = {
34
+ args: {
35
+ items: MOCK_HEADER_ITEMS,
36
+ hoverEffect: "ghost",
37
+ },
38
+ };
39
+
40
+ export const BorderedHover: Story = {
41
+ args: {
42
+ items: MOCK_HEADER_ITEMS,
43
+ hoverEffect: "bordered",
44
+ },
45
+ };
46
+
47
+ export const UnderlinedHover: Story = {
48
+ args: {
49
+ items: MOCK_HEADER_ITEMS,
50
+ hoverEffect: "underline",
51
+ },
52
+ };