@js-empire/emperor-ui 1.2.3 → 1.2.5

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 (157) hide show
  1. package/.cursor/rules/code-conventions.mdc +50 -0
  2. package/README.md +222 -31
  3. package/dist/emperor-ui.js +119 -71
  4. package/dist/emperor-ui.umd.cjs +27 -13
  5. package/dist/globals.css +1 -1
  6. package/dist/index-BXtdEByK.js +5 -0
  7. package/dist/index-CDB93OLO.js +55965 -0
  8. package/dist/index-CYORMghp.js +290 -0
  9. package/dist/index.d.ts +334 -33
  10. package/dist/src-UW24ZMRV-C1Pn8-w8.js +5 -0
  11. package/package.json +32 -2
  12. package/src/animations/blink.ts +26 -0
  13. package/src/animations/floating.ts +12 -0
  14. package/src/animations/index.ts +2 -0
  15. package/src/components/atoms/brand/brand.tsx +1 -1
  16. package/src/components/atoms/color-picker/color-picker.tsx +13 -0
  17. package/src/components/atoms/color-picker/free-color-picker.tsx +60 -0
  18. package/src/components/atoms/color-picker/index.ts +3 -0
  19. package/src/components/atoms/color-picker/preset-color-picker.tsx +64 -0
  20. package/src/components/atoms/color-picker/stories/color-picker.stories.tsx +49 -0
  21. package/src/components/atoms/color-picker/styles/color-picker.css +23 -0
  22. package/src/components/atoms/copy-button/copy-button.tsx +73 -0
  23. package/src/components/atoms/copy-button/index.ts +1 -0
  24. package/src/components/atoms/copy-button/stories/copy-button.stories.tsx +21 -0
  25. package/src/components/atoms/field/field.stories.tsx +27 -0
  26. package/src/components/atoms/field/field.tsx +11 -0
  27. package/src/components/atoms/field/index.ts +1 -0
  28. package/src/components/atoms/field/styles/classes.ts +9 -0
  29. package/src/components/atoms/field/styles/index.ts +1 -0
  30. package/src/components/atoms/filter/filter.tsx +92 -0
  31. package/src/components/atoms/filter/index.ts +3 -0
  32. package/src/components/atoms/filter/stories/filter.stories.tsx +97 -0
  33. package/src/components/atoms/filter/styles/classes.ts +20 -0
  34. package/src/components/atoms/filter/styles/index.ts +1 -0
  35. package/src/components/atoms/filter/units/autocomplete-filter.tsx +39 -0
  36. package/src/components/atoms/filter/units/checkbox-filter.tsx +32 -0
  37. package/src/components/atoms/filter/units/checkbox-group-filter.tsx +37 -0
  38. package/src/components/atoms/filter/units/date-filter.tsx +50 -0
  39. package/src/components/atoms/filter/units/index.ts +9 -0
  40. package/src/components/atoms/filter/units/numeric-filter.tsx +36 -0
  41. package/src/components/atoms/filter/units/range-filter.tsx +36 -0
  42. package/src/components/atoms/filter/units/search-filter.tsx +52 -0
  43. package/src/components/atoms/filter/units/select-filter.tsx +49 -0
  44. package/src/components/atoms/filter/units/switch-filter.tsx +33 -0
  45. package/src/components/atoms/index.ts +5 -0
  46. package/src/components/atoms/theme-switch/index.ts +1 -0
  47. package/src/components/atoms/theme-switch/styles/classes.ts +16 -0
  48. package/src/components/atoms/theme-switch/styles/index.ts +1 -0
  49. package/src/components/atoms/theme-switch/theme-switch.stories.tsx +26 -0
  50. package/src/components/atoms/theme-switch/theme-switch.tsx +54 -0
  51. package/src/components/atoms/uploader/avatar-label.tsx +3 -1
  52. package/src/components/atoms/uploader/stories/uploader.stories.tsx +1 -1
  53. package/src/components/atoms/uploader/upload-file-error-box.tsx +1 -1
  54. package/src/components/atoms/uploader/upload-file-input.tsx +1 -1
  55. package/src/components/atoms/uploader/upload-file-label.tsx +2 -1
  56. package/src/components/atoms/uploader/upload-file-listing.tsx +2 -1
  57. package/src/components/atoms/uploader/view-image-modal.tsx +2 -1
  58. package/src/components/molecules/index.ts +0 -1
  59. package/src/components/molecules/item-card/index.ts +6 -0
  60. package/src/components/molecules/item-card/item-actions-dropdown.tsx +57 -0
  61. package/src/components/molecules/item-card/item-banner.tsx +22 -0
  62. package/src/components/molecules/item-card/item-card-body.tsx +68 -0
  63. package/src/components/molecules/item-card/item-card-footer.tsx +55 -0
  64. package/src/components/molecules/item-card/item-card-header.tsx +61 -0
  65. package/src/components/molecules/item-card/item-card.tsx +83 -3
  66. package/src/components/molecules/item-card/loading-item.tsx +88 -0
  67. package/src/components/molecules/item-card/stories/item-card.stories.tsx +182 -0
  68. package/src/components/molecules/item-card/styles/classes.ts +138 -0
  69. package/src/components/molecules/item-card/styles/index.ts +1 -0
  70. package/src/components/molecules/nav-bar/sub-items-box.tsx +2 -1
  71. package/src/components/molecules/scaffold/index.ts +1 -0
  72. package/src/components/molecules/scaffold/scaffold.tsx +4 -17
  73. package/src/components/molecules/scaffold/styles/index.ts +1 -0
  74. package/src/components/molecules/scaffold/styles/scaffold-classes.ts +10 -0
  75. package/src/components/molecules/side-bar/compact-side-bar.tsx +3 -1
  76. package/src/components/molecules/side-bar/side-bar-drawer.tsx +6 -17
  77. package/src/components/molecules/side-bar/side-bar.stories.tsx +1 -1
  78. package/src/components/organisms/filters/filters.stories.tsx +32 -0
  79. package/src/components/organisms/filters/filters.tsx +36 -0
  80. package/src/components/organisms/filters/index.ts +1 -0
  81. package/src/components/organisms/filters/styles/classes.ts +9 -0
  82. package/src/components/organisms/filters/styles/index.ts +1 -0
  83. package/src/components/organisms/footer/copy-rights-box.tsx +1 -1
  84. package/src/components/organisms/footer/footer.tsx +1 -1
  85. package/src/components/organisms/footer/policies-box.tsx +2 -1
  86. package/src/components/organisms/footer/quick-links-box.tsx +2 -1
  87. package/src/components/organisms/footer/social-links-box.tsx +2 -1
  88. package/src/components/organisms/footer/stories/footer.stories.tsx +1 -1
  89. package/src/components/organisms/header/header.tsx +1 -8
  90. package/src/components/organisms/index.ts +1 -0
  91. package/src/components/organisms/listings/empty-listings.tsx +80 -0
  92. package/src/components/organisms/listings/index.ts +2 -0
  93. package/src/components/organisms/listings/listings.tsx +90 -9
  94. package/src/components/organisms/listings/stories/grid-listings.stories.tsx +153 -0
  95. package/src/components/organisms/listings/stories/list-listings.stories.tsx +171 -0
  96. package/src/components/organisms/listings/styles/classes.ts +41 -3
  97. package/src/constants/animations.ts +14 -0
  98. package/src/constants/card.tsx +26 -0
  99. package/src/constants/defaults.ts +1 -16
  100. package/src/constants/index.ts +2 -0
  101. package/src/hooks/index.ts +3 -0
  102. package/src/hooks/use-filters.ts +20 -0
  103. package/src/hooks/use-search-params-handler.tsx +186 -0
  104. package/src/hooks/use-uploader.tsx +1 -1
  105. package/src/hooks/use-window-size.tsx +53 -0
  106. package/src/i18n/locales/atoms/ar.ts +3 -0
  107. package/src/i18n/locales/atoms/en.ts +3 -0
  108. package/src/i18n/locales/organisms/ar.ts +7 -1
  109. package/src/i18n/locales/organisms/en.ts +7 -1
  110. package/src/mocks/constants.ts +103 -0
  111. package/src/mocks/index.ts +2 -0
  112. package/src/mocks/listings.tsx +154 -0
  113. package/src/mocks/types.ts +64 -0
  114. package/src/providers/config-provider.tsx +0 -8
  115. package/src/providers/emperor-ui-provider.tsx +16 -5
  116. package/src/providers/index.ts +1 -0
  117. package/src/providers/theme-provider.tsx +16 -0
  118. package/src/providers/uploader-provider.tsx +1 -1
  119. package/src/styles/hero.ts +1 -1
  120. package/src/styles/index.css +23 -0
  121. package/src/types/components/atoms/color-picker/color-picker.ts +12 -0
  122. package/src/types/components/atoms/color-picker/index.ts +1 -0
  123. package/src/types/components/atoms/field/field.ts +9 -0
  124. package/src/types/components/atoms/field/index.ts +1 -0
  125. package/src/types/components/atoms/filter/filter.ts +43 -0
  126. package/src/types/components/atoms/filter/index.ts +2 -0
  127. package/src/types/components/atoms/filter/select-filter.ts +8 -0
  128. package/src/types/components/atoms/index.ts +3 -0
  129. package/src/types/components/atoms/uploader.ts +1 -1
  130. package/src/types/components/index.ts +1 -0
  131. package/src/types/components/molecules/index.ts +1 -1
  132. package/src/types/components/molecules/item-card/item-card.ts +50 -0
  133. package/src/types/components/molecules/listings/listings.ts +21 -5
  134. package/src/types/components/molecules/side-bar/side-bar.ts +1 -1
  135. package/src/types/components/molecules/theme-switch/index.ts +1 -0
  136. package/src/types/components/molecules/theme-switch/theme-switch.ts +9 -0
  137. package/src/types/components/organisms/filters/filters.ts +11 -0
  138. package/src/types/components/organisms/filters/index.ts +1 -0
  139. package/src/types/components/organisms/index.ts +1 -0
  140. package/src/types/context/config.ts +3 -4
  141. package/src/types/context/index.ts +0 -1
  142. package/src/types/context/localization.ts +1 -0
  143. package/src/types/shared/components.ts +3 -0
  144. package/src/utils/uploader.ts +1 -1
  145. package/dist/features-animation-w9dWMd15.js +0 -1938
  146. package/dist/index-BY47HgaP.js +0 -26533
  147. package/dist/index-CN4cJ1N7.js +0 -1630
  148. package/dist/index-Cr1mc-d4.js +0 -5
  149. package/dist/src-UW24ZMRV-nsR4cpiy.js +0 -5
  150. package/src/components/molecules/filter/filter.tsx +0 -6
  151. package/src/components/molecules/filter/index.ts +0 -1
  152. package/src/components/organisms/listings/stories/listings.stories.tsx +0 -30
  153. package/src/main.tsx +0 -3
  154. package/src/mocks/listings.ts +0 -200
  155. package/src/types/components/molecules/filter/filter.ts +0 -9
  156. package/src/types/components/molecules/filter/index.ts +0 -1
  157. package/src/types/context/theme.ts +0 -17
@@ -0,0 +1,32 @@
1
+ "use client";
2
+
3
+ import { FilterProps } from "@/types";
4
+ import { filterClasses } from "@/components";
5
+ import { Checkbox } from "@heroui/checkbox";
6
+ import { cn } from "@/utils";
7
+ import { useSearchParamsHandler } from "@/hooks";
8
+
9
+ export function CheckboxFilter({
10
+ classNames,
11
+ checkboxProps,
12
+ paramKey,
13
+ ...props
14
+ }: Pick<FilterProps, "classNames" | "checkboxProps" | "paramKey">) {
15
+ const { getParam, setParams } = useSearchParamsHandler();
16
+ const value = getParam(paramKey);
17
+ const isSelected = value === "true" || value === "1" || value === "yes";
18
+
19
+ return (
20
+ <Checkbox
21
+ isSelected={isSelected}
22
+ onValueChange={(selected) =>
23
+ setParams({
24
+ params: { [paramKey]: selected ? "true" : undefined },
25
+ })
26
+ }
27
+ {...checkboxProps}
28
+ {...props}
29
+ className={cn(filterClasses({ type: "checkbox" }), classNames?.field)}
30
+ />
31
+ );
32
+ }
@@ -0,0 +1,37 @@
1
+ "use client";
2
+
3
+ import { FilterProps } from "@/types";
4
+ import { filterClasses } from "@/components";
5
+ import { CheckboxGroup } from "@heroui/checkbox";
6
+ import { cn } from "@/utils";
7
+ import { useSearchParamsHandler } from "@/hooks";
8
+
9
+ export function CheckboxGroupFilter({
10
+ classNames,
11
+ checkboxGroupProps,
12
+ paramKey,
13
+ ...props
14
+ }: Pick<FilterProps, "classNames" | "checkboxGroupProps" | "paramKey">) {
15
+ const { getParam, setParams } = useSearchParamsHandler();
16
+ const valueStr = getParam(paramKey);
17
+ const value = valueStr ? valueStr.split(",").filter(Boolean) : [];
18
+
19
+ return (
20
+ <CheckboxGroup
21
+ value={value}
22
+ onValueChange={(values) =>
23
+ setParams({
24
+ params: {
25
+ [paramKey]: values.length > 0 ? values.join(",") : undefined,
26
+ },
27
+ })
28
+ }
29
+ {...checkboxGroupProps}
30
+ {...props}
31
+ className={cn(
32
+ filterClasses({ type: "checkboxGroup" }),
33
+ classNames?.field,
34
+ )}
35
+ />
36
+ );
37
+ }
@@ -0,0 +1,50 @@
1
+ "use client";
2
+
3
+ import { FilterProps } from "@/types";
4
+ import { filterClasses } from "@/components";
5
+ import { DatePicker } from "@heroui/date-picker";
6
+ import { cn } from "@/utils";
7
+ import { useSearchParamsHandler } from "@/hooks";
8
+ import { parseDate, type DateValue } from "@internationalized/date";
9
+ import { useMemo } from "react";
10
+
11
+ export function DateFilter({
12
+ classNames,
13
+ dateProps,
14
+ paramKey,
15
+ ...props
16
+ }: Pick<FilterProps, "classNames" | "dateProps" | "paramKey">) {
17
+ const { getParam, setParams } = useSearchParamsHandler();
18
+ const valueStr = getParam(paramKey);
19
+
20
+ const value = useMemo(() => {
21
+ if (!valueStr) return null;
22
+
23
+ try {
24
+ return parseDate(valueStr);
25
+ } catch {
26
+ return null;
27
+ }
28
+ }, [valueStr]);
29
+
30
+ return (
31
+ <DatePicker
32
+ labelPlacement="outside-top"
33
+ variant="faded"
34
+ radius="sm"
35
+ value={value}
36
+ onChange={(date: DateValue | null) =>
37
+ setParams({
38
+ params: {
39
+ [paramKey]: date
40
+ ? `${date.year}-${String(date.month).padStart(2, "0")}-${String(date.day).padStart(2, "0")}`
41
+ : undefined,
42
+ },
43
+ })
44
+ }
45
+ {...dateProps}
46
+ {...props}
47
+ className={cn(filterClasses({ type: "date" }), classNames?.field)}
48
+ />
49
+ );
50
+ }
@@ -0,0 +1,9 @@
1
+ export * from "./search-filter";
2
+ export * from "./select-filter";
3
+ export * from "./autocomplete-filter";
4
+ export * from "./date-filter";
5
+ export * from "./numeric-filter";
6
+ export * from "./checkbox-filter";
7
+ export * from "./checkbox-group-filter";
8
+ export * from "./switch-filter";
9
+ export * from "./range-filter";
@@ -0,0 +1,36 @@
1
+ "use client";
2
+
3
+ import { FilterProps } from "@/types";
4
+ import { filterClasses } from "@/components";
5
+ import { Input } from "@heroui/input";
6
+ import { cn } from "@/utils";
7
+ import { useSearchParamsHandler } from "@/hooks";
8
+
9
+ export function NumericFilter({
10
+ classNames,
11
+ numericProps,
12
+ paramKey,
13
+ ...props
14
+ }: Pick<FilterProps, "classNames" | "numericProps" | "paramKey">) {
15
+ const { getParam, setParams } = useSearchParamsHandler();
16
+
17
+ const value = getParam(paramKey);
18
+
19
+ return (
20
+ <Input
21
+ type="number"
22
+ labelPlacement="outside-top"
23
+ variant="faded"
24
+ radius="sm"
25
+ value={value ?? ""}
26
+ onValueChange={(v) =>
27
+ setParams({
28
+ params: { [paramKey]: v || undefined },
29
+ })
30
+ }
31
+ {...numericProps}
32
+ {...props}
33
+ className={cn(filterClasses({ type: "numeric" }), classNames?.field)}
34
+ />
35
+ );
36
+ }
@@ -0,0 +1,36 @@
1
+ "use client";
2
+
3
+ import { FilterProps } from "@/types";
4
+ import { filterClasses } from "@/components";
5
+ import { Slider } from "@heroui/slider";
6
+ import { cn } from "@/utils";
7
+ import { useSearchParamsHandler } from "@/hooks";
8
+
9
+ export function RangeFilter({
10
+ classNames,
11
+ rangeProps,
12
+ paramKey,
13
+ ...props
14
+ }: Pick<FilterProps, "classNames" | "rangeProps" | "paramKey">) {
15
+ const { getParam, setParams } = useSearchParamsHandler();
16
+
17
+ const valueStr = getParam(paramKey);
18
+ const value = valueStr != null ? Number(valueStr) : undefined;
19
+ const numValue = value != null && !Number.isNaN(value) ? value : undefined;
20
+
21
+ return (
22
+ <Slider
23
+ value={numValue}
24
+ onChange={(v) =>
25
+ setParams({
26
+ params: {
27
+ [paramKey]: v != null ? String(v) : undefined,
28
+ },
29
+ })
30
+ }
31
+ {...rangeProps}
32
+ {...props}
33
+ className={cn(filterClasses({ type: "range" }), classNames?.field)}
34
+ />
35
+ );
36
+ }
@@ -0,0 +1,52 @@
1
+ "use client";
2
+
3
+ import { FilterProps } from "@/types";
4
+ import { filterClasses } from "@/components";
5
+ import { Input } from "@heroui/input";
6
+ import { cn } from "@/utils";
7
+ import { useEffect, useState } from "react";
8
+ import { useDebounce } from "use-debounce";
9
+ import { Search } from "lucide-react";
10
+ import { useSearchParamsHandler } from "@/hooks";
11
+
12
+ export function SearchFilter({
13
+ classNames,
14
+ searchProps,
15
+ paramKey,
16
+ ...props
17
+ }: Pick<FilterProps, "classNames" | "searchProps" | "paramKey">) {
18
+ const [searchValue, setSearchValue] = useState(
19
+ searchProps?.defaultValue || "",
20
+ );
21
+ const { setParams } = useSearchParamsHandler();
22
+
23
+ const [debouncedSearchValue] = useDebounce(searchValue, 700);
24
+
25
+ useEffect(() => {
26
+ setParams({
27
+ params: {
28
+ [paramKey]: debouncedSearchValue,
29
+ },
30
+ });
31
+ // eslint-disable-next-line react-hooks/exhaustive-deps
32
+ }, [debouncedSearchValue]);
33
+
34
+ return (
35
+ <Input
36
+ labelPlacement="outside-top"
37
+ variant="faded"
38
+ radius="sm"
39
+ endContent={<Search className="size-4" />}
40
+ {...searchProps}
41
+ {...props}
42
+ value={searchValue}
43
+ onValueChange={setSearchValue}
44
+ className={cn(filterClasses({ type: "search" }), classNames?.field)}
45
+ classNames={{
46
+ input: "min-w-40",
47
+ label: "font-semibold",
48
+ ...searchProps?.classNames,
49
+ }}
50
+ />
51
+ );
52
+ }
@@ -0,0 +1,49 @@
1
+ "use client";
2
+
3
+ import { FilterProps } from "@/types";
4
+ import { filterClasses } from "@/components";
5
+ import { Select, SelectItem } from "@heroui/select";
6
+ import { cn } from "@/utils";
7
+ import { useSearchParamsHandler } from "@/hooks";
8
+
9
+ export function SelectFilter({
10
+ classNames,
11
+ selectProps,
12
+ paramKey,
13
+ options,
14
+ ...props
15
+ }: Pick<FilterProps, "classNames" | "selectProps" | "paramKey" | "options">) {
16
+ const { getParam, setParams } = useSearchParamsHandler();
17
+ const key = paramKey ?? "select";
18
+ const value = getParam(key);
19
+
20
+ if (!options?.length)
21
+ throw new Error("Filter with type 'select' must have 'options' property.");
22
+
23
+ return (
24
+ <Select
25
+ labelPlacement="outside-top"
26
+ variant="faded"
27
+ radius="sm"
28
+ selectedKeys={value ? [value] : []}
29
+ onSelectionChange={(keys) => {
30
+ const selectedKey = Array.from(keys)[0];
31
+ setParams({
32
+ params: { [key]: selectedKey ? String(selectedKey) : undefined },
33
+ });
34
+ }}
35
+ {...selectProps}
36
+ {...props}
37
+ className={cn(filterClasses({ type: "select" }), classNames?.field)}
38
+ classNames={{
39
+ trigger: "min-w-40",
40
+ label: "font-semibold",
41
+ ...selectProps?.classNames,
42
+ }}
43
+ >
44
+ {options.map((option) => (
45
+ <SelectItem key={option.key}>{option.label}</SelectItem>
46
+ ))}
47
+ </Select>
48
+ );
49
+ }
@@ -0,0 +1,33 @@
1
+ "use client";
2
+
3
+ import { FilterProps } from "@/types";
4
+ import { filterClasses } from "@/components";
5
+ import { Switch } from "@heroui/switch";
6
+ import { cn } from "@/utils";
7
+ import { useSearchParamsHandler } from "@/hooks";
8
+
9
+ export function SwitchFilter({
10
+ classNames,
11
+ switchProps,
12
+ paramKey,
13
+ ...props
14
+ }: Pick<FilterProps, "classNames" | "switchProps" | "paramKey">) {
15
+ const { getParam, setParams } = useSearchParamsHandler();
16
+
17
+ const value = getParam(paramKey);
18
+ const isSelected = value === "true" || value === "1" || value === "yes";
19
+
20
+ return (
21
+ <Switch
22
+ isSelected={isSelected}
23
+ onValueChange={(selected) =>
24
+ setParams({
25
+ params: { [paramKey]: selected ? "true" : undefined },
26
+ })
27
+ }
28
+ {...switchProps}
29
+ {...props}
30
+ className={cn(filterClasses({ type: "switch" }), classNames?.field)}
31
+ />
32
+ );
33
+ }
@@ -4,3 +4,8 @@ export * from "./container";
4
4
  export * from "./row";
5
5
  export * from "./portal";
6
6
  export * from "./uploader";
7
+ export * from "./filter";
8
+ export * from "./field";
9
+ export * from "./theme-switch";
10
+ export * from "./color-picker";
11
+ export * from "./copy-button";
@@ -0,0 +1 @@
1
+ export * from "./theme-switch";
@@ -0,0 +1,16 @@
1
+ import { cva } from "class-variance-authority";
2
+
3
+ export const themeSwitcherClasses = cva(
4
+ [
5
+ "flex items-center justify-center size-9 rounded-lg transition-all duration-300 text-foreground/80 cursor-pointer",
6
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 ",
7
+ "hover:bg-default-100 hover:text-foreground",
8
+ ],
9
+ {
10
+ variants: {
11
+ variant: {},
12
+ },
13
+ defaultVariants: {},
14
+ compoundVariants: [],
15
+ },
16
+ );
@@ -0,0 +1 @@
1
+ export * from "./classes";
@@ -0,0 +1,26 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { ThemeSwitch } from "@/components";
3
+ import { getStorybookDecorators } from "@/utils";
4
+
5
+ const meta: Meta<typeof ThemeSwitch> = {
6
+ title: "Atoms/ThemeSwitch",
7
+ component: ThemeSwitch,
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
+ render: () => (
22
+ <div className="flex justify-center items-center w-screen h-screen">
23
+ <ThemeSwitch className="m-auto" />
24
+ </div>
25
+ ),
26
+ };
@@ -0,0 +1,54 @@
1
+ import { cn } from "@/utils";
2
+ import { themeSwitcherClasses } from "./styles";
3
+ import { ThemeSwitchProps } from "@/types";
4
+ import { useTheme } from "next-themes";
5
+ import { MoonIcon, SunIcon } from "lucide-react";
6
+ import { motion, AnimatePresence } from "motion/react";
7
+
8
+ export function ThemeSwitch({
9
+ className,
10
+ classNames,
11
+ ...props
12
+ }: ThemeSwitchProps) {
13
+ const { setTheme, resolvedTheme } = useTheme();
14
+
15
+ const handleThemeToggle = () => {
16
+ setTheme(resolvedTheme === "dark" ? "light" : "dark");
17
+ };
18
+
19
+ return (
20
+ <motion.button
21
+ data-slot="emperor-ui-theme-knob"
22
+ className={cn(themeSwitcherClasses(), className, classNames?.base)}
23
+ onClick={handleThemeToggle}
24
+ initial={false}
25
+ whileHover={{ scale: 1.08 }}
26
+ whileTap={{ scale: 0.92 }}
27
+ transition={{
28
+ type: "spring" as const,
29
+ stiffness: 500,
30
+ damping: 28,
31
+ }}
32
+ {...props}
33
+ >
34
+ <span className="relative flex size-5 items-center justify-center">
35
+ <AnimatePresence mode="wait">
36
+ <motion.span
37
+ key={resolvedTheme ?? "loading"}
38
+ initial={{ opacity: 0 }}
39
+ animate={{ opacity: 1 }}
40
+ exit={{ opacity: 0 }}
41
+ transition={{ duration: 0.2 }}
42
+ className="absolute inset-0 flex items-center justify-center"
43
+ >
44
+ {resolvedTheme === "dark" ? (
45
+ <SunIcon className="size-5" />
46
+ ) : (
47
+ <MoonIcon className="size-5" />
48
+ )}
49
+ </motion.span>
50
+ </AnimatePresence>
51
+ </span>
52
+ </motion.button>
53
+ );
54
+ }
@@ -1,7 +1,9 @@
1
1
  "use client";
2
2
 
3
3
  import { Placeholders } from "@/enums";
4
- import { Avatar, Spinner, cn } from "@heroui/react";
4
+ import { Avatar } from "@heroui/avatar";
5
+ import { Spinner } from "@heroui/spinner";
6
+ import { cn } from "@/utils";
5
7
  import { useEmperorUI, useUploaderContext } from "@/hooks";
6
8
  import { useState } from "react";
7
9
 
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite";
2
2
  import { Uploader } from "@/components";
3
3
  import { getStorybookDecorators } from "@/utils";
4
4
  import { useUploader } from "@/hooks";
5
- import { useDisclosure } from "@heroui/react";
5
+ import { useDisclosure } from "@heroui/modal";
6
6
  import { LangKey } from "@/i18n";
7
7
 
8
8
  const meta: Meta<typeof Uploader> = {
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { cn } from "@heroui/react";
3
+ import { cn } from "@/utils";
4
4
  import { useEmperorUI, useUploaderContext } from "@/hooks";
5
5
  import { useMemo } from "react";
6
6
 
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { cn } from "@heroui/react";
3
+ import { cn } from "@/utils";
4
4
  import { useUploaderContext } from "@/hooks";
5
5
  import { fileTypesMapping } from "@/constants";
6
6
 
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
- import { Spinner, cn } from "@heroui/react";
3
+ import { Spinner } from "@heroui/spinner";
4
+ import { cn } from "@/utils";
4
5
  import { useEmperorUI, useUploaderContext } from "@/hooks";
5
6
  import { UploadCloud } from "lucide-react";
6
7
  import { useState } from "react";
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
- import { Button, cn } from "@heroui/react";
3
+ import { Button } from "@heroui/button";
4
+ import { cn } from "@/utils";
4
5
  import { useUploaderContext } from "@/hooks";
5
6
  import { Eye, Trash2 } from "lucide-react";
6
7
 
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
- import { Image, Modal, ModalBody, ModalContent } from "@heroui/react";
3
+ import { Image } from "@heroui/image";
4
+ import { Modal, ModalBody, ModalContent } from "@heroui/modal";
4
5
  import { useEmperorUI, useUploaderContext } from "@/hooks";
5
6
 
6
7
  export function ViewImageModal() {
@@ -1,4 +1,3 @@
1
- export * from "./filter";
2
1
  export * from "./item-card";
3
2
  export * from "./nav-bar";
4
3
  export * from "./side-bar";
@@ -1 +1,7 @@
1
1
  export * from "./item-card";
2
+ export * from "./loading-item";
3
+ export * from "./item-banner";
4
+ export * from "./item-actions-dropdown";
5
+ export * from "./item-card-header";
6
+ export * from "./item-card-body";
7
+ export * from "./item-card-footer";
@@ -0,0 +1,57 @@
1
+ "use client";
2
+
3
+ import { ItemCardProps } from "@/types";
4
+ import { cn } from "@/utils";
5
+ import { Button } from "@heroui/button";
6
+ import {
7
+ Dropdown,
8
+ DropdownTrigger,
9
+ DropdownMenu,
10
+ DropdownItem,
11
+ } from "@heroui/dropdown";
12
+ import { EllipsisVertical } from "lucide-react";
13
+
14
+ export function ItemActionsDropdown({
15
+ actions,
16
+ classNames,
17
+ onActionClick,
18
+ }: Pick<ItemCardProps, "actions" | "classNames" | "onActionClick">) {
19
+ if (!actions || actions?.length === 0) return null;
20
+
21
+ return (
22
+ <Dropdown placement="bottom-end">
23
+ <DropdownTrigger data-slot="emperor-ui-item-card-actions-dropdown-trigger">
24
+ <Button
25
+ isIconOnly
26
+ size="sm"
27
+ variant="flat"
28
+ radius="full"
29
+ className={cn(
30
+ "absolute right-2 top-2 z-10 size-7 min-w-7 backdrop-blur-lg",
31
+ "border border-default-300",
32
+ classNames?.dropdown,
33
+ )}
34
+ >
35
+ <EllipsisVertical className="size-4" />
36
+ </Button>
37
+ </DropdownTrigger>
38
+
39
+ <DropdownMenu
40
+ data-slot="emperor-ui-item-card-actions-dropdown-menu"
41
+ aria-label="Card actions"
42
+ onAction={(key) => onActionClick?.(String(key))}
43
+ items={actions}
44
+ >
45
+ {({ key, label, ...props }) => (
46
+ <DropdownItem
47
+ key={key}
48
+ data-slot="emperor-ui-item-card-actions-dropdown-item"
49
+ textValue={label}
50
+ title={label}
51
+ {...props}
52
+ />
53
+ )}
54
+ </DropdownMenu>
55
+ </Dropdown>
56
+ );
57
+ }
@@ -0,0 +1,22 @@
1
+ "use client";
2
+
3
+ import { ItemCardProps } from "@/types";
4
+ import { cn } from "@/utils";
5
+ import { itemBannerClasses } from "./styles";
6
+
7
+ export function ItemBanner({
8
+ item,
9
+ orientation,
10
+ classNames,
11
+ }: Pick<ItemCardProps, "item" | "orientation" | "classNames">) {
12
+ if (!item?.banner) return null;
13
+
14
+ return (
15
+ <div
16
+ data-slot="emperor-ui-item-card-banner"
17
+ className={cn(itemBannerClasses({ orientation }), classNames?.banner)}
18
+ >
19
+ {item?.banner}
20
+ </div>
21
+ );
22
+ }