@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,50 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { NavBar } from "@/components";
3
+ import { getStorybookDecorators } from "@/utils";
4
+ import { MOCK_HEADER_ITEMS, MOCK_HEADER_ITEMS_WITH_SUB_ITEMS } from "@/mocks";
5
+
6
+ const meta: Meta<typeof NavBar> = {
7
+ title: "Molecules/NavBar",
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 Default: Story = {
27
+ args: {
28
+ items: MOCK_HEADER_ITEMS,
29
+ },
30
+ };
31
+
32
+ export const Solid: Story = {
33
+ args: {
34
+ items: MOCK_HEADER_ITEMS,
35
+ variant: "solid",
36
+ },
37
+ };
38
+
39
+ export const Bordered: Story = {
40
+ args: {
41
+ items: MOCK_HEADER_ITEMS,
42
+ variant: "bordered",
43
+ },
44
+ };
45
+
46
+ export const WithSubItems: Story = {
47
+ args: {
48
+ items: MOCK_HEADER_ITEMS_WITH_SUB_ITEMS,
49
+ },
50
+ };
@@ -0,0 +1,68 @@
1
+ import { cva } from "class-variance-authority";
2
+
3
+ export const navBarClasses = cva(["relative flex items-center gap-3"], {
4
+ variants: {
5
+ hoverEffect: {
6
+ default: [],
7
+ solid: [],
8
+ underline: [],
9
+ ghost: [],
10
+ bordered: [],
11
+ none: [],
12
+ },
13
+ variant: {
14
+ default: [],
15
+ solid: [],
16
+ bordered: [],
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ hoverEffect: "default",
21
+ variant: "default",
22
+ },
23
+ });
24
+
25
+ export const navBarMenuClasses = cva(["size-full flex items-center"], {
26
+ variants: {
27
+ variant: {
28
+ default: [],
29
+ solid: [],
30
+ bordered: [],
31
+ },
32
+ },
33
+ defaultVariants: {
34
+ variant: "default",
35
+ },
36
+ });
37
+
38
+ export const navBarItemClasses = cva(
39
+ [
40
+ "relative cursor-pointer px-4 py-2 transition-all font-semibold flex items-center gap-2",
41
+ ],
42
+ {
43
+ variants: {
44
+ hoverEffect: {
45
+ default: [],
46
+ solid: ["hover:opacity-80"],
47
+ underline: [
48
+ "relative font-bold",
49
+ "before:absolute before:bottom-0 before:left-0",
50
+ "before:h-0.5 before:w-full before:bg-current before:rounded-lg",
51
+ "before:scale-x-0 before:transition-transform hover:before:scale-x-100",
52
+ ],
53
+ ghost: [],
54
+ bordered: ["last:border-r-2! first:border-l-2!"],
55
+ none: [],
56
+ },
57
+ variant: {
58
+ default: [],
59
+ solid: [],
60
+ bordered: ["last:border-r-2! first:border-l-2!"],
61
+ },
62
+ },
63
+ defaultVariants: {
64
+ hoverEffect: "default",
65
+ variant: "default",
66
+ },
67
+ },
68
+ );
@@ -0,0 +1,2 @@
1
+ export * from "./classes";
2
+ export * from "./styles";
@@ -0,0 +1,85 @@
1
+ import {
2
+ NavBarItemStylesProps,
3
+ NavBarMenuStylesProps,
4
+ NavBarStylesProps,
5
+ } from "@/types";
6
+ import { CSSProperties } from "react";
7
+
8
+ export const navBarStyles = ({
9
+ primaryColor,
10
+ foregroundColor,
11
+ variant,
12
+ }: NavBarStylesProps): CSSProperties => {
13
+ if (variant === "solid") {
14
+ return {
15
+ backgroundColor: primaryColor,
16
+ color: foregroundColor,
17
+ };
18
+ }
19
+
20
+ return {};
21
+ };
22
+
23
+ // eslint-disable-next-line no-empty-pattern
24
+ export const navBarMenuStyles = ({}: NavBarMenuStylesProps): CSSProperties => {
25
+ return {};
26
+ };
27
+
28
+ export const navBarItemStyles = ({
29
+ foregroundColor,
30
+ hoverEffect,
31
+ primaryColor,
32
+ isHovered,
33
+ variant,
34
+ }: NavBarItemStylesProps): CSSProperties => {
35
+ if (hoverEffect === "solid") {
36
+ return {
37
+ color: foregroundColor,
38
+ backgroundColor: primaryColor,
39
+ };
40
+ }
41
+
42
+ if (hoverEffect === "underline") {
43
+ return {
44
+ // color: primaryColor,
45
+ };
46
+ }
47
+
48
+ if (hoverEffect === "ghost") {
49
+ return {
50
+ color: isHovered ? primaryColor : foregroundColor,
51
+ backgroundColor: isHovered ? "transparent" : primaryColor,
52
+ borderColor: isHovered ? primaryColor : "transparent",
53
+ borderWidth: "2px",
54
+ borderStyle: "solid",
55
+ };
56
+ }
57
+
58
+ if (hoverEffect === "bordered") {
59
+ return {
60
+ color: isHovered ? foregroundColor : primaryColor,
61
+ backgroundColor: isHovered ? primaryColor : "transparent",
62
+ borderColor: isHovered ? "transparent" : primaryColor,
63
+ borderTopWidth: "2px",
64
+ borderBottomWidth: "2px",
65
+ borderRightWidth: "1px",
66
+ borderLeftWidth: "1px",
67
+ borderStyle: "solid",
68
+ };
69
+ }
70
+
71
+ if (variant === "bordered") {
72
+ return {
73
+ color: primaryColor,
74
+ backgroundColor: "transparent",
75
+ borderColor: primaryColor,
76
+ borderTopWidth: "2px",
77
+ borderBottomWidth: "2px",
78
+ borderRightWidth: "1px",
79
+ borderLeftWidth: "1px",
80
+ borderStyle: "solid",
81
+ };
82
+ }
83
+
84
+ return {};
85
+ };
@@ -0,0 +1,57 @@
1
+ import { cn, Link } from "@heroui/react";
2
+ import { useNavigation } from "@/hooks";
3
+ import { SubItemsBoxProps } from "@/types";
4
+
5
+ export const SubItemsBox = ({ subItemsColumns = 3 }: SubItemsBoxProps) => {
6
+ const {
7
+ hoveredItemId,
8
+ subItems,
9
+ isSubItemsBoxOpen,
10
+ subItemsBoxIsHovered,
11
+ setSubItemsBoxIsHovered,
12
+ setIsSubItemsBoxOpen,
13
+ } = useNavigation();
14
+
15
+ const isOpen = isSubItemsBoxOpen || subItemsBoxIsHovered;
16
+
17
+ return (
18
+ <div
19
+ className={cn(
20
+ "absolute top-full left-0 w-full overflow-hidden bg-background",
21
+ "transition-all duration-300 ease-in-out z-50",
22
+ )}
23
+ style={{
24
+ maxHeight: isOpen
25
+ ? `${Number(subItems.length / subItemsColumns) * 70}px`
26
+ : "0px",
27
+ }}
28
+ onMouseEnter={() => {
29
+ setSubItemsBoxIsHovered(true);
30
+ setIsSubItemsBoxOpen(true);
31
+ }}
32
+ onMouseLeave={() => {
33
+ setSubItemsBoxIsHovered(false);
34
+ if (!hoveredItemId) setIsSubItemsBoxOpen(false);
35
+ }}
36
+ >
37
+ <ul
38
+ className="grid border border-gray-200"
39
+ style={{ gridTemplateColumns: `repeat(${subItemsColumns}, 1fr)` }}
40
+ >
41
+ {subItems?.map(({ id, href, label, Icon }) => (
42
+ <Link
43
+ key={id}
44
+ href={href}
45
+ className={cn(
46
+ " w-full flex items-center gap-2 text-foreground p-2 transition-all duration-300 ease-in-out",
47
+ "hover:text-background hover:bg-primary",
48
+ )}
49
+ >
50
+ {Icon && <Icon className="size-4" />}
51
+ {label && <p>{label}</p>}
52
+ </Link>
53
+ ))}
54
+ </ul>
55
+ </div>
56
+ );
57
+ };
@@ -0,0 +1,21 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { Scaffold } from "@/components";
3
+ import { getStorybookDecorators } from "@/utils";
4
+
5
+ const meta: Meta<typeof Scaffold> = {
6
+ title: "Molecules/Scaffold",
7
+ component: Scaffold,
8
+ parameters: {
9
+ layout: "fullscreen",
10
+ },
11
+ tags: ["autodocs"],
12
+ decorators: getStorybookDecorators({}),
13
+ };
14
+
15
+ export default meta;
16
+
17
+ type Story = StoryObj<typeof meta>;
18
+
19
+ export const Default: Story = {
20
+ args: {},
21
+ };
@@ -0,0 +1,36 @@
1
+ import { useEmperorUI } from "@/hooks";
2
+ import type { ScaffoldProps } from "@/types";
3
+ import { cn } from "@/utils";
4
+ import { cva } from "class-variance-authority";
5
+ import { forwardRef } from "react";
6
+
7
+ const scaffoldStyles = cva(["flex flex-col min-h-screen w-full max-w-screen"], {
8
+ variants: {},
9
+ defaultVariants: {},
10
+ compoundVariants: [],
11
+ });
12
+
13
+ export const Scaffold = forwardRef<HTMLDivElement, ScaffoldProps>(
14
+ ({ className, children, ...props }, ref) => {
15
+ const { config } = useEmperorUI();
16
+
17
+ const backgroundColor = config?.theme?.colors?.background;
18
+ const foregroundColor = config?.theme?.colors?.foreground;
19
+
20
+ return (
21
+ <div
22
+ ref={ref}
23
+ dir={config?.interLocalization?.dir}
24
+ data-slot="scaffold"
25
+ className={cn(scaffoldStyles({ className }), className)}
26
+ style={{
27
+ backgroundColor: backgroundColor,
28
+ color: foregroundColor,
29
+ }}
30
+ {...props}
31
+ >
32
+ {children}
33
+ </div>
34
+ );
35
+ },
36
+ );
@@ -0,0 +1,75 @@
1
+ import { createPortal } from "react-dom";
2
+ import { useEffect, useState } from "react";
3
+ import { cn } from "@/utils";
4
+ import { Button, Divider, ScrollShadow } from "@heroui/react";
5
+ import { CompactSideBarProps } from "@/types";
6
+
7
+ const CompactSideBarContent = ({
8
+ items = [],
9
+ isOpen,
10
+ onOpenChange,
11
+ triggerProps,
12
+ }: CompactSideBarProps) => {
13
+ const { content, ...restTriggerProps } = triggerProps || {};
14
+
15
+ return (
16
+ <div
17
+ className={cn(
18
+ "flex flex-col gap-4 p-2",
19
+ "h-[calc(100vh-16px)] bg-gray-200 m-2 rounded-lg overflow-hidden",
20
+ "transition-width ease-in-out duration-300",
21
+ isOpen ? "w-[20vw]" : "w-[48px]",
22
+ )}
23
+ >
24
+ <div className="w-fit flex flex-col">
25
+ <Button
26
+ variant="light"
27
+ size="sm"
28
+ onPress={() => onOpenChange?.(true)}
29
+ className={cn("min-w-7 w-fit px-0.5")}
30
+ {...restTriggerProps}
31
+ >
32
+ {content}
33
+ </Button>
34
+ </div>
35
+
36
+ <Divider />
37
+
38
+ <ScrollShadow
39
+ as="ul"
40
+ hideScrollBar
41
+ className="max-h-[70vh] flex flex-col gap-4 p-0 overflow-y-auto"
42
+ >
43
+ {items?.map(({ id, Icon, label }) => (
44
+ <li
45
+ key={id}
46
+ className="w-fit flex items-center justify-center gap-3 ms-1.5 cursor-pointer"
47
+ >
48
+ {Icon && <Icon className="size-5" />}
49
+
50
+ {label && isOpen && (
51
+ <p className={cn("text-sm font-semibold")}>{label}</p>
52
+ )}
53
+ </li>
54
+ ))}
55
+ </ScrollShadow>
56
+ </div>
57
+ );
58
+ };
59
+
60
+ export const CompactSideBar = (props: CompactSideBarProps) => {
61
+ const [container, setContainer] = useState<HTMLElement | null>(null);
62
+
63
+ useEffect(() => {
64
+ const element = document.getElementById("emperor-compact-sidebar");
65
+ setTimeout(() => {
66
+ setContainer(element);
67
+ }, 100);
68
+ }, []);
69
+
70
+ if (!container) {
71
+ return null;
72
+ }
73
+
74
+ return createPortal(<CompactSideBarContent {...props} />, container);
75
+ };
@@ -0,0 +1 @@
1
+ export * from "./side-bar";
@@ -0,0 +1,124 @@
1
+ import { useEmperorUI } from "@/hooks";
2
+ import { useState } from "react";
3
+ import {
4
+ Button,
5
+ Drawer,
6
+ DrawerHeader,
7
+ DrawerBody,
8
+ DrawerContent,
9
+ ScrollShadow,
10
+ Divider,
11
+ DrawerFooter,
12
+ } from "@heroui/react";
13
+ import { SideBarProps } from "@/types";
14
+ import { cn } from "@/utils";
15
+ import { sideBarItemClasses, sideBarItemStyles } from "./styles";
16
+
17
+ export const SideBarDrawer = ({
18
+ isOpen,
19
+ onOpenChange,
20
+ triggerProps,
21
+ classNames,
22
+ header,
23
+ items = [],
24
+ actions = [],
25
+ variant = "default",
26
+ }: SideBarProps) => {
27
+ const { config } = useEmperorUI();
28
+ const [hoveredItem, setHoveredItem] = useState<string | null>(null);
29
+ const { content, ...restTriggerProps } = triggerProps || {};
30
+
31
+ const dir = config?.interLocalization?.dir;
32
+ const backgroundColor = config?.theme?.colors?.background;
33
+ const primaryColor = config?.theme?.colors?.primary;
34
+ const foregroundColor = config?.theme?.colors?.foreground;
35
+
36
+ const isRTL = dir === "rtl";
37
+
38
+ return (
39
+ <>
40
+ <Button
41
+ variant="ghost"
42
+ isIconOnly
43
+ size="sm"
44
+ onPress={() => onOpenChange?.(true)}
45
+ className={cn(classNames?.trigger)}
46
+ {...restTriggerProps}
47
+ >
48
+ {content}
49
+ </Button>
50
+
51
+ <Drawer
52
+ isOpen={isOpen}
53
+ onOpenChange={onOpenChange}
54
+ placement={isRTL ? "right" : "left"}
55
+ size="sm"
56
+ dir={dir}
57
+ className="p-0"
58
+ >
59
+ <DrawerContent
60
+ className="p-0"
61
+ style={{ backgroundColor, color: foregroundColor }}
62
+ >
63
+ <DrawerHeader className={cn("text-xl font-bold", classNames?.title)}>
64
+ {header}
65
+ </DrawerHeader>
66
+
67
+ <DrawerBody className={cn("p-0 mt-4 h-fit")}>
68
+ <ScrollShadow
69
+ as="ul"
70
+ hideScrollBar
71
+ data-slot="emperor-side-bar-menu"
72
+ className={cn("max-h-[60vh] overflow-y-auto", classNames?.menu)}
73
+ >
74
+ {items?.map(({ id, label, href, Icon }) => (
75
+ <li
76
+ key={href}
77
+ data-slot="emperor-side-bar-item"
78
+ style={sideBarItemStyles({
79
+ foregroundColor,
80
+ primaryColor,
81
+ isHovered: hoveredItem === id,
82
+ variant,
83
+ })}
84
+ className={cn(
85
+ sideBarItemClasses({ variant }),
86
+ classNames?.menuItem,
87
+ )}
88
+ onMouseEnter={() => setHoveredItem(id)}
89
+ onMouseLeave={() => setHoveredItem(null)}
90
+ >
91
+ {Icon && <Icon className="size-4" />}
92
+ {label && <p>{label}</p>}
93
+ </li>
94
+ ))}
95
+ </ScrollShadow>
96
+ </DrawerBody>
97
+
98
+ {actions && actions?.length > 0 && (
99
+ <>
100
+ <Divider
101
+ className="m-auto w-11/12"
102
+ style={{ backgroundColor: foregroundColor + "20" }}
103
+ />
104
+
105
+ <DrawerFooter className={cn(classNames?.actionsWrapper)}>
106
+ {actions?.map(({ key, label, ...props }) => (
107
+ <Button
108
+ key={key}
109
+ variant="ghost"
110
+ size="sm"
111
+ className={cn(classNames?.actionItem)}
112
+ {...props}
113
+ >
114
+ {label}
115
+ </Button>
116
+ ))}
117
+ </DrawerFooter>
118
+ </>
119
+ )}
120
+ </DrawerContent>
121
+ </Drawer>
122
+ </>
123
+ );
124
+ };
@@ -0,0 +1,111 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { Brand, SideBar } from "@/components";
3
+ import { getStorybookDecorators } from "@/utils";
4
+ import { MenuIcon } from "lucide-react";
5
+ import { useDisclosure } from "@heroui/react";
6
+ import { MOCK_HEADER_ACTIONS, MOCK_HEADER_ITEMS } from "@/mocks";
7
+ import { SideBarProps } from "@/types";
8
+
9
+ const meta: Meta<typeof SideBar> = {
10
+ title: "Molecules/SideBar",
11
+ component: SideBar,
12
+ parameters: {
13
+ layout: "fullscreen",
14
+ },
15
+ tags: ["autodocs"],
16
+ decorators: getStorybookDecorators({
17
+ config: {
18
+ layout: {
19
+ withScaffold: false,
20
+ },
21
+ },
22
+ }),
23
+ };
24
+
25
+ export default meta;
26
+
27
+ type Story = StoryObj<typeof meta>;
28
+
29
+ export const Default: Story = {
30
+ args: {
31
+ header: <Brand className="text-white" />,
32
+ triggerProps: {
33
+ className: "m-5",
34
+ startContent: <MenuIcon className="size-4" />,
35
+ },
36
+ },
37
+ render: (args: SideBarProps) => {
38
+ const { isOpen, onOpenChange } = useDisclosure();
39
+
40
+ return <SideBar {...args} isOpen={isOpen} onOpenChange={onOpenChange} />;
41
+ },
42
+ };
43
+
44
+ export const WithItems: Story = {
45
+ args: {
46
+ header: <Brand className="text-white" />,
47
+ triggerProps: {
48
+ className: "m-5",
49
+ startContent: <MenuIcon className="size-4" />,
50
+ },
51
+ },
52
+ render: (args: SideBarProps) => {
53
+ const { isOpen, onOpenChange } = useDisclosure();
54
+
55
+ return (
56
+ <SideBar
57
+ {...args}
58
+ isOpen={isOpen}
59
+ onOpenChange={onOpenChange}
60
+ items={MOCK_HEADER_ITEMS}
61
+ />
62
+ );
63
+ },
64
+ };
65
+
66
+ export const WithActions: Story = {
67
+ args: {
68
+ header: <Brand className="text-white" />,
69
+ triggerProps: {
70
+ className: "m-5",
71
+ startContent: <MenuIcon className="size-4" />,
72
+ },
73
+ },
74
+ render: (args: SideBarProps) => {
75
+ const { isOpen, onOpenChange } = useDisclosure();
76
+
77
+ return (
78
+ <SideBar
79
+ {...args}
80
+ isOpen={isOpen}
81
+ onOpenChange={onOpenChange}
82
+ items={MOCK_HEADER_ITEMS}
83
+ actions={MOCK_HEADER_ACTIONS}
84
+ />
85
+ );
86
+ },
87
+ };
88
+
89
+ export const Compact: Story = {
90
+ args: {
91
+ variant: "compact",
92
+ },
93
+ render: (args: SideBarProps) => {
94
+ const { isOpen, onOpenChange } = useDisclosure();
95
+
96
+ return (
97
+ <SideBar
98
+ {...args}
99
+ isOpen={isOpen}
100
+ onOpenChange={onOpenChange}
101
+ items={MOCK_HEADER_ITEMS}
102
+ actions={MOCK_HEADER_ACTIONS}
103
+ triggerProps={{
104
+ content: (
105
+ <Brand classNames={{ logo: "size-7" }} isIconOnly={!isOpen} />
106
+ ),
107
+ }}
108
+ />
109
+ );
110
+ },
111
+ };
@@ -0,0 +1,31 @@
1
+ import { cn } from "@/utils";
2
+ import { VariantProps } from "class-variance-authority";
3
+ import { forwardRef, ComponentProps } from "react";
4
+ import { sideBarClasses } from "./styles";
5
+ import type { SideBarProps } from "@/types";
6
+ import { SideBarDrawer } from "./side-bar-drawer";
7
+ import { CompactSideBar } from "./compact-side-bar";
8
+
9
+ export const SideBar = forwardRef<
10
+ HTMLDivElement,
11
+ ComponentProps<"div"> & VariantProps<typeof sideBarClasses> & SideBarProps
12
+ >((props, ref) => {
13
+ const { variant = "default", className, classNames } = props;
14
+
15
+ return (
16
+ <div
17
+ ref={ref}
18
+ data-slot="emperor-side-bar"
19
+ className={cn(
20
+ sideBarClasses({
21
+ variant,
22
+ className: cn(className, classNames?.base),
23
+ }),
24
+ )}
25
+ {...props}
26
+ >
27
+ {variant === "default" && <SideBarDrawer {...props} />}
28
+ {variant === "compact" && <CompactSideBar {...props} />}
29
+ </div>
30
+ );
31
+ });
@@ -0,0 +1,28 @@
1
+ import { cva } from "class-variance-authority";
2
+
3
+ export const sideBarClasses = cva([""], {
4
+ variants: {
5
+ variant: {
6
+ default: [],
7
+ compact: [],
8
+ },
9
+ },
10
+ defaultVariants: {},
11
+ compoundVariants: [],
12
+ });
13
+
14
+ export const sideBarItemClasses = cva(
15
+ [
16
+ "w-full cursor-pointer px-6 py-2 transition-all font-semibold text-sm flex items-center gap-2",
17
+ ],
18
+ {
19
+ variants: {
20
+ variant: {
21
+ default: [],
22
+ compact: [],
23
+ },
24
+ },
25
+ defaultVariants: {},
26
+ compoundVariants: [],
27
+ },
28
+ );
@@ -0,0 +1,2 @@
1
+ export * from "./styles";
2
+ export * from "./classes";