@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,171 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { Listings } from "@/components";
3
+ import { getStorybookDecorators } from "@/utils";
4
+ import { getListings, MOCK_LISTINGS } from "@/mocks";
5
+ import type { MockItemType } from "@/mocks";
6
+ import { useMemo, useState } from "react";
7
+ import { useEffect } from "react";
8
+ import { ListingsProps } from "@/types";
9
+ import { usePagination } from "@heroui/pagination";
10
+
11
+ const meta: Meta<typeof Listings> = {
12
+ title: "Organisms/Listings/List",
13
+ component: Listings,
14
+ parameters: {
15
+ layout: "centered",
16
+ },
17
+ tags: ["autodocs"],
18
+ decorators: getStorybookDecorators({
19
+ config: {
20
+ layout: {
21
+ withScaffold: false,
22
+ },
23
+ },
24
+ }),
25
+ };
26
+
27
+ export default meta;
28
+
29
+ type Story = StoryObj<typeof meta>;
30
+
31
+ export const Default: Story = {
32
+ args: {
33
+ layout: "list",
34
+ },
35
+ render: (args: ListingsProps) => {
36
+ return (
37
+ <main className="w-screen container mx-auto p-5">
38
+ <Listings
39
+ {...args}
40
+ items={MOCK_LISTINGS.map((item) => ({
41
+ item: {
42
+ key: String(item.id),
43
+ title: item?.title,
44
+ description: item.description,
45
+ image: { src: item?.image, alt: item?.title },
46
+ },
47
+ }))}
48
+ />
49
+ </main>
50
+ );
51
+ },
52
+ };
53
+
54
+ export const WithLoading: Story = {
55
+ args: {
56
+ layout: "list",
57
+ },
58
+ render: (args: ListingsProps) => {
59
+ const [items, setItems] = useState<MockItemType[]>([]);
60
+ const [isLoading, setIsLoading] = useState(true);
61
+
62
+ useEffect(() => {
63
+ (async () => {
64
+ const { items } = await getListings({});
65
+
66
+ setItems(items);
67
+ setIsLoading(false);
68
+ })();
69
+ }, []);
70
+
71
+ return (
72
+ <Listings
73
+ {...args}
74
+ isLoading={isLoading}
75
+ items={items.map((item) => ({
76
+ item: {
77
+ key: String(item.id),
78
+ title: item?.title,
79
+ description: item.description,
80
+ image: { src: item?.image, alt: item?.title },
81
+ },
82
+ }))}
83
+ />
84
+ );
85
+ },
86
+ };
87
+
88
+ export const WithActions: Story = {
89
+ args: {
90
+ layout: "list",
91
+ items: MOCK_LISTINGS.map((item) => ({
92
+ item: {
93
+ key: String(item.id),
94
+ title: item?.title,
95
+ description: item.description,
96
+ image: { src: item?.image, alt: item?.title },
97
+ },
98
+ actions: [
99
+ { key: "view", label: "View details" },
100
+ { key: "edit", label: "Edit" },
101
+ { key: "delete", label: "Delete" },
102
+ ],
103
+ })),
104
+ },
105
+ };
106
+
107
+ export const WithPagination: Story = {
108
+ args: {
109
+ layout: "list",
110
+ },
111
+ render: (args: ListingsProps) => {
112
+ const [items, setItems] = useState<MockItemType[]>([]);
113
+ const [isLoading, setIsLoading] = useState(true);
114
+
115
+ const pageSize = 6;
116
+ const totalItemsCount = MOCK_LISTINGS.length;
117
+ const pagesCount = useMemo(
118
+ () => Math.ceil(totalItemsCount / pageSize),
119
+ [totalItemsCount, pageSize],
120
+ );
121
+
122
+ const { activePage: page, setPage } = usePagination({
123
+ total: pagesCount,
124
+ initialPage: 1,
125
+ });
126
+
127
+ useEffect(() => {
128
+ (async () => {
129
+ setIsLoading(true);
130
+
131
+ const { items } = await getListings({
132
+ page,
133
+ pageSize,
134
+ });
135
+
136
+ setItems(items);
137
+ setIsLoading(false);
138
+ })();
139
+ }, [page]);
140
+
141
+ return (
142
+ <Listings
143
+ {...args}
144
+ isLoading={isLoading}
145
+ items={items.map((item) => ({
146
+ item: {
147
+ key: String(item.id),
148
+ title: item?.title,
149
+ description: item.description,
150
+ image: { src: item?.image, alt: item?.title },
151
+ banner: item?.isBestSeller ? "Best Seller" : undefined,
152
+ chips: item?.categories || [],
153
+ },
154
+ }))}
155
+ pagination={{
156
+ page,
157
+ setPage,
158
+ pageSize,
159
+ totalItemsCount,
160
+ pagesCount,
161
+ }}
162
+ />
163
+ );
164
+ },
165
+ };
166
+
167
+ export const EmptyListings: Story = {
168
+ args: {
169
+ items: [],
170
+ },
171
+ };
@@ -1,11 +1,49 @@
1
1
  import { cva } from "class-variance-authority";
2
2
 
3
- export const listingsClasses = cva([], {
3
+ export const listingsClasses = cva(["w-full p-4 gap-4 relative"], {
4
4
  variants: {
5
- variant: {
6
- default: [],
5
+ layout: {
6
+ grid: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
7
+ list: "flex flex-col gap-4",
8
+ carousel: "flex flex-col gap-4",
7
9
  },
8
10
  },
9
11
  defaultVariants: {},
10
12
  compoundVariants: [],
11
13
  });
14
+
15
+ export const listingsItemClasses = cva([""], {
16
+ variants: {
17
+ layout: {
18
+ grid: "size-full",
19
+ list: "w-full h-fit",
20
+ carousel: "",
21
+ },
22
+ },
23
+ defaultVariants: {},
24
+ compoundVariants: [],
25
+ });
26
+
27
+ export const listingsPaginationClasses = cva(["mx-auto my-5 col-span-full"], {
28
+ variants: {
29
+ layout: {
30
+ grid: "",
31
+ list: "",
32
+ carousel: "",
33
+ },
34
+ },
35
+ defaultVariants: {},
36
+ compoundVariants: [],
37
+ });
38
+
39
+ export const emptyListingsClasses = cva(
40
+ [
41
+ "flex flex-col items-center justify-center min-h-[280px] w-full py-12 px-4",
42
+ "text-center",
43
+ ],
44
+ {
45
+ variants: {},
46
+ defaultVariants: {},
47
+ compoundVariants: [],
48
+ },
49
+ );
@@ -0,0 +1,14 @@
1
+ /* eslint-disable no-empty-pattern */
2
+ import { ItemCardHoverEffect } from "@/types";
3
+ import { MotionProps } from "framer-motion";
4
+
5
+ export const getCardMotion = ({}: {
6
+ hoverEffect: ItemCardHoverEffect;
7
+ }): MotionProps => ({
8
+ initial: { opacity: 0, y: 12 },
9
+ animate: { opacity: 1, y: 0 },
10
+ transition: { duration: 0.3, ease: "easeOut" as const },
11
+ whileHover: {
12
+ y: -4,
13
+ },
14
+ });
@@ -0,0 +1,26 @@
1
+ import { Eye, Pencil, Trash2 } from "lucide-react";
2
+ import { ItemCardAction } from "@/types";
3
+
4
+ export const ITEM_CARD_ACTIONS: ItemCardAction[] = [
5
+ {
6
+ key: "view",
7
+ label: "View details",
8
+ color: "primary",
9
+ variant: "flat",
10
+ startContent: <Eye className="size-4" />,
11
+ },
12
+ {
13
+ key: "edit",
14
+ label: "Edit",
15
+ color: "secondary",
16
+ variant: "flat",
17
+ startContent: <Pencil className="size-4" />,
18
+ },
19
+ {
20
+ key: "delete",
21
+ label: "Delete",
22
+ color: "danger",
23
+ variant: "flat",
24
+ startContent: <Trash2 className="size-4" />,
25
+ },
26
+ ];
@@ -1,22 +1,7 @@
1
1
  import { en, ar } from "@/i18n";
2
- import type { ColorsPalette, EmperorUIConfig } from "@/types";
3
-
4
- export const defaultColorsPalette: ColorsPalette = {
5
- primary: "#006FEE",
6
- secondary: "#9353d3",
7
- background: "#3f3f46",
8
- foreground: "#ECEDEE",
9
- success: "#17c964",
10
- warning: "#f5a524",
11
- danger: "#f31260",
12
- info: "#3B82F6",
13
- };
2
+ import type { EmperorUIConfig } from "@/types";
14
3
 
15
4
  export const defaultEmperorUIConfig: EmperorUIConfig = {
16
- theme: {
17
- mode: "dark",
18
- colors: defaultColorsPalette,
19
- },
20
5
  layout: {
21
6
  withScaffold: true,
22
7
  },
@@ -2,3 +2,5 @@ export * from "./defaults";
2
2
  export * from "./fake";
3
3
  export * from "./uploader";
4
4
  export * from "./footer";
5
+ export * from "./animations";
6
+ export * from "./card";
@@ -1,4 +1,7 @@
1
1
  export * from "./use-emperor-ui";
2
2
  export * from "./use-navigation";
3
+ export * from "./use-search-params-handler";
3
4
  export * from "./use-uploader-context";
4
5
  export * from "./use-uploader";
6
+ export * from "./use-window-size";
7
+ export * from "./use-filters";
@@ -0,0 +1,20 @@
1
+ "use client";
2
+
3
+ import { useMemo } from "react";
4
+ import { useSearchParamsHandler } from "@/hooks";
5
+
6
+ export function useFilters<
7
+ FiltersType extends Record<string, string | number | boolean>,
8
+ >() {
9
+ const { allParams } = useSearchParamsHandler();
10
+
11
+ const filters = useMemo(() => {
12
+ if (!Object.keys(allParams).length) {
13
+ return null;
14
+ }
15
+
16
+ return allParams as unknown as FiltersType;
17
+ }, [allParams]);
18
+
19
+ return { filters };
20
+ }
@@ -0,0 +1,186 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useState } from "react";
4
+
5
+ const URL_SEARCH_PARAMS_CHANGE_EVENT = "URLSearchParamsChange";
6
+
7
+ export function useSearchParamsHandler() {
8
+ const getAllParams = useCallback((): Record<string, string> => {
9
+ if (typeof window === "undefined") {
10
+ return {};
11
+ }
12
+
13
+ const currentSearchParams = new URLSearchParams(window.location.search);
14
+
15
+ return Array.from(currentSearchParams.entries()).reduce<
16
+ Record<string, string>
17
+ >((paramsRecord, [key, value]) => {
18
+ paramsRecord[key] = value;
19
+ return paramsRecord;
20
+ }, {});
21
+ }, []);
22
+
23
+ const [allParams, setAllParams] = useState<Record<string, string>>(() =>
24
+ getAllParams(),
25
+ );
26
+
27
+ const updateUrlSearchParams = useCallback(
28
+ ({ searchParams }: { searchParams: URLSearchParams }) => {
29
+ if (typeof window === "undefined") return;
30
+
31
+ const searchParamsString = searchParams.toString();
32
+ const searchPath = searchParamsString ? `?${searchParamsString}` : "";
33
+ const newPath = `${window.location.pathname}${searchPath}${window.location.hash}`;
34
+ const currentPath = `${window.location.pathname}${window.location.search}${window.location.hash}`;
35
+
36
+ if (newPath === currentPath) return;
37
+
38
+ window.history.replaceState(window.history.state, "", newPath);
39
+ window.dispatchEvent(new Event(URL_SEARCH_PARAMS_CHANGE_EVENT));
40
+ },
41
+ [],
42
+ );
43
+
44
+ const setParams = useCallback(
45
+ ({
46
+ params,
47
+ options = { replace: true },
48
+ }: {
49
+ params: Record<string, string | number | boolean | undefined | null>;
50
+ options?: { replace?: boolean };
51
+ }) => {
52
+ if (typeof window === "undefined") return;
53
+
54
+ const nextSearchParams = new URLSearchParams(window.location.search);
55
+ let hasChanges = false;
56
+
57
+ Object.entries(params).forEach(([key, value]) => {
58
+ if (value === undefined || value === null || value === "") {
59
+ const hasKey = nextSearchParams.has(key);
60
+ nextSearchParams.delete(key);
61
+ if (hasKey) {
62
+ hasChanges = true;
63
+ }
64
+ return;
65
+ }
66
+
67
+ const serializedValue = String(value);
68
+
69
+ if (options.replace) {
70
+ const existingValues = nextSearchParams.getAll(key);
71
+ const isAlreadyReplaced =
72
+ existingValues.length === 1 &&
73
+ existingValues[0] === serializedValue;
74
+
75
+ if (isAlreadyReplaced) {
76
+ return;
77
+ }
78
+
79
+ nextSearchParams.delete(key);
80
+ nextSearchParams.append(key, serializedValue);
81
+ hasChanges = true;
82
+ return;
83
+ }
84
+
85
+ const existingValues = nextSearchParams.getAll(key);
86
+ const hasMatchingValue = existingValues.some(
87
+ (existingValue) => existingValue === serializedValue,
88
+ );
89
+
90
+ if (!hasMatchingValue) {
91
+ nextSearchParams.append(key, serializedValue);
92
+ hasChanges = true;
93
+ }
94
+ });
95
+
96
+ if (!hasChanges) return;
97
+
98
+ updateUrlSearchParams({ searchParams: nextSearchParams });
99
+ },
100
+ // eslint-disable-next-line react-hooks/exhaustive-deps
101
+ [],
102
+ );
103
+
104
+ const deleteParam = useCallback(
105
+ ({ key }: { key: string }) => {
106
+ if (typeof window === "undefined") {
107
+ return;
108
+ }
109
+
110
+ const nextSearchParams = new URLSearchParams(window.location.search);
111
+ nextSearchParams.delete(key);
112
+ updateUrlSearchParams({ searchParams: nextSearchParams });
113
+ },
114
+ // eslint-disable-next-line react-hooks/exhaustive-deps
115
+ [],
116
+ );
117
+
118
+ const deleteParams = useCallback(
119
+ ({ keys }: { keys: string[] }) => {
120
+ if (typeof window === "undefined") return;
121
+
122
+ const nextSearchParams = new URLSearchParams(window.location.search);
123
+ let hasChanges = false;
124
+ keys.forEach((key) => {
125
+ const hasKey = nextSearchParams.has(key);
126
+ nextSearchParams.delete(key);
127
+ if (hasKey) {
128
+ hasChanges = true;
129
+ }
130
+ });
131
+
132
+ if (!hasChanges) {
133
+ return;
134
+ }
135
+
136
+ updateUrlSearchParams({ searchParams: nextSearchParams });
137
+ },
138
+ // eslint-disable-next-line react-hooks/exhaustive-deps
139
+ [],
140
+ );
141
+
142
+ const clearParams = useCallback(() => {
143
+ if (typeof window === "undefined") return;
144
+
145
+ if (!window.location.search) {
146
+ return;
147
+ }
148
+
149
+ updateUrlSearchParams({ searchParams: new URLSearchParams() });
150
+ }, [updateUrlSearchParams]);
151
+
152
+ const getParam = useCallback((name: string) => {
153
+ if (typeof window === "undefined") return null;
154
+
155
+ const currentSearchParams = new URLSearchParams(window.location.search);
156
+ return currentSearchParams.get(name);
157
+ }, []);
158
+
159
+ useEffect(() => {
160
+ if (typeof window === "undefined") {
161
+ return;
162
+ }
163
+
164
+ const syncAllParams = () => {
165
+ setAllParams(getAllParams());
166
+ };
167
+
168
+ syncAllParams();
169
+ window.addEventListener("popstate", syncAllParams);
170
+ window.addEventListener(URL_SEARCH_PARAMS_CHANGE_EVENT, syncAllParams);
171
+
172
+ return () => {
173
+ window.removeEventListener("popstate", syncAllParams);
174
+ window.removeEventListener(URL_SEARCH_PARAMS_CHANGE_EVENT, syncAllParams);
175
+ };
176
+ }, [getAllParams]);
177
+
178
+ return {
179
+ getParam,
180
+ allParams,
181
+ setParams,
182
+ deleteParam,
183
+ deleteParams,
184
+ clearParams,
185
+ };
186
+ }
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { addToast } from "@heroui/react";
3
+ import { addToast } from "@heroui/toast";
4
4
  import { FileObject, UseUploadFileProps, UseUploadFileReturn } from "@/types";
5
5
  import { useState } from "react";
6
6
  import { useEmperorUI } from "@/hooks";
@@ -0,0 +1,53 @@
1
+ "use client";
2
+
3
+ import { useEffect, useMemo, useState } from "react";
4
+
5
+ const SCREENS = {
6
+ sm: 640,
7
+ md: 768,
8
+ lg: 1024,
9
+ xl: 1280,
10
+ "2xl": 1536,
11
+ };
12
+
13
+ export const useWindowSize = () => {
14
+ const [size, setSize] = useState({ width: 0, height: 0 });
15
+
16
+ useEffect(() => {
17
+ function updateSize() {
18
+ setSize({ width: window.innerWidth, height: window.innerHeight });
19
+ }
20
+ window.addEventListener("resize", updateSize);
21
+ updateSize();
22
+
23
+ return () => window.removeEventListener("resize", updateSize);
24
+ }, []);
25
+
26
+ const currentScreen: "base" | "sm" | "md" | "lg" | "xl" | "2xl" =
27
+ useMemo(() => {
28
+ if (size?.width > SCREENS?.["2xl"]) return "2xl";
29
+ else if (size?.width > SCREENS?.xl) return "xl";
30
+ else if (size?.width > SCREENS?.lg) return "lg";
31
+ else if (size?.width > SCREENS?.md) return "md";
32
+ else if (size?.width > SCREENS?.sm) return "sm";
33
+ else return "base";
34
+ }, [size]);
35
+
36
+ const isSmallDevice = useMemo(
37
+ () => ["base", "sm", "md"].includes(currentScreen),
38
+ [currentScreen],
39
+ );
40
+
41
+ const isExtraSmallDevice = useMemo(
42
+ () => ["base"].includes(currentScreen),
43
+ [currentScreen],
44
+ );
45
+
46
+ return {
47
+ viewportWidth: size.width,
48
+ viewportHeight: size.height,
49
+ currentScreen,
50
+ isSmallDevice,
51
+ isExtraSmallDevice,
52
+ };
53
+ };
@@ -1,4 +1,7 @@
1
1
  export const atomsAr = {
2
+ colorPicker: {
3
+ invalidColorFormat: "يرجى إدخال لون صالح (مثال: #000000 أو #fff)",
4
+ },
2
5
  uploader: {
3
6
  dropHere: "أسقط الملف هنا",
4
7
  selectFile: "حدد ملفًا أو اسحبه وأفلته هنا",
@@ -1,4 +1,7 @@
1
1
  export const atomsEn = {
2
+ colorPicker: {
3
+ invalidColorFormat: "Please enter a valid color (e.g., #000000 or #fff)",
4
+ },
2
5
  uploader: {
3
6
  dropHere: "Drop file here",
4
7
  selectFile: "Select a file or drag and drop here",
@@ -1 +1,7 @@
1
- export const organismsAr = {};
1
+ export const organismsAr = {
2
+ listings: {
3
+ emptyTitle: "لا توجد عناصر",
4
+ emptyDescription:
5
+ "لم نجد أي عناصر تطابق بحثك. جرّب تعديل الفلاتر أو البحث لاحقاً.",
6
+ },
7
+ };
@@ -1 +1,7 @@
1
- export const organismsEn = {};
1
+ export const organismsEn = {
2
+ listings: {
3
+ emptyTitle: "No listings found",
4
+ emptyDescription:
5
+ "We couldn't find any listings matching your search. Try adjusting your filters or check back later.",
6
+ },
7
+ };