@js-empire/emperor-ui 1.2.2 → 1.2.4

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 +0 -15
  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 +338 -36
  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 +8 -1
  52. package/src/components/atoms/uploader/stories/uploader.stories.tsx +3 -3
  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 +8 -1
  56. package/src/components/atoms/uploader/upload-file-listing.tsx +46 -37
  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 +2 -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-uyo1KMg-.js +0 -1938
  146. package/dist/index-B3d8-vnJ.js +0 -1630
  147. package/dist/index-DOwkJus4.js +0 -26528
  148. package/dist/index-DrkA25TM.js +0 -5
  149. package/dist/src-UW24ZMRV-D6kiVea5.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,80 @@
1
+ "use client";
2
+
3
+ import { Locale } from "@/i18n";
4
+ import { Inbox } from "lucide-react";
5
+ import { motion } from "framer-motion";
6
+ import { cn } from "@/utils";
7
+ import { emptyListingsClasses } from "@/components";
8
+ import { useEmperorUI } from "@/hooks";
9
+ import { blinkContainer, blinkItem, floating } from "@/animations";
10
+
11
+ export function EmptyListings({
12
+ className,
13
+ classNames,
14
+ }: {
15
+ className?: string;
16
+ classNames?: {
17
+ wrapper?: string;
18
+ iconWrapper?: string;
19
+ title?: string;
20
+ description?: string;
21
+ };
22
+ }) {
23
+ const { config } = useEmperorUI();
24
+
25
+ const locales = config?.interLocalization?.locales;
26
+ const lang = config?.interLocalization?.lang;
27
+
28
+ const locale = locales?.[lang || "en"] as Locale;
29
+ const listingsLocale = locale?.organisms?.listings;
30
+
31
+ return (
32
+ <motion.div
33
+ data-slot="emperor-ui-empty-listings"
34
+ className={cn(emptyListingsClasses(), className)}
35
+ variants={blinkContainer}
36
+ initial="hidden"
37
+ animate="visible"
38
+ >
39
+ <motion.div
40
+ className={cn(
41
+ "flex items-center justify-center rounded-2xl bg-default-100/80 p-6 mb-5",
42
+ classNames?.iconWrapper,
43
+ )}
44
+ variants={blinkItem}
45
+ >
46
+ <motion.div
47
+ variants={floating}
48
+ animate="animate"
49
+ className="flex items-center justify-center"
50
+ >
51
+ <Inbox
52
+ className="size-14 text-default-400"
53
+ strokeWidth={1.25}
54
+ aria-hidden
55
+ />
56
+ </motion.div>
57
+ </motion.div>
58
+
59
+ <motion.h2
60
+ className={cn(
61
+ "text-xl font-semibold text-foreground mb-2",
62
+ classNames?.title,
63
+ )}
64
+ variants={blinkItem}
65
+ >
66
+ {listingsLocale?.emptyTitle}
67
+ </motion.h2>
68
+
69
+ <motion.p
70
+ className={cn(
71
+ "text-sm text-default-500 max-w-sm",
72
+ classNames?.description,
73
+ )}
74
+ variants={blinkItem}
75
+ >
76
+ {listingsLocale?.emptyDescription}
77
+ </motion.p>
78
+ </motion.div>
79
+ );
80
+ }
@@ -1 +1,3 @@
1
1
  export * from "./listings";
2
+ export * from "./empty-listings";
3
+ export * from "./styles";
@@ -1,15 +1,96 @@
1
- import type { ListingsProps } from "@/types";
2
- import { listingsClasses, listingsStyles } from "./styles";
1
+ import { EmptyListings, ItemCard, LoadingItem } from "@/components";
2
+ import type { ItemCardOrientation, ListingsProps } from "@/types";
3
+ import { cn } from "@/utils";
4
+ import {
5
+ listingsClasses,
6
+ listingsItemClasses,
7
+ listingsPaginationClasses,
8
+ listingsStyles,
9
+ } from "./styles";
10
+ import { Pagination } from "@heroui/pagination";
11
+ import { motion } from "framer-motion";
3
12
 
4
- export function Listings<ListingType>({
13
+ export function Listings({
14
+ items = [],
15
+ isLoading = false,
5
16
  className,
6
- variant = "default",
7
- }: ListingsProps<ListingType>) {
17
+ classNames,
18
+ layout = "grid",
19
+ actions = [],
20
+ onActionClick,
21
+ pagination,
22
+ }: ListingsProps) {
23
+ const orientation: ItemCardOrientation = {
24
+ grid: "vertical",
25
+ list: "horizontal",
26
+ carousel: "vertical",
27
+ }[layout] as ItemCardOrientation;
28
+
29
+ const isEmpty = items?.length === 0;
30
+
8
31
  return (
9
- <section
32
+ <motion.section
10
33
  data-slot="emperor-ui-listings"
11
- className={listingsClasses({ className, variant })}
12
- style={listingsStyles({ variant })}
13
- ></section>
34
+ className={cn(listingsClasses({ className, layout }))}
35
+ style={listingsStyles({ layout })}
36
+ initial="hidden"
37
+ animate="visible"
38
+ >
39
+ {isLoading &&
40
+ Array.from({ length: pagination?.pageSize || 12 }).map((_, index) => (
41
+ <LoadingItem
42
+ key={index}
43
+ className={className}
44
+ classNames={classNames}
45
+ orientation={orientation}
46
+ />
47
+ ))}
48
+
49
+ {!isLoading && isEmpty && <EmptyListings className="col-span-full" />}
50
+
51
+ {!isLoading &&
52
+ !isEmpty &&
53
+ items?.map(
54
+ ({
55
+ item,
56
+ actions: itemActions,
57
+ onActionClick: onItemActionClick,
58
+ }) => (
59
+ <ItemCard
60
+ key={item?.key}
61
+ className={cn(listingsItemClasses({ layout }), classNames?.item)}
62
+ item={item}
63
+ isLoading={isLoading}
64
+ actions={itemActions || actions || []}
65
+ onActionClick={onItemActionClick || onActionClick}
66
+ orientation={orientation}
67
+ />
68
+ ),
69
+ )}
70
+
71
+ {!isLoading && !isEmpty && pagination && (
72
+ <Pagination
73
+ className={cn(
74
+ listingsPaginationClasses({ layout }),
75
+ classNames?.pagination,
76
+ )}
77
+ classNames={{
78
+ next: "rtl:rotate-180 cursor-pointer",
79
+ prev: "rtl:rotate-180 cursor-pointer",
80
+ item: "cursor-pointer",
81
+ chevronNext: "rtl:rotate-180 cursor-pointer",
82
+ forwardIcon: "rtl:rotate-180 cursor-pointer",
83
+ }}
84
+ total={pagination?.pagesCount}
85
+ page={pagination?.page}
86
+ onChange={pagination?.setPage}
87
+ variant="faded"
88
+ showControls
89
+ color="primary"
90
+ initialPage={1}
91
+ showShadow
92
+ />
93
+ )}
94
+ </motion.section>
14
95
  );
15
96
  }
@@ -0,0 +1,153 @@
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 { usePagination } from "@heroui/pagination";
9
+
10
+ const meta: Meta<typeof Listings> = {
11
+ title: "Organisms/Listings/Grid",
12
+ component: Listings,
13
+ parameters: {
14
+ layout: "centered",
15
+ },
16
+ tags: ["autodocs"],
17
+ decorators: getStorybookDecorators({
18
+ config: {
19
+ layout: {
20
+ withScaffold: false,
21
+ },
22
+ },
23
+ }),
24
+ };
25
+
26
+ export default meta;
27
+
28
+ type Story = StoryObj<typeof meta>;
29
+
30
+ export const Default: Story = {
31
+ args: {
32
+ items: MOCK_LISTINGS.map((item) => ({
33
+ item: {
34
+ key: String(item.id),
35
+ title: item?.title,
36
+ description: item.description,
37
+ image: { src: item?.image, alt: item?.title },
38
+ },
39
+ })),
40
+ },
41
+ };
42
+
43
+ export const WithLoading: Story = {
44
+ args: {},
45
+ render: () => {
46
+ const [items, setItems] = useState<MockItemType[]>([]);
47
+ const [isLoading, setIsLoading] = useState(true);
48
+
49
+ useEffect(() => {
50
+ (async () => {
51
+ const { items } = await getListings({});
52
+
53
+ setItems(items);
54
+ setIsLoading(false);
55
+ })();
56
+ }, []);
57
+
58
+ return (
59
+ <Listings
60
+ isLoading={isLoading}
61
+ items={items.map((item) => ({
62
+ item: {
63
+ key: String(item.id),
64
+ title: item?.title,
65
+ description: item.description,
66
+ image: { src: item?.image, alt: item?.title },
67
+ },
68
+ }))}
69
+ />
70
+ );
71
+ },
72
+ };
73
+
74
+ export const WithActions: Story = {
75
+ args: {
76
+ items: MOCK_LISTINGS.map((item) => ({
77
+ item: {
78
+ key: String(item.id),
79
+ title: item?.title,
80
+ description: item.description,
81
+ image: { src: item?.image, alt: item?.title },
82
+ },
83
+ actions: [
84
+ { key: "view", label: "View details" },
85
+ { key: "edit", label: "Edit" },
86
+ { key: "delete", label: "Delete" },
87
+ ],
88
+ })),
89
+ },
90
+ };
91
+
92
+ export const WithPagination: Story = {
93
+ args: {},
94
+ render: () => {
95
+ const [items, setItems] = useState<MockItemType[]>([]);
96
+ const [isLoading, setIsLoading] = useState(true);
97
+
98
+ const pageSize = 6;
99
+ const totalItemsCount = MOCK_LISTINGS.length;
100
+ const pagesCount = useMemo(
101
+ () => Math.ceil(totalItemsCount / pageSize),
102
+ [totalItemsCount, pageSize],
103
+ );
104
+
105
+ const { activePage: page, setPage } = usePagination({
106
+ total: pagesCount,
107
+ initialPage: 1,
108
+ });
109
+
110
+ useEffect(() => {
111
+ (async () => {
112
+ setIsLoading(true);
113
+
114
+ const { items } = await getListings({
115
+ page,
116
+ pageSize,
117
+ });
118
+
119
+ setItems(items);
120
+ setIsLoading(false);
121
+ })();
122
+ }, [page]);
123
+
124
+ return (
125
+ <Listings
126
+ isLoading={isLoading}
127
+ items={items.map((item) => ({
128
+ item: {
129
+ key: String(item.id),
130
+ title: item?.title,
131
+ description: item.description,
132
+ image: { src: item?.image, alt: item?.title },
133
+ banner: item?.isBestSeller ? "Best Seller" : undefined,
134
+ chips: item?.categories || [],
135
+ },
136
+ }))}
137
+ pagination={{
138
+ page,
139
+ setPage,
140
+ pageSize,
141
+ totalItemsCount,
142
+ pagesCount,
143
+ }}
144
+ />
145
+ );
146
+ },
147
+ };
148
+
149
+ export const EmptyListings: Story = {
150
+ args: {
151
+ items: [],
152
+ },
153
+ };
@@ -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
+ }