diff --git a/__tests__/components/component-categories.test.tsx b/__tests__/components/component-categories.test.tsx
index ef0b20d6..57d29a32 100644
--- a/__tests__/components/component-categories.test.tsx
+++ b/__tests__/components/component-categories.test.tsx
@@ -21,6 +21,10 @@ describe("SectionsList", () => {
it("category should have componentList === null if comingSoonCategory === true", () => {
for (const section of SectionsList) {
+ if (section.type !== "multiple-component") {
+ continue;
+ }
+ //TODO: Handle the single-component test
for (const category of section.categoriesList) {
if (category.comingSoonCategory) {
expect(category.componentList).toBeNull();
@@ -51,6 +55,9 @@ describe("SectionsList", () => {
};
for (const section of SectionsList) {
+ if (section.type !== "multiple-component") {
+ continue;
+ }
for (const category of section.categoriesList) {
if (category.componentList) {
for (const component of category.componentList) {
diff --git a/biome.json b/biome.json
index e72046c7..79c2428d 100644
--- a/biome.json
+++ b/biome.json
@@ -44,7 +44,7 @@
"all": true,
"noDefaultExport": "off",
"noNamespaceImport": "info",
- "useNamingConvention": "info"
+ "useNamingConvention": "off"
},
"suspicious": {
"all": true,
@@ -56,4 +56,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index e34fe1db..dec15afc 100644
--- a/package.json
+++ b/package.json
@@ -17,12 +17,6 @@
"test": "vitest",
"generate-package-list-check": "node scripts/generate-package-list-check.js"
},
- "git": {
- "pre-commit": "lint-staged"
- },
- "lint-staged": {
- "*": "biome format --write ./src"
- },
"dependencies": {
"@ctrl/tinycolor": "4.1.0",
"@radix-ui/react-dialog": "1.1.1",
@@ -81,4 +75,4 @@
"typescript": "5.4.5",
"vitest": "2.0.5"
}
-}
+}
\ No newline at end of file
diff --git a/readme.md b/readme.md
index f3d97eee..b942af27 100644
--- a/readme.md
+++ b/readme.md
@@ -52,6 +52,16 @@ The component roadmap can be find on the [featurebase page](https://cuicui.featu
- [ ] Create the pages for the category sections (i.e https://cuicui.day/application-ui) -> Display the grid of the components as the home page but for each section
- [ ] Improve SEO with better title and meta tags on components & site pages
- [ ] Improve homepage performance by replacing component integrations by images
+- [ ] Re-add precommit (fix importer order & console rules)
+
+```json
+"git": {
+ "pre-commit": "lint-staged"
+},
+"lint-staged": {
+ "*": "biome format --write ./src"
+},
+```
## License
diff --git a/src/app/(components)/[section]/[category]/multiple-component-section.tsx b/src/app/(components)/[section]/[category]/multiple-component-section.tsx
new file mode 100644
index 00000000..c3002dad
--- /dev/null
+++ b/src/app/(components)/[section]/[category]/multiple-component-section.tsx
@@ -0,0 +1,57 @@
+import { fetchMultipleComponentData } from "#/src/app/(components)/[section]/[category]/process-variant-data";
+import ComingSoonCard from "#/src/components/coming-soon";
+import HeaderComponent from "#/src/components/component-wrapper/header-component";
+import InspirationComponentFooter from "#/src/components/component-wrapper/inspiration-component-footer";
+import VariantTabs from "#/src/components/component-wrapper/variant-tabs";
+import type { CategoryType } from "#/src/lib/types/component";
+import { notFound } from "next/navigation";
+import { Fragment } from "react";
+
+export default async function MultipleComponentCategory({
+ category,
+}: Readonly<{ category: CategoryType }>) {
+ if (category.comingSoonCategory) {
+ return ;
+ }
+
+ if (!category.componentList) {
+ return notFound();
+ }
+
+ // If the componentList is not an array, we convert it to an array
+ const componentArray = Array.isArray(category.componentList)
+ ? category.componentList
+ : [category.componentList];
+
+ const componentList = await fetchMultipleComponentData({
+ categorySlug: category.slug,
+ componentList: componentArray,
+ });
+
+ return (
+
+ {componentList.map((component) => (
+
+
+
+
+
+ ))}
+
+ );
+}
diff --git a/src/app/(components)/[section]/[category]/page.tsx b/src/app/(components)/[section]/[category]/page.tsx
index e812be43..6db04b37 100644
--- a/src/app/(components)/[section]/[category]/page.tsx
+++ b/src/app/(components)/[section]/[category]/page.tsx
@@ -1,20 +1,33 @@
import { notFound } from "next/navigation";
-import { Fragment } from "react";
-import { fetchComponentData } from "#/src/app/(components)/[section]/[category]/process-variant-data";
-import ComingSoonCard from "#/src/components/coming-soon";
-import HeaderComponent from "#/src/components/component-wrapper/header-component";
-import InspirationComponentFooter from "#/src/components/component-wrapper/inspiration-component-footer";
-import VariantTabs from "#/src/components/component-wrapper/variant-tabs";
import { SectionsList } from "#/src/lib/cuicui-components/sections-list";
+import SingleComponentCategory from "#/src/app/(components)/[section]/[category]/single-component-section";
+import MultipleComponentCategory from "#/src/app/(components)/[section]/[category]/multiple-component-section";
+import type {
+ CategoryType,
+ MultiComponentSectionType,
+ PageSectionType,
+ SectionType,
+ SingleComponentCategoryType,
+ SingleComponentSectionType,
+} from "#/src/lib/types/component";
export async function generateStaticParams() {
// Iterate over the SectionsList to create all possible combinations of section and category
- const params = SectionsList.flatMap((section) =>
- section.categoriesList.map((category) => ({
- section: section.slug,
- category: category.slug,
- })),
- );
+
+ const params = SectionsList.flatMap((section) => {
+ if (
+ section.type === "multiple-component" ||
+ section.type === "single-component"
+ ) {
+ return section.categoriesList.map((category) => ({
+ section: section.slug,
+ category: category.slug,
+ }));
+ }
+ if (section.type === "page") {
+ //TODO : Handle pages
+ }
+ });
// Return the generated params array
return params;
@@ -31,51 +44,57 @@ export default async function Page({
const section = SectionsList.find(
(section) => section.slug === params.section,
);
-
- const category = section?.categoriesList.find(
- (category) => category.slug === params.category,
- );
-
- if (!category) {
+ if (!section) {
return notFound();
}
+ if (section?.type === "page") {
+ return;
+ }
- if (category.comingSoonCategory) {
- return ;
+ if (section?.type === "single-component") {
+ const category = findCategoryBySlug(section, params.category);
+ if (!category) {
+ return notFound();
+ }
+ return ;
}
- if (!category.componentList) {
- return notFound();
+
+ if (section?.type === "multiple-component") {
+ const category = findCategoryBySlug(section, params.category);
+ if (!category) {
+ return notFound();
+ }
+ return ;
}
+}
- const componentList = await fetchComponentData({
- categorySlug: category.slug,
- componentList: category.componentList,
- });
+// Function Overloads
+function findCategoryBySlug(
+ section: SingleComponentSectionType,
+ categoryParamSlug: string,
+): SingleComponentCategoryType | null;
+function findCategoryBySlug(
+ section: MultiComponentSectionType,
+ categoryParamSlug: string,
+): CategoryType | null;
+
+function findCategoryBySlug(
+ section: PageSectionType,
+ categoryParamSlug: string,
+): null;
+
+// General Implementation
+function findCategoryBySlug(
+ section: SectionType,
+ categoryParamSlug: string,
+): SingleComponentCategoryType | CategoryType | null {
+ if (section.type === "page") {
+ return null;
+ }
return (
-
- {componentList.map((component) => (
-
-
-
-
-
- ))}
-
+ section?.categoriesList.find(
+ (category) => category.slug === categoryParamSlug,
+ ) ?? null
);
}
diff --git a/src/app/(components)/[section]/[category]/process-variant-data.ts b/src/app/(components)/[section]/[category]/process-variant-data.ts
index d719b256..55627892 100644
--- a/src/app/(components)/[section]/[category]/process-variant-data.ts
+++ b/src/app/(components)/[section]/[category]/process-variant-data.ts
@@ -1,10 +1,11 @@
import type {
ComponentType,
- ComponentVariantType,
+ SingleComponentType,
+ VariantType,
} from "#/src/lib/types/component";
import { getFileContentAsString } from "#/src/utils/get-file-content-as-string";
-interface ProcessedVariant extends ComponentVariantType {
+interface ProcessedVariant extends VariantType {
previewCode: string;
componentCode?: string;
}
@@ -13,27 +14,46 @@ interface ProcessedComponent extends ComponentType {
componentList: ProcessedVariant[];
}
-export async function fetchVariantData(
- categorySlug: string,
- componentSlug: string,
- variant: ComponentVariantType,
-) {
+export async function fetchVariantData({
+ categorySlug,
+ componentSlug,
+ variant,
+}: {
+ categorySlug: string;
+ componentSlug?: string;
+ variant: VariantType;
+}) {
const variantName = variant.name;
const variantSlugPreviewFile = variant.slugPreviewFile ?? variantName;
const variantSlugComponentFile = variant.slugComponentFile ?? variantName;
- const previewCode = await getFileContentAsString({
- componentSlug: categorySlug,
- variantName: `${componentSlug}/${variantSlugPreviewFile}`,
- });
+ let previewCode: string | null = null;
+ let componentCode: string | undefined;
+ if (componentSlug) {
+ previewCode = await getFileContentAsString({
+ componentSlug: categorySlug,
+ variantName: `${componentSlug}/${variantSlugPreviewFile}`,
+ });
- const componentCode = variant.slugComponentFile
- ? await getFileContentAsString({
- componentSlug: categorySlug,
- variantName: `${componentSlug}/${variantSlugComponentFile}`,
- })
- : undefined;
+ componentCode = variant.slugComponentFile
+ ? await getFileContentAsString({
+ componentSlug: categorySlug,
+ variantName: `${componentSlug}/${variantSlugComponentFile}`,
+ })
+ : undefined;
+ } else {
+ previewCode = await getFileContentAsString({
+ componentSlug: categorySlug,
+ variantName: variantSlugPreviewFile,
+ });
+ componentCode = variant.slugComponentFile
+ ? await getFileContentAsString({
+ componentSlug: categorySlug,
+ variantName: variantSlugComponentFile,
+ })
+ : undefined;
+ }
return {
...variant,
previewCode,
@@ -41,7 +61,7 @@ export async function fetchVariantData(
};
}
-export async function fetchComponentData({
+export async function fetchMultipleComponentData({
componentList,
categorySlug,
}: {
@@ -54,11 +74,11 @@ export async function fetchComponentData({
const processedVariants: ProcessedVariant[] = [];
for (const variant of component.variantList) {
- const processedVariant = await fetchVariantData(
+ const processedVariant = await fetchVariantData({
categorySlug,
- component.slug,
+ componentSlug: component.slug,
variant,
- );
+ });
processedVariants.push(processedVariant);
}
@@ -72,3 +92,23 @@ export async function fetchComponentData({
return processedComponents;
}
+
+export async function fetchSingleComponentData({
+ categorySlug,
+ component,
+}: {
+ categorySlug: string;
+ component: SingleComponentType;
+}) {
+ const processedVariants: ProcessedVariant[] = [];
+
+ for (const variant of component.variantList) {
+ const processedVariant = await fetchVariantData({ categorySlug, variant });
+ processedVariants.push(processedVariant);
+ }
+
+ return {
+ ...component,
+ componentList: processedVariants,
+ };
+}
diff --git a/src/app/(components)/[section]/[category]/single-component-section.tsx b/src/app/(components)/[section]/[category]/single-component-section.tsx
new file mode 100644
index 00000000..ae334116
--- /dev/null
+++ b/src/app/(components)/[section]/[category]/single-component-section.tsx
@@ -0,0 +1,49 @@
+import { fetchSingleComponentData } from "#/src/app/(components)/[section]/[category]/process-variant-data";
+import ComingSoonCard from "#/src/components/coming-soon";
+import HeaderComponent from "#/src/components/component-wrapper/header-component";
+import InspirationComponentFooter from "#/src/components/component-wrapper/inspiration-component-footer";
+import VariantTabs from "#/src/components/component-wrapper/variant-tabs";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { notFound } from "next/navigation";
+import { Fragment } from "react";
+
+export default async function SingleComponentCategory({
+ category,
+}: Readonly<{ category: SingleComponentCategoryType }>) {
+ if (category.comingSoonCategory) {
+ return ;
+ }
+
+ if (!category.component) {
+ return notFound();
+ }
+
+ const component = await fetchSingleComponentData({
+ categorySlug: category.slug,
+ component: category.component,
+ });
+
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/card.tsx b/src/app/card.tsx
index 3b9d6578..2a2af657 100644
--- a/src/app/card.tsx
+++ b/src/app/card.tsx
@@ -1,12 +1,15 @@
import Image from "next/image";
import { cn } from "#/src/utils/cn";
-import type { CategoryType } from "../lib/types/component";
+import type {
+ CategoryType,
+ SingleComponentCategoryType,
+} from "../lib/types/component";
export const MainMenuCard = ({
category,
}: {
- category?: CategoryType;
+ category?: CategoryType | SingleComponentCategoryType;
}) => {
if (category?.comingSoonCategory) {
return (
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 6c560df8..91781bd3 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -44,22 +44,23 @@ export default function HomePage() {
{section.name}
- {section.categoriesList.map((category) => {
- return (
-
- {
+ return (
+
-
-
- {/* */}
-
- );
- })}
+
+
+
+ {/* */}
+
+ );
+ })}
))}
diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts
index 1215b3e2..7d26d5fe 100644
--- a/src/app/sitemap.ts
+++ b/src/app/sitemap.ts
@@ -5,7 +5,7 @@ export const port = process.env.PORT ?? 3000;
export const HOST =
process.env.NEXT_PUBLIC_SITE_URL ?? `http://localhost:${port}`;
-export default async function sitemap(): Promise {
+export default function sitemap(): MetadataRoute.Sitemap {
// Google's limit is 50,000 URLs per sitemap
return [...staticSitemap, ...getComponentsSitemap()];
@@ -35,17 +35,22 @@ const staticSitemap: MetadataRoute.Sitemap = [
function getComponentsSitemap(): MetadataRoute.Sitemap {
const componentSitemap: MetadataRoute.Sitemap = [];
SectionsList.flatMap((section) => {
- for (const category of section.categoriesList) {
- if (category.comingSoonCategory) {
- return;
- }
- if (section.slug && category.slug) {
- componentSitemap.push({
- url: `${HOST}/${section.slug}/${category.slug}`,
- lastModified: category.releaseDateCategory,
- changeFrequency: "monthly",
- priority: 0.9,
- });
+ if (
+ section.type === "multiple-component" ||
+ section.type === "single-component"
+ ) {
+ for (const category of section.categoriesList) {
+ if (category.comingSoonCategory) {
+ return;
+ }
+ if (section.slug && category.slug) {
+ componentSitemap.push({
+ url: `${HOST}/${section.slug}/${category.slug}`,
+ lastModified: category.releaseDateCategory,
+ changeFrequency: "monthly",
+ priority: 0.9,
+ });
+ }
}
}
});
diff --git a/src/app/tools/color-converter/components/color-converter.tsx b/src/app/tools/color-converter/components/color-converter.tsx
index 2d51442c..ddf93c7a 100644
--- a/src/app/tools/color-converter/components/color-converter.tsx
+++ b/src/app/tools/color-converter/components/color-converter.tsx
@@ -7,7 +7,7 @@ import {
getExactColorTailwindNameFromHexaValue,
} from "#/src/app/tools/color-converter/components/to-tailwind";
import { InlineCode } from "#/src/ui/cuicui/application-ui/code/inline-code/inline-code";
-import { useCopyToClipboard } from "#/src/ui/cuicui/hooks/use-copy-to-clipboard/hook/use-copy-to-clipboard";
+import { useCopyToClipboard } from "#/src/ui/cuicui/hooks/use-copy-to-clipboard/use-copy-to-clipboard";
import { CheckIcon, CopyIcon } from "lucide-react";
import HoverCard from "#/src/ui/cuicui/common-ui/cards/hover-effect-card/hover-effect-card";
import { TailwindCssLogo } from "#/src/assets/logo/tailwind-css-logo";
diff --git a/src/components/component-wrapper/component-tab-renderer.tsx b/src/components/component-wrapper/component-tab-renderer.tsx
index b5379f37..0633293c 100644
--- a/src/components/component-wrapper/component-tab-renderer.tsx
+++ b/src/components/component-wrapper/component-tab-renderer.tsx
@@ -9,7 +9,7 @@ import {
import StepToInstall from "#/src/components/steps-to-install/step-to-install";
import type {
ComponentHeightType,
- VariantComponent,
+ ProcessVariantType,
} from "#/src/lib/types/component";
import CodeHighlighter from "#/src/ui/code-highlighter";
import {
@@ -49,7 +49,7 @@ export default function ComponentTabRenderer({
isIframed?: boolean;
size: ComponentHeightType;
isChildUsingHeightFull?: boolean;
-} & VariantComponent) {
+} & ProcessVariantType) {
return (
@@ -39,7 +39,7 @@ export default function VariantTabs({
"flex items-center p-0.5 *:data-[state=active]:rounded-lg *:data-[state=active]:border-neutral-500/20 *:data-[state=active]:bg-neutral-400/15 *:data-[state=active]:text-neutral-900",
)}
>
- {componentList.map((variant, _index) => {
+ {variantList.map((variant, _index) => {
return (
- {componentList.map((variant, _index) => (
+ {variantList.map((variant, _index) => (
void }>) {
+ const router = useRouter();
+ return (
+
+ {firstMenuSection.categoryList.map((category) => (
+ {
+ if (category.href) {
+ window.open(category.href, "_blank");
+ return;
+ }
+ router.push(`/${category.slug}`);
+ closeSearchMenu();
+ }}
+ >
+
+ {category.name}
+
+ ))}
+
+ );
+}
diff --git a/src/components/search-menu/search-group-multi-component-section.tsx b/src/components/search-menu/search-group-multi-component-section.tsx
new file mode 100644
index 00000000..c2185c20
--- /dev/null
+++ b/src/components/search-menu/search-group-multi-component-section.tsx
@@ -0,0 +1,39 @@
+import type { SectionType } from "#/src/lib/types/component";
+import { CommandGroup, CommandItem } from "#/src/ui/shadcn/command";
+import { useRouter } from "next/navigation";
+
+//TODO: Add variant lists
+export function SearchGroupComponentSection({
+ closeSearchMenu,
+ section,
+}: Readonly<{ closeSearchMenu: () => void; section: SectionType }>) {
+ const router = useRouter();
+
+ if (
+ section.type === "multiple-component" ||
+ section.type === "single-component"
+ ) {
+ return (
+
+ {section.categoriesList.map((category) => {
+ const Icon = category.icon;
+ return (
+ {
+ router.push(`/${section.slug}/${category.slug}`);
+ closeSearchMenu();
+ }}
+ >
+ {Icon && }
+
+ {category.name}
+
+ );
+ })}
+
+ );
+ }
+
+ return null;
+}
diff --git a/src/components/search-menu/search-menu.tsx b/src/components/search-menu/search-menu.tsx
new file mode 100644
index 00000000..b2d7d187
--- /dev/null
+++ b/src/components/search-menu/search-menu.tsx
@@ -0,0 +1,106 @@
+"use client";
+
+import { SearchIcon } from "lucide-react";
+
+import { SectionsList } from "#/src/lib/cuicui-components/sections-list";
+import {
+ CommandDialog,
+ CommandEmpty,
+ CommandInput,
+ CommandList,
+} from "#/src/ui/shadcn/command";
+import { useState } from "react";
+import { useKeyPress } from "#/src/ui/cuicui/hooks/use-key-press/use-key-press";
+import { FirstSectionCommandGroup } from "#/src/components/search-menu/first-section-command-group";
+import { SearchGroupComponentSection } from "#/src/components/search-menu/search-group-multi-component-section";
+
+export function SearchMenu() {
+ const [open, setOpen] = useState(false);
+
+ useKeyPress({
+ keyPressItems: [
+ {
+ keys: ["KeyK", "Meta"],
+ event: () => setOpen((prev) => !prev),
+ },
+ {
+ keys: ["KeyK", "Control"],
+ event: () => setOpen((prev) => !prev),
+ },
+ ],
+ // Important to trigger even on input fields (override default behavior)
+ tagsToIgnore: [""],
+ });
+
+ return (
+ <>
+
+
+
+
+ No results found.
+
+ setOpen(false)} />
+
+ {SectionsList.map((section) => {
+ return (
+ setOpen(false)}
+ section={section}
+ />
+ );
+ })}
+ {/* {SectionsList.map((section) => {
+ if (section.type !== "multiple-component") {
+ return;
+ }
+ return (
+
+ {section.categoriesList.map((category) => {
+ return category.componentList?.map((component) => {
+ const Icon = category.icon;
+ return (
+ {
+ router.push(
+ `/${section.slug}/${category.slug}#${component.slug}`,
+ );
+ setOpen(false);
+ }}
+ >
+ {Icon && (
+
+ )}
+
+ {component.title}
+
+ );
+ });
+ })}
+
+ );
+ })} */}
+
+
+ >
+ );
+}
diff --git a/src/lib/cuicui-components/sections-list.tsx b/src/lib/cuicui-components/sections-list.tsx
index 42a47b1f..252a4cc6 100644
--- a/src/lib/cuicui-components/sections-list.tsx
+++ b/src/lib/cuicui-components/sections-list.tsx
@@ -11,6 +11,7 @@ export const SectionsList: SectionType[] = [
commonUiSection,
applicationUiCategoryList,
{
+ type: "multiple-component",
name: "Marketing UI",
slug: "marketing-ui",
categoriesList: marketingUiComponentList,
diff --git a/src/lib/types/component.d.ts b/src/lib/types/component.d.ts
index 7413eb1b..6058010f 100644
--- a/src/lib/types/component.d.ts
+++ b/src/lib/types/component.d.ts
@@ -4,7 +4,7 @@ import type { JSX, ReactNode } from "react";
import type { ComponentBadgeList } from "../badges.const";
export type Variant = `variant${number}`;
-export type VariantComponent = {
+export type ProcessVariantType = {
name: string;
component: JSX.Element;
previewCode: string;
@@ -41,11 +41,36 @@ export type PreviewComponent = {
------------------------------------
*/
-export type SectionType = {
+// Base interface for common properties
+interface BaseSectionType {
name: string;
slug: string;
+}
+
+// Section with single-component type
+interface SingleComponentSectionType extends BaseSectionType {
+ type: "single-component";
+ categoriesList: SingleComponentCategoryType[];
+}
+
+// Section with multi-component type
+interface MultiComponentSectionType extends BaseSectionType {
+ type: "multiple-component";
categoriesList: CategoryType[];
-};
+}
+
+// Section with page type
+interface PageSectionType extends BaseSectionType {
+ type: "page";
+ href: string;
+ icon: LucideIcon;
+}
+
+// Union type for SectionType
+export type SectionType =
+ | SingleComponentSectionType
+ | MultiComponentSectionType
+ | PageSectionType;
/*
------------------------------------
@@ -64,6 +89,17 @@ type CategoryType = {
componentList: ComponentType[] | null;
};
+type SingleComponentCategoryType = {
+ slug: string;
+ name: string;
+ description: string;
+ icon: LucideIcon;
+ comingSoonCategory?: boolean;
+ releaseDateCategory: Date;
+ previewCategory?: PreviewComponent;
+ component: SingleComponentType | null;
+};
+
/*
------------------------------------
************ Components ************
@@ -84,16 +120,32 @@ export type ComponentType = {
inspirationLink?: string;
sizePreview: ComponentHeightType;
slug: string;
- variantList: ComponentVariantType[];
+ variantList: VariantType[];
+};
+
+export type SingleComponentType = {
+ // Excluded: title, description, slug
+
+ releaseDateComponent?: Date;
+ lastUpdatedDateComponent?: Date;
+ isResizable?: boolean;
+ componentBadges?: ComponentBadgeSlug[];
+ isIframed?: boolean;
+ rerenderButton?: boolean;
+ isChildUsingHeightFull?: boolean;
+ inspiration?: string;
+ inspirationLink?: string;
+ sizePreview: ComponentHeightType;
+ variantList: VariantType[];
};
/*
------------------------------------
-************ Variants ************
+************* Variants *************
------------------------------------
*/
-export type ComponentVariantType = {
+export type VariantType = {
name: string;
component: JSX.Element;
slugComponentFile?: string;
diff --git a/src/ui/cuicui/application-ui/application-ui.section.tsx b/src/ui/cuicui/application-ui/application-ui.section.tsx
index 6af88ea8..8a83e2d5 100644
--- a/src/ui/cuicui/application-ui/application-ui.section.tsx
+++ b/src/ui/cuicui/application-ui/application-ui.section.tsx
@@ -15,6 +15,7 @@ import { themeCategory } from "#/src/ui/cuicui/application-ui/theme/theme.catego
import { treeCategory } from "#/src/ui/cuicui/application-ui/tree/tree.category";
export const applicationUiCategoryList: SectionType = {
+ type: "multiple-component",
name: "Application UI",
slug: "application-ui",
categoriesList: [
diff --git a/src/ui/cuicui/application-ui/code/code-snippet/variant1.tsx b/src/ui/cuicui/application-ui/code/code-snippet/variant1.tsx
index 3dd8b25e..4e2f05c8 100644
--- a/src/ui/cuicui/application-ui/code/code-snippet/variant1.tsx
+++ b/src/ui/cuicui/application-ui/code/code-snippet/variant1.tsx
@@ -1,7 +1,7 @@
"use client";
import { ClipboardIcon } from "lucide-react";
import { toast } from "sonner";
-import { useCopyToClipboard } from "#/src/ui/cuicui/hooks/use-copy-to-clipboard/hook/use-copy-to-clipboard";
+import { useCopyToClipboard } from "#/src/ui/cuicui/hooks/use-copy-to-clipboard/use-copy-to-clipboard";
export function CommandCode({ children }: Readonly<{ children: string }>) {
const [copiedText, copy] = useCopyToClipboard();
diff --git a/src/ui/cuicui/application-ui/signature/react-signature/react-signature.tsx b/src/ui/cuicui/application-ui/signature/react-signature/react-signature.tsx
index 5c81c434..b7b6e94d 100644
--- a/src/ui/cuicui/application-ui/signature/react-signature/react-signature.tsx
+++ b/src/ui/cuicui/application-ui/signature/react-signature/react-signature.tsx
@@ -8,7 +8,7 @@ import {
RefreshCcwIcon,
} from "lucide-react";
import { type ComponentProps, useRef, useState } from "react";
-import { useCopyToClipboard } from "#/src/ui/cuicui/hooks/use-copy-to-clipboard/hook/use-copy-to-clipboard";
+import { useCopyToClipboard } from "#/src/ui/cuicui/hooks/use-copy-to-clipboard/use-copy-to-clipboard";
import { cn } from "#/src/utils/cn";
export function ReactSignature({
diff --git a/src/ui/cuicui/common-ui/common-ui.section.tsx b/src/ui/cuicui/common-ui/common-ui.section.tsx
index 238f8f26..e49a0402 100644
--- a/src/ui/cuicui/common-ui/common-ui.section.tsx
+++ b/src/ui/cuicui/common-ui/common-ui.section.tsx
@@ -14,6 +14,7 @@ import { toggleCategory } from "#/src/ui/cuicui/common-ui/toggle/toggle.category
export const commonUiSection: SectionType = {
name: "Common UI",
slug: "common-ui",
+ type: "multiple-component",
categoriesList: [
avatarsCategory,
badgesCategory,
diff --git a/src/ui/cuicui/hooks/hooks.section.tsx b/src/ui/cuicui/hooks/hooks.section.tsx
index 32797a4f..aecc6590 100644
--- a/src/ui/cuicui/hooks/hooks.section.tsx
+++ b/src/ui/cuicui/hooks/hooks.section.tsx
@@ -1,32 +1,60 @@
-import { PictureInPictureIcon } from "lucide-react";
import type { SectionType } from "#/src/lib/types/component";
import { useBatteryCategory } from "#/src/ui/cuicui/hooks/use-battery/use-battery.category";
-import { useCopyToClipboardCategory } from "#/src/ui/cuicui/hooks/use-copy-to-clipboard/use-copy-to-clipboard.component";
-import { useCounterCategory } from "#/src/ui/cuicui/hooks/use-counter/use-counter.category";
-import { useDebounceCategory } from "#/src/ui/cuicui/hooks/use-debounce/use-debounce.category";
-import { useInViewCategory } from "#/src/ui/cuicui/hooks/use-in-view/use-in-view.category";
-import { useMouseCategory } from "#/src/ui/cuicui/hooks/use-mouse/use-mouse.category";
-import { useNetworkCategory } from "#/src/ui/cuicui/hooks/use-network-status/use-network.category";
+
+import { useCounterCategory } from "#/src/ui/cuicui/hooks/use-counter/category.use-counter";
+import { useDebounceCategory } from "#/src/ui/cuicui/hooks/use-debounce/category.use-debounce";
+import { useInViewCategory } from "#/src/ui/cuicui/hooks/use-in-view/category.use-in-view";
+import { useMouseCategory } from "#/src/ui/cuicui/hooks/use-mouse/category.use-mouse";
+import { useNetworkCategory } from "#/src/ui/cuicui/hooks/use-network-status/category.use-network";
+import { useKeyPressCategory } from "#/src/ui/cuicui/hooks/use-key-press/category.use-key-press";
+import { useInputValueCategory } from "#/src/ui/cuicui/hooks/use-input-value/category.use-input-value";
+import { useGeoLocationCategory } from "#/src/ui/cuicui/hooks/use-geolocation/category-use-geolocation";
+import { useComponentSizeCategory } from "#/src/ui/cuicui/hooks/use-component-size/category.use-component-size";
+import { useKonamiCodeCategory } from "#/src/ui/cuicui/hooks/use-konami-code/category.use-konami-code";
+import { useLocalStorageCategory } from "#/src/ui/cuicui/hooks/use-local-storage/category.use-local-storage";
+import { useWindowSizeCategory } from "#/src/ui/cuicui/hooks/use-window-size/category.use-window-size";
+import { useWindowScrollPositionCategory } from "#/src/ui/cuicui/hooks/use-window-scroll-position/category.use-window-scroll-postion";
+import { useThrottleCategory } from "#/src/ui/cuicui/hooks/use-throttle/category.use-throttle";
+import { useStepCategory } from "#/src/ui/cuicui/hooks/use-step/category.use-step";
+import { useSessionStorageCategory } from "#/src/ui/cuicui/hooks/use-session-storage/category.use-session-storage";
+import { useOnlineStatusCategory } from "#/src/ui/cuicui/hooks/use-online-status/category.use-online-status";
+import { useLocationCategory } from "#/src/ui/cuicui/hooks/use-location/category.use-location";
+import { useIsomorphicLayoutEffectCategory } from "#/src/ui/cuicui/hooks/use-isomorphic-layout-effect/category.use-isomorphic-layout-effect";
+import { useCopyToClipboardCategory } from "#/src/ui/cuicui/hooks/use-copy-to-clipboard/category.use-copy-to-clipboard";
+import { useEventListenerCategory } from "#/src/ui/cuicui/hooks/use-event-listener/category.use-event-listener";
+import { useEventCallbackCategory } from "#/src/ui/cuicui/hooks/use-event-callback/category.use-event-callback";
+import { useSpeechToTextCategory } from "#/src/ui/cuicui/hooks/use-speech-to-text/category.use-speech-to-text";
+import { useTextToSpeechCategory } from "#/src/ui/cuicui/hooks/use-text-to-speech/category.use-text-to-speech";
export const hooksSection: SectionType = {
+ type: "single-component",
name: "Hooks",
slug: "hooks",
categoriesList: [
useBatteryCategory,
- useDebounceCategory,
- {
- slug: "picture-in-picture",
- name: "Picture in Picture",
- description: "Create a picture in picture mode for your videos",
- releaseDateCategory: new Date(),
- icon: PictureInPictureIcon,
- comingSoonCategory: true,
- componentList: null,
- },
- useNetworkCategory,
+ useComponentSizeCategory,
useCopyToClipboardCategory,
useCounterCategory,
+ useDebounceCategory,
+ useEventCallbackCategory,
+ useEventListenerCategory,
+ useGeoLocationCategory,
useInViewCategory,
+ useInputValueCategory,
+ useIsomorphicLayoutEffectCategory,
+ useKeyPressCategory,
+ useKonamiCodeCategory,
+ useLocalStorageCategory,
+ useLocationCategory,
useMouseCategory,
+ useNetworkCategory,
+ useOnlineStatusCategory,
+ useSpeechToTextCategory,
+ useSessionStorageCategory,
+ useStepCategory,
+ useTextToSpeechCategory,
+ useThrottleCategory,
+ useWindowScrollPositionCategory,
+ useWindowSizeCategory,
],
};
diff --git a/src/ui/cuicui/hooks/use-battery/hook/preview-use-battery.tsx b/src/ui/cuicui/hooks/use-battery/preview-use-battery.tsx
similarity index 93%
rename from src/ui/cuicui/hooks/use-battery/hook/preview-use-battery.tsx
rename to src/ui/cuicui/hooks/use-battery/preview-use-battery.tsx
index 6159098b..572ea780 100644
--- a/src/ui/cuicui/hooks/use-battery/hook/preview-use-battery.tsx
+++ b/src/ui/cuicui/hooks/use-battery/preview-use-battery.tsx
@@ -1,7 +1,7 @@
"use client";
import { BatteryIndicator } from "#/src/ui/cuicui/application-ui/battery/battery-indicator/battery-indicator";
import { ThreeDotSimpleLoader } from "#/src/ui/cuicui/common-ui/loaders/three-dot-simple-loader/three-dot-simple-loader"; // Allows to wait for the battery information to load
-import { useBattery } from "#/src/ui/cuicui/hooks/use-battery/hook/use-battery";
+import { useBattery } from "#/src/ui/cuicui/hooks/use-battery/use-battery";
export const PreviewUseBattery = () => {
const {
diff --git a/src/ui/cuicui/hooks/use-battery/use-battery.category.tsx b/src/ui/cuicui/hooks/use-battery/use-battery.category.tsx
index d3eb8218..58487855 100644
--- a/src/ui/cuicui/hooks/use-battery/use-battery.category.tsx
+++ b/src/ui/cuicui/hooks/use-battery/use-battery.category.tsx
@@ -1,8 +1,8 @@
import { BatteryCharging } from "lucide-react";
-import type { CategoryType } from "#/src/lib/types/component";
-import { PreviewUseBattery } from "#/src/ui/cuicui/hooks/use-battery/hook/preview-use-battery";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { PreviewUseBattery } from "#/src/ui/cuicui/hooks/use-battery/preview-use-battery";
-export const useBatteryCategory: CategoryType = {
+export const useBatteryCategory: SingleComponentCategoryType = {
slug: "use-battery",
name: "Use Battery",
description: "A hook to get battery informations",
@@ -12,23 +12,18 @@ export const useBatteryCategory: CategoryType = {
component: ,
previewScale: 1,
},
- componentList: [
- {
- rerenderButton: true,
- lastUpdatedDateComponent: new Date("2024-09-16"),
- sizePreview: "lg",
- slug: "hook",
- isIframed: false,
- title: "Use Battery",
- description: "A hook to get battery informations",
- variantList: [
- {
- name: "variant 1",
- component: ,
- slugComponentFile: "use-battery",
- slugPreviewFile: "preview-use-battery",
- },
- ],
- },
- ],
+ component: {
+ rerenderButton: true,
+ lastUpdatedDateComponent: new Date("2024-09-16"),
+ sizePreview: "lg",
+ isIframed: false,
+ variantList: [
+ {
+ name: "variant 1",
+ component: ,
+ slugComponentFile: "use-battery",
+ slugPreviewFile: "preview-use-battery",
+ },
+ ],
+ },
};
diff --git a/src/ui/cuicui/hooks/use-battery/hook/use-battery.tsx b/src/ui/cuicui/hooks/use-battery/use-battery.tsx
similarity index 100%
rename from src/ui/cuicui/hooks/use-battery/hook/use-battery.tsx
rename to src/ui/cuicui/hooks/use-battery/use-battery.tsx
diff --git a/src/ui/cuicui/hooks/use-component-size/category.use-component-size.tsx b/src/ui/cuicui/hooks/use-component-size/category.use-component-size.tsx
new file mode 100644
index 00000000..d3475d18
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-component-size/category.use-component-size.tsx
@@ -0,0 +1,28 @@
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { PreviewUseComponentSize } from "#/src/ui/cuicui/hooks/use-component-size/preview-use-component-size";
+import { RulerIcon } from "lucide-react";
+
+export const useComponentSizeCategory: SingleComponentCategoryType = {
+ slug: "use-component-size",
+ name: "Use Component Size",
+ description: "A hook that allows you to manage the size of a component",
+ releaseDateCategory: new Date("2024-09-28"),
+ icon: RulerIcon,
+ previewCategory: {
+ component: ,
+ previewScale: 0.8,
+ },
+ component: {
+ lastUpdatedDateComponent: new Date("2024-09-28"),
+ sizePreview: "sm",
+ isIframed: false,
+ variantList: [
+ {
+ name: "default variant",
+ component: ,
+ slugComponentFile: "use-component-size",
+ slugPreviewFile: "preview-use-component-size",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-component-size/preview-use-component-size.tsx b/src/ui/cuicui/hooks/use-component-size/preview-use-component-size.tsx
new file mode 100644
index 00000000..4802aa55
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-component-size/preview-use-component-size.tsx
@@ -0,0 +1,29 @@
+"use client";
+import { useComponentSize } from "#/src/ui/cuicui/hooks/use-component-size/use-component-size";
+import { useRef } from "react";
+
+export const PreviewUseComponentSize = () => {
+ const divRef = useRef(null);
+ const { width, height } = useComponentSize(divRef);
+
+ return (
+
+
+ Resize me!
+
+
+ Width: {width}px, Height: {height}px
+
+
+ );
+};
diff --git a/src/ui/cuicui/hooks/use-component-size/use-component-size.ts b/src/ui/cuicui/hooks/use-component-size/use-component-size.ts
new file mode 100644
index 00000000..830de0aa
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-component-size/use-component-size.ts
@@ -0,0 +1,77 @@
+"use client";
+
+import { useState, useLayoutEffect, type RefObject, useCallback } from "react";
+
+// Define a type for the size object
+interface Size {
+ width: number;
+ height: number;
+}
+
+/**
+ * Custom hook to track the size of a DOM element.
+ *
+ * @param ref - RefObject pointing to the DOM element.
+ * @returns An object containing the width and height of the element.
+ */
+export function useComponentSize(
+ ref: RefObject,
+): Size {
+ const getSize = useCallback((element: T | null): Size => {
+ if (!element) {
+ return { width: 0, height: 0 };
+ }
+
+ return {
+ width: element.offsetWidth,
+ height: element.offsetHeight,
+ };
+ }, []);
+
+ const [size, setSize] = useState(() => getSize(ref.current));
+
+ useLayoutEffect(() => {
+ if (!ref.current) {
+ return;
+ }
+
+ const handleResize = (entries: ResizeObserverEntry[]) => {
+ if (!Array.isArray(entries)) {
+ return;
+ }
+ const entry = entries[0];
+ if (entry) {
+ const { width, height } = entry.contentRect;
+ setSize({ width, height });
+ }
+ };
+
+ // Check if ResizeObserver is supported
+ if (typeof ResizeObserver !== "undefined") {
+ const resizeObserver = new ResizeObserver(handleResize);
+ resizeObserver.observe(ref.current);
+
+ // Initialize size
+ setSize(getSize(ref.current));
+
+ return () => {
+ resizeObserver.disconnect();
+ };
+ }
+ // Fallback to window resize event
+ const handleWindowResize = () => {
+ setSize(getSize(ref.current));
+ };
+
+ window.addEventListener("resize", handleWindowResize);
+
+ // Initialize size
+ handleWindowResize();
+
+ return () => {
+ window.removeEventListener("resize", handleWindowResize);
+ };
+ }, [ref, getSize]);
+
+ return size;
+}
diff --git a/src/ui/cuicui/hooks/use-copy-to-clipboard/category.use-copy-to-clipboard.tsx b/src/ui/cuicui/hooks/use-copy-to-clipboard/category.use-copy-to-clipboard.tsx
new file mode 100644
index 00000000..1d948333
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-copy-to-clipboard/category.use-copy-to-clipboard.tsx
@@ -0,0 +1,27 @@
+import { ClipboardCopyIcon } from "lucide-react";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import PreviewCopyToClipboard from "#/src/ui/cuicui/hooks/use-copy-to-clipboard/preview-copy-to-clipboard";
+
+export const useCopyToClipboardCategory: SingleComponentCategoryType = {
+ slug: "use-copy-to-clipboard",
+ name: "Use Copy to Clipboard",
+ description: "A hook that allows you to copy text to the clipboard",
+ releaseDateCategory: new Date("2024-08-20"),
+ icon: ClipboardCopyIcon,
+ previewCategory: {
+ component: ,
+ previewScale: 0.8,
+ },
+ component: {
+ sizePreview: "lg",
+ isIframed: false,
+ variantList: [
+ {
+ name: "variant 1",
+ component: ,
+ slugComponentFile: "use-copy-to-clipboard",
+ slugPreviewFile: "preview-copy-to-clipboard",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-copy-to-clipboard/hook/preview-copy-to-clipboard.tsx b/src/ui/cuicui/hooks/use-copy-to-clipboard/preview-copy-to-clipboard.tsx
similarity index 96%
rename from src/ui/cuicui/hooks/use-copy-to-clipboard/hook/preview-copy-to-clipboard.tsx
rename to src/ui/cuicui/hooks/use-copy-to-clipboard/preview-copy-to-clipboard.tsx
index e2fb600f..f97734de 100644
--- a/src/ui/cuicui/hooks/use-copy-to-clipboard/hook/preview-copy-to-clipboard.tsx
+++ b/src/ui/cuicui/hooks/use-copy-to-clipboard/preview-copy-to-clipboard.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useCopyToClipboard } from "#/src/ui/cuicui/hooks/use-copy-to-clipboard/hook/use-copy-to-clipboard";
+import { useCopyToClipboard } from "#/src/ui/cuicui/hooks/use-copy-to-clipboard/use-copy-to-clipboard";
export default function PreviewCopyToClipboard() {
const [copiedText, copy] = useCopyToClipboard();
diff --git a/src/ui/cuicui/hooks/use-copy-to-clipboard/use-copy-to-clipboard.component.tsx b/src/ui/cuicui/hooks/use-copy-to-clipboard/use-copy-to-clipboard.component.tsx
deleted file mode 100644
index e9e13d93..00000000
--- a/src/ui/cuicui/hooks/use-copy-to-clipboard/use-copy-to-clipboard.component.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { ClipboardCopyIcon } from "lucide-react";
-import type { CategoryType } from "#/src/lib/types/component";
-import PreviewCopyToClipboard from "#/src/ui/cuicui/hooks/use-copy-to-clipboard/hook/preview-copy-to-clipboard";
-
-export const useCopyToClipboardCategory: CategoryType = {
- slug: "use-copy-to-clipboard",
- name: "Copy to Clipboard",
- description: "A hook that allows you to copy text to the clipboard",
- releaseDateCategory: new Date("2024-08-20"),
- icon: ClipboardCopyIcon,
- previewCategory: {
- component: ,
- previewScale: 0.8,
- },
- componentList: [
- {
- sizePreview: "lg",
- slug: "hook",
- isIframed: false,
- title: "Copy to Clipboard",
- description: "A hook that allows you to copy text to the clipboard",
- variantList: [
- {
- name: "variant 1",
- component: ,
- slugComponentFile: "use-copy-to-clipboard",
- slugPreviewFile: "preview-copy-to-clipboard",
- },
- ],
- },
- ],
-};
diff --git a/src/ui/cuicui/hooks/use-copy-to-clipboard/hook/use-copy-to-clipboard.tsx b/src/ui/cuicui/hooks/use-copy-to-clipboard/use-copy-to-clipboard.tsx
similarity index 100%
rename from src/ui/cuicui/hooks/use-copy-to-clipboard/hook/use-copy-to-clipboard.tsx
rename to src/ui/cuicui/hooks/use-copy-to-clipboard/use-copy-to-clipboard.tsx
diff --git a/src/ui/cuicui/hooks/use-counter/category.use-counter.tsx b/src/ui/cuicui/hooks/use-counter/category.use-counter.tsx
new file mode 100644
index 00000000..058ddbc3
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-counter/category.use-counter.tsx
@@ -0,0 +1,28 @@
+import { CalculatorIcon } from "lucide-react";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import PreviewUseCounter from "#/src/ui/cuicui/hooks/use-counter/preview-use-counter";
+
+export const useCounterCategory: SingleComponentCategoryType = {
+ slug: "use-counter",
+ name: "Use counter",
+ description: "A hook that allows you to count easily",
+ releaseDateCategory: new Date("2024-09-16"),
+ icon: CalculatorIcon,
+ previewCategory: {
+ component: ,
+ previewScale: 1,
+ },
+ component: {
+ lastUpdatedDateComponent: new Date("2024-09-16"),
+ sizePreview: "lg",
+ isIframed: false,
+ variantList: [
+ {
+ name: "variant 1",
+ component: ,
+ slugComponentFile: "use-counter",
+ slugPreviewFile: "preview-use-counter",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-counter/hook/preview-use-counter.tsx b/src/ui/cuicui/hooks/use-counter/preview-use-counter.tsx
similarity index 94%
rename from src/ui/cuicui/hooks/use-counter/hook/preview-use-counter.tsx
rename to src/ui/cuicui/hooks/use-counter/preview-use-counter.tsx
index 7c5bd378..c996ad8f 100644
--- a/src/ui/cuicui/hooks/use-counter/hook/preview-use-counter.tsx
+++ b/src/ui/cuicui/hooks/use-counter/preview-use-counter.tsx
@@ -1,6 +1,6 @@
"use client";
import { BeforeEffectButton } from "#/src/ui/cuicui/common-ui/buttons/before-effect-button/before-effect-button";
-import { useCounter } from "#/src/ui/cuicui/hooks/use-counter/hook/use-counter";
+import { useCounter } from "#/src/ui/cuicui/hooks/use-counter/use-counter";
export default function PreviewUseCounter() {
const [count, { increment, decrement, set, reset }] = useCounter(6, {
diff --git a/src/ui/cuicui/hooks/use-counter/use-counter.category.tsx b/src/ui/cuicui/hooks/use-counter/use-counter.category.tsx
deleted file mode 100644
index ff1b0972..00000000
--- a/src/ui/cuicui/hooks/use-counter/use-counter.category.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { CalculatorIcon } from "lucide-react";
-import type { CategoryType } from "#/src/lib/types/component";
-import PreviewUseCounter from "#/src/ui/cuicui/hooks/use-counter/hook/preview-use-counter";
-
-export const useCounterCategory: CategoryType = {
- slug: "use-counter",
- name: "Use counter",
- description: "A hook that allows you to count easily",
- releaseDateCategory: new Date("2024-09-16"),
- icon: CalculatorIcon,
- previewCategory: {
- component: ,
- previewScale: 1,
- },
- componentList: [
- {
- lastUpdatedDateComponent: new Date("2024-09-16"),
- sizePreview: "lg",
- slug: "hook",
- isIframed: false,
- title: "Use counter",
- description: "A hook that allows you to count easily",
- variantList: [
- {
- name: "variant 1",
- component: ,
- slugComponentFile: "use-counter",
- slugPreviewFile: "preview-use-counter",
- },
- ],
- },
- ],
-};
diff --git a/src/ui/cuicui/hooks/use-counter/hook/use-counter.tsx b/src/ui/cuicui/hooks/use-counter/use-counter.tsx
similarity index 100%
rename from src/ui/cuicui/hooks/use-counter/hook/use-counter.tsx
rename to src/ui/cuicui/hooks/use-counter/use-counter.tsx
diff --git a/src/ui/cuicui/hooks/use-debounce/category.use-debounce.tsx b/src/ui/cuicui/hooks/use-debounce/category.use-debounce.tsx
new file mode 100644
index 00000000..68bcd0c9
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-debounce/category.use-debounce.tsx
@@ -0,0 +1,28 @@
+import { TimerIcon } from "lucide-react";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import PreviewUseDebounce from "#/src/ui/cuicui/hooks/use-debounce/preview-use-debounce";
+
+export const useDebounceCategory: SingleComponentCategoryType = {
+ slug: "use-debounce",
+ name: "Use debounce",
+ description: "A hook that allows you to debounce the value of an input",
+ releaseDateCategory: new Date("2024-09-16"),
+ icon: TimerIcon,
+ previewCategory: {
+ component: ,
+ previewScale: 1,
+ },
+ component: {
+ lastUpdatedDateComponent: new Date("2024-09-16"),
+ sizePreview: "lg",
+ isIframed: false,
+ variantList: [
+ {
+ name: "variant 1",
+ component: ,
+ slugComponentFile: "use-debounce",
+ slugPreviewFile: "preview-use-debounce",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-debounce/hook/preview-use-debounce.tsx b/src/ui/cuicui/hooks/use-debounce/preview-use-debounce.tsx
similarity index 98%
rename from src/ui/cuicui/hooks/use-debounce/hook/preview-use-debounce.tsx
rename to src/ui/cuicui/hooks/use-debounce/preview-use-debounce.tsx
index 465b64a2..90e797dc 100644
--- a/src/ui/cuicui/hooks/use-debounce/hook/preview-use-debounce.tsx
+++ b/src/ui/cuicui/hooks/use-debounce/preview-use-debounce.tsx
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { ModernSimpleInput } from "#/src/ui/cuicui/common-ui/inputs/modern-simple-input/modern-simple-input";
-import { useDebounce } from "#/src/ui/cuicui/hooks/use-debounce/hook/use-debounce";
+import { useDebounce } from "#/src/ui/cuicui/hooks/use-debounce/use-debounce";
export default function PreviewUseDebounce() {
const [value, setValue] = useState("");
diff --git a/src/ui/cuicui/hooks/use-debounce/use-debounce.category.tsx b/src/ui/cuicui/hooks/use-debounce/use-debounce.category.tsx
deleted file mode 100644
index 96553193..00000000
--- a/src/ui/cuicui/hooks/use-debounce/use-debounce.category.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { TimerIcon } from "lucide-react";
-import type { CategoryType } from "#/src/lib/types/component";
-import PreviewUseDebounce from "#/src/ui/cuicui/hooks/use-debounce/hook/preview-use-debounce";
-
-export const useDebounceCategory: CategoryType = {
- slug: "use-debounce",
- name: "Use debounce",
- description: "A hook that allows you to debounce the value of an input",
- releaseDateCategory: new Date("2024-09-16"),
- icon: TimerIcon,
- previewCategory: {
- component: ,
- previewScale: 1,
- },
- componentList: [
- {
- lastUpdatedDateComponent: new Date("2024-09-16"),
- sizePreview: "lg",
- slug: "hook",
- isIframed: false,
- title: "Use in View",
- description: "A hook that allows you to debounce the value of an input",
- variantList: [
- {
- name: "variant 1",
- component: ,
- slugComponentFile: "use-debounce",
- slugPreviewFile: "preview-use-debounce",
- },
- ],
- },
- ],
-};
diff --git a/src/ui/cuicui/hooks/use-debounce/hook/use-debounce.tsx b/src/ui/cuicui/hooks/use-debounce/use-debounce.tsx
similarity index 100%
rename from src/ui/cuicui/hooks/use-debounce/hook/use-debounce.tsx
rename to src/ui/cuicui/hooks/use-debounce/use-debounce.tsx
diff --git a/src/ui/cuicui/hooks/use-event-callback/category.use-event-callback.tsx b/src/ui/cuicui/hooks/use-event-callback/category.use-event-callback.tsx
new file mode 100644
index 00000000..fb893e2b
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-event-callback/category.use-event-callback.tsx
@@ -0,0 +1,28 @@
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { PreviewUseEventCallback } from "#/src/ui/cuicui/hooks/use-event-callback/preview-use-event-callback";
+import { SquareFunctionIcon } from "lucide-react";
+
+export const useEventCallbackCategory: SingleComponentCategoryType = {
+ slug: "use-event-callback",
+ name: "Use Event Callback",
+ description: "A hook that returns a memoized event callback",
+ releaseDateCategory: new Date("2024-09-28"),
+ icon: SquareFunctionIcon,
+ previewCategory: {
+ component: ,
+ previewScale: 0.8,
+ },
+ component: {
+ lastUpdatedDateComponent: new Date("2024-09-28"),
+ sizePreview: "sm",
+ isIframed: false,
+ variantList: [
+ {
+ name: "variant 1",
+ component: ,
+ slugComponentFile: "use-event-callback",
+ slugPreviewFile: "preview-use-event-callback",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-event-callback/preview-use-event-callback.tsx b/src/ui/cuicui/hooks/use-event-callback/preview-use-event-callback.tsx
new file mode 100644
index 00000000..1240764e
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-event-callback/preview-use-event-callback.tsx
@@ -0,0 +1,3 @@
+export function PreviewUseEventCallback() {
+ return preview-use-event-callback
;
+}
diff --git a/src/ui/cuicui/hooks/use-event-callback/use-event-callback.ts b/src/ui/cuicui/hooks/use-event-callback/use-event-callback.ts
new file mode 100644
index 00000000..534261ac
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-event-callback/use-event-callback.ts
@@ -0,0 +1,26 @@
+import { useIsomorphicLayoutEffect } from "#/src/ui/cuicui/hooks/use-isomorphic-layout-effect/use-isomorphic-layout-effect";
+import { useCallback, useRef } from "react";
+
+export function useEventCallback(
+ fn: (...args: Args) => R,
+): (...args: Args) => R;
+export function useEventCallback(
+ fn: ((...args: Args) => R) | undefined,
+): ((...args: Args) => R) | undefined;
+export function useEventCallback(
+ fn: ((...args: Args) => R) | undefined,
+): ((...args: Args) => R) | undefined {
+ const ref = useRef(() => {
+ throw new Error("Cannot call an event handler while rendering.");
+ });
+
+ useIsomorphicLayoutEffect(() => {
+ ref.current = fn;
+ }, [fn]);
+
+ //!! To check
+ // biome-ignore lint/correctness/useExhaustiveDependencies:
+ return useCallback((...args: Args) => ref.current?.(...args), [ref]) as (
+ ...args: Args
+ ) => R;
+}
diff --git a/src/ui/cuicui/hooks/use-event-listener/category.use-event-listener.tsx b/src/ui/cuicui/hooks/use-event-listener/category.use-event-listener.tsx
new file mode 100644
index 00000000..40240ade
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-event-listener/category.use-event-listener.tsx
@@ -0,0 +1,29 @@
+import { EarIcon } from "lucide-react";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { PreviewUseEventListener } from "#/src/ui/cuicui/hooks/use-event-listener/preview-use-event-listener";
+
+export const useEventListenerCategory: SingleComponentCategoryType = {
+ slug: "use-event-listener",
+ name: "Use Event Listener",
+ description:
+ "A hook that allows you to manage event listeners on DOM elements",
+ releaseDateCategory: new Date("2024-09-28"),
+ icon: EarIcon,
+ previewCategory: {
+ component: ,
+ previewScale: 0.8,
+ },
+ component: {
+ lastUpdatedDateComponent: new Date("2024-09-28"),
+ sizePreview: "sm",
+ isIframed: false,
+ variantList: [
+ {
+ name: "Default Variant",
+ component: ,
+ slugComponentFile: "use-event-listener",
+ slugPreviewFile: "preview-use-event-listener",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-event-listener/preview-use-event-listener.tsx b/src/ui/cuicui/hooks/use-event-listener/preview-use-event-listener.tsx
new file mode 100644
index 00000000..dde870a5
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-event-listener/preview-use-event-listener.tsx
@@ -0,0 +1,34 @@
+"use client";
+import { useEventListener } from "#/src/ui/cuicui/hooks/use-event-listener/use-event-listener";
+import { useState } from "react";
+export const PreviewUseEventListener = () => {
+ // Initialize state with current window dimensions
+ if (typeof window === "undefined") {
+ return null;
+ }
+ const [windowSize, setWindowSize] = useState({
+ width: window.innerWidth,
+ height: window.innerHeight,
+ });
+
+ // Handler function to update state on resize
+ const handleResize = () => {
+ setWindowSize({
+ width: window.innerWidth,
+ height: window.innerHeight,
+ });
+ };
+
+ // Use the custom useEventListener hook to listen for 'resize' events on the window
+ useEventListener("resize", handleResize);
+
+ return (
+
+
Window Size Demo
+
+
Width: {windowSize.width}px
+
Height: {windowSize.height}px
+
+
+ );
+};
diff --git a/src/ui/cuicui/hooks/use-event-listener/use-event-listener.ts b/src/ui/cuicui/hooks/use-event-listener/use-event-listener.ts
new file mode 100644
index 00000000..80e7d5c1
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-event-listener/use-event-listener.ts
@@ -0,0 +1,93 @@
+"use client";
+import { useEffect, useRef } from "react";
+
+import type { RefObject } from "react";
+
+import { useIsomorphicLayoutEffect } from "#/src/ui/cuicui/hooks/use-isomorphic-layout-effect/use-isomorphic-layout-effect";
+// MediaQueryList Event based useEventListener interface
+function useEventListener(
+ eventName: K,
+ handler: (event: MediaQueryListEventMap[K]) => void,
+ element: RefObject,
+ options?: boolean | AddEventListenerOptions,
+): void;
+
+// Window Event based useEventListener interface
+function useEventListener(
+ eventName: K,
+ handler: (event: WindowEventMap[K]) => void,
+ element?: undefined,
+ options?: boolean | AddEventListenerOptions,
+): void;
+
+// Element Event based useEventListener interface
+function useEventListener<
+ K extends keyof HTMLElementEventMap & keyof SVGElementEventMap,
+ T extends Element = K extends keyof HTMLElementEventMap
+ ? HTMLDivElement
+ : SVGElement,
+>(
+ eventName: K,
+ handler:
+ | ((event: HTMLElementEventMap[K]) => void)
+ | ((event: SVGElementEventMap[K]) => void),
+ element: RefObject,
+ options?: boolean | AddEventListenerOptions,
+): void;
+
+// Document Event based useEventListener interface
+function useEventListener(
+ eventName: K,
+ handler: (event: DocumentEventMap[K]) => void,
+ element: RefObject,
+ options?: boolean | AddEventListenerOptions,
+): void;
+
+function useEventListener<
+ KW extends keyof WindowEventMap,
+ KH extends keyof HTMLElementEventMap & keyof SVGElementEventMap,
+ KM extends keyof MediaQueryListEventMap,
+ T extends HTMLElement | SVGAElement | MediaQueryList = HTMLElement,
+>(
+ eventName: KW | KH | KM,
+ handler: (
+ event:
+ | WindowEventMap[KW]
+ | HTMLElementEventMap[KH]
+ | SVGElementEventMap[KH]
+ | MediaQueryListEventMap[KM]
+ | Event,
+ ) => void,
+ element?: RefObject,
+ options?: boolean | AddEventListenerOptions,
+) {
+ // Create a ref that stores handler
+ const savedHandler = useRef(handler);
+
+ useIsomorphicLayoutEffect(() => {
+ savedHandler.current = handler;
+ }, [handler]);
+
+ useEffect(() => {
+ // Define the listening target
+ const targetElement: T | Window = element?.current ?? window;
+
+ if (!targetElement?.addEventListener) {
+ return;
+ }
+
+ // Create event listener that calls handler function stored in ref
+ const listener: typeof handler = (event) => {
+ savedHandler.current(event);
+ };
+
+ targetElement.addEventListener(eventName, listener, options);
+
+ // Remove event listener on cleanup
+ return () => {
+ targetElement.removeEventListener(eventName, listener, options);
+ };
+ }, [eventName, element, options]);
+}
+
+export { useEventListener };
diff --git a/src/ui/cuicui/hooks/use-geolocation/category-use-geolocation.tsx b/src/ui/cuicui/hooks/use-geolocation/category-use-geolocation.tsx
new file mode 100644
index 00000000..ace999c5
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-geolocation/category-use-geolocation.tsx
@@ -0,0 +1,28 @@
+import { Globe2Icon } from "lucide-react";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { PreviewUseGeolocation } from "#/src/ui/cuicui/hooks/use-geolocation/preview-use-geolocation";
+export const useGeoLocationCategory: SingleComponentCategoryType = {
+ slug: "use-geolocation",
+ name: "Use GeoLocation",
+ description:
+ "A hook that allows you to access and manage the user's geolocation data",
+ releaseDateCategory: new Date("2024-09-28"),
+ icon: Globe2Icon,
+ previewCategory: {
+ component: ,
+ previewScale: 0.8,
+ },
+ component: {
+ lastUpdatedDateComponent: new Date("2024-09-28"),
+ sizePreview: "sm",
+ isIframed: false,
+ variantList: [
+ {
+ name: "variant 1",
+ component: ,
+ slugComponentFile: "use-geolocation",
+ slugPreviewFile: "preview-use-geolocation",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-geolocation/preview-use-geolocation.tsx b/src/ui/cuicui/hooks/use-geolocation/preview-use-geolocation.tsx
new file mode 100644
index 00000000..d5c39aae
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-geolocation/preview-use-geolocation.tsx
@@ -0,0 +1,8 @@
+"use client";
+import { useGeolocation } from "#/src/ui/cuicui/hooks/use-geolocation/use-geolocation";
+
+export function PreviewUseGeolocation() {
+ const state = useGeolocation();
+
+ return {JSON.stringify(state, null, 2)}
;
+}
diff --git a/src/ui/cuicui/hooks/use-geolocation/use-geolocation.tsx b/src/ui/cuicui/hooks/use-geolocation/use-geolocation.tsx
new file mode 100644
index 00000000..d90cf1cd
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-geolocation/use-geolocation.tsx
@@ -0,0 +1,97 @@
+import { useState, useEffect, useRef } from "react";
+
+// Define the shape of the geolocation data
+interface GeolocationData {
+ accuracy: number;
+ altitude: number | null;
+ altitudeAccuracy: number | null;
+ heading: number | null;
+ latitude: number;
+ longitude: number;
+ speed: number | null;
+ timestamp: number;
+}
+
+// Define possible geolocation errors
+interface GeolocationError {
+ code: number;
+ message: string;
+}
+
+// Define the return type of the hook
+interface UseGeolocationReturn {
+ data: GeolocationData | null;
+ error: GeolocationError | null;
+ isLoading: boolean;
+}
+
+export const useGeolocation = (): UseGeolocationReturn => {
+ const [data, setData] = useState(null);
+ const [error, setError] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+ const watchIdRef = useRef(null);
+
+ useEffect(() => {
+ if (!navigator.geolocation) {
+ setError({
+ code: 0,
+ message: "Geolocation is not supported by your browser.",
+ });
+ setIsLoading(false);
+ return;
+ }
+
+ // Success handler
+ const handleSuccess = (position: GeolocationPosition) => {
+ const {
+ accuracy,
+ altitude,
+ altitudeAccuracy,
+ heading,
+ latitude,
+ longitude,
+ speed,
+ } = position.coords;
+ const { timestamp } = position;
+
+ setData({
+ accuracy,
+ altitude,
+ altitudeAccuracy,
+ heading,
+ latitude,
+ longitude,
+ speed,
+ timestamp,
+ });
+ setIsLoading(false);
+ };
+
+ // Error handler
+ const handleError = (geolocationError: GeolocationPositionError) => {
+ setError({
+ code: geolocationError.code,
+ message: geolocationError.message,
+ });
+ setIsLoading(false);
+ };
+
+ // Get the current position
+ navigator.geolocation.getCurrentPosition(handleSuccess, handleError);
+
+ // Watch for position changes
+ watchIdRef.current = navigator.geolocation.watchPosition(
+ handleSuccess,
+ handleError,
+ );
+
+ // Cleanup function to clear the watch on unmount
+ return () => {
+ if (watchIdRef.current !== null) {
+ navigator.geolocation.clearWatch(watchIdRef.current);
+ }
+ };
+ }, []);
+
+ return { data, error, isLoading };
+};
diff --git a/src/ui/cuicui/hooks/use-in-view/category.use-in-view.tsx b/src/ui/cuicui/hooks/use-in-view/category.use-in-view.tsx
new file mode 100644
index 00000000..3ccad218
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-in-view/category.use-in-view.tsx
@@ -0,0 +1,32 @@
+import { ViewIcon } from "lucide-react";
+import PreviewImageUseInView from "#/src/assets/components-preview/use-in-view.png";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { PreviewUseInView } from "#/src/ui/cuicui/hooks/use-in-view/preview-in-view";
+export const useInViewCategory: SingleComponentCategoryType = {
+ slug: "use-in-view",
+ name: "Use in View",
+ description:
+ "A hook that allows you to know if an element is in the viewport",
+ releaseDateCategory: new Date("2024-08-28"),
+ icon: ViewIcon,
+ previewCategory: {
+ component: ,
+ previewImage: PreviewImageUseInView,
+ previewScale: 0.8,
+ },
+ component: {
+ lastUpdatedDateComponent: new Date("2024-08-28"),
+ sizePreview: "lg",
+
+ isIframed: true,
+
+ variantList: [
+ {
+ name: "variant 1",
+ component: ,
+ slugComponentFile: "use-in-view",
+ slugPreviewFile: "preview-in-view",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-in-view/hook/preview-in-view.tsx b/src/ui/cuicui/hooks/use-in-view/preview-in-view.tsx
similarity index 96%
rename from src/ui/cuicui/hooks/use-in-view/hook/preview-in-view.tsx
rename to src/ui/cuicui/hooks/use-in-view/preview-in-view.tsx
index 05d0def9..44e3c3b9 100644
--- a/src/ui/cuicui/hooks/use-in-view/hook/preview-in-view.tsx
+++ b/src/ui/cuicui/hooks/use-in-view/preview-in-view.tsx
@@ -1,7 +1,7 @@
"use client";
import { useRef } from "react";
-import useInView from "#/src/ui/cuicui/hooks/use-in-view/hook/use-in-view";
+import useInView from "#/src/ui/cuicui/hooks/use-in-view/use-in-view";
export const PreviewUseInView = () => {
const ref1 = useRef(null);
diff --git a/src/ui/cuicui/hooks/use-in-view/use-in-view.category.tsx b/src/ui/cuicui/hooks/use-in-view/use-in-view.category.tsx
deleted file mode 100644
index 45fecbc5..00000000
--- a/src/ui/cuicui/hooks/use-in-view/use-in-view.category.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { ViewIcon } from "lucide-react";
-import PreviewImageUseInView from "#/src/assets/components-preview/use-in-view.png";
-import type { CategoryType } from "#/src/lib/types/component";
-import { PreviewUseInView } from "#/src/ui/cuicui/hooks/use-in-view/hook/preview-in-view";
-export const useInViewCategory: CategoryType = {
- slug: "use-in-view",
- name: "Use in View",
- description:
- "A hook that allows you to know if an element is in the viewport",
- releaseDateCategory: new Date("2024-08-28"),
- icon: ViewIcon,
- previewCategory: {
- component: ,
- previewImage: PreviewImageUseInView,
- previewScale: 0.8,
- },
- componentList: [
- {
- lastUpdatedDateComponent: new Date("2024-08-28"),
- sizePreview: "lg",
- slug: "hook",
- isIframed: true,
- title: "Use in View",
- description:
- "A hook that allows you to know if an element is in the viewport",
- variantList: [
- {
- name: "variant 1",
- component: ,
- slugComponentFile: "use-in-view",
- slugPreviewFile: "preview-in-view",
- },
- ],
- },
- ],
-};
diff --git a/src/ui/cuicui/hooks/use-in-view/hook/use-in-view.tsx b/src/ui/cuicui/hooks/use-in-view/use-in-view.tsx
similarity index 100%
rename from src/ui/cuicui/hooks/use-in-view/hook/use-in-view.tsx
rename to src/ui/cuicui/hooks/use-in-view/use-in-view.tsx
diff --git a/src/ui/cuicui/hooks/use-input-value/category.use-input-value.tsx b/src/ui/cuicui/hooks/use-input-value/category.use-input-value.tsx
new file mode 100644
index 00000000..b8973334
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-input-value/category.use-input-value.tsx
@@ -0,0 +1,29 @@
+import { TextCursorInputIcon } from "lucide-react";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { PreviewUseInputValue } from "#/src/ui/cuicui/hooks/use-input-value/preview-use-input-value";
+export const useInputValueCategory: SingleComponentCategoryType = {
+ slug: "use-input-value",
+ name: "Use Input Value",
+ description: "A hook that allows you to manage the value of an input field",
+ releaseDateCategory: new Date("2024-09-28"),
+ icon: TextCursorInputIcon,
+ previewCategory: {
+ component: ,
+ previewScale: 0.8,
+ },
+ component: {
+ lastUpdatedDateComponent: new Date("2024-09-28"),
+ sizePreview: "sm",
+
+ isIframed: false,
+
+ variantList: [
+ {
+ name: "variant 1",
+ component: ,
+ slugComponentFile: "use-input-value",
+ slugPreviewFile: "preview-use-input-value",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-input-value/preview-use-input-value.tsx b/src/ui/cuicui/hooks/use-input-value/preview-use-input-value.tsx
new file mode 100644
index 00000000..18089603
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-input-value/preview-use-input-value.tsx
@@ -0,0 +1,8 @@
+"use client";
+import { useInputValue } from "#/src/ui/cuicui/hooks/use-input-value/use-input-value";
+
+export function PreviewUseInputValue() {
+ const name = useInputValue("Cuicui");
+
+ return ;
+}
diff --git a/src/ui/cuicui/hooks/use-input-value/use-input-value.ts b/src/ui/cuicui/hooks/use-input-value/use-input-value.ts
new file mode 100644
index 00000000..8d996d38
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-input-value/use-input-value.ts
@@ -0,0 +1,15 @@
+"use client";
+import { type ChangeEvent, useState } from "react";
+
+export function useInputValue(initialValue: string) {
+ const [value, setValue] = useState(initialValue);
+
+ function onChange(event: ChangeEvent) {
+ setValue(event.currentTarget.value);
+ }
+
+ return {
+ value,
+ onChange,
+ };
+}
diff --git a/src/ui/cuicui/hooks/use-isomorphic-layout-effect/category.use-isomorphic-layout-effect.tsx b/src/ui/cuicui/hooks/use-isomorphic-layout-effect/category.use-isomorphic-layout-effect.tsx
new file mode 100644
index 00000000..26a4a9bf
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-isomorphic-layout-effect/category.use-isomorphic-layout-effect.tsx
@@ -0,0 +1,29 @@
+import { ServerCogIcon } from "lucide-react";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { PreviewUseIsomorphicLayoutEffect } from "#/src/ui/cuicui/hooks/use-isomorphic-layout-effect/preview-use-isomorphic-layout-effect";
+
+export const useIsomorphicLayoutEffectCategory: SingleComponentCategoryType = {
+ slug: "use-isomorphic-layout-effect",
+ name: "Use Isomorphic Layout Effect",
+ description:
+ "A hook that allows you to use the isomorphic version of useLayoutEffect, ensuring compatibility with server-side rendering.",
+ releaseDateCategory: new Date("2024-09-28"),
+ icon: ServerCogIcon,
+ previewCategory: {
+ component: ,
+ previewScale: 0.8,
+ },
+ component: {
+ lastUpdatedDateComponent: new Date("2024-09-28"),
+ sizePreview: "sm",
+ isIframed: false,
+ variantList: [
+ {
+ name: "default",
+ component: ,
+ slugComponentFile: "use-isomorphic-layout-effect",
+ slugPreviewFile: "preview-use-isomorphic-layout-effect",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-isomorphic-layout-effect/preview-use-isomorphic-layout-effect.tsx b/src/ui/cuicui/hooks/use-isomorphic-layout-effect/preview-use-isomorphic-layout-effect.tsx
new file mode 100644
index 00000000..8af93ad5
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-isomorphic-layout-effect/preview-use-isomorphic-layout-effect.tsx
@@ -0,0 +1,11 @@
+export function PreviewUseIsomorphicLayoutEffect() {
+ return (
+ <>
+ No preview, check the code to understand how it works
+
+ The useIsomorphicLayoutEffect hook is a custom hook that allows you to
+ use the useEffect hook in the server and client side.
+
+ >
+ );
+}
diff --git a/src/ui/cuicui/hooks/use-isomorphic-layout-effect/use-isomorphic-layout-effect.ts b/src/ui/cuicui/hooks/use-isomorphic-layout-effect/use-isomorphic-layout-effect.ts
new file mode 100644
index 00000000..ea6a0bc0
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-isomorphic-layout-effect/use-isomorphic-layout-effect.ts
@@ -0,0 +1,4 @@
+import { useEffect, useLayoutEffect } from "react";
+
+export const useIsomorphicLayoutEffect =
+ typeof window !== "undefined" ? useLayoutEffect : useEffect;
diff --git a/src/ui/cuicui/hooks/use-key-press/category.use-key-press.tsx b/src/ui/cuicui/hooks/use-key-press/category.use-key-press.tsx
new file mode 100644
index 00000000..4dd149ef
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-key-press/category.use-key-press.tsx
@@ -0,0 +1,29 @@
+import { KeyboardIcon } from "lucide-react";
+
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { PreviewUseKeyPress } from "#/src/ui/cuicui/hooks/use-key-press/preview-use-key-press";
+export const useKeyPressCategory: SingleComponentCategoryType = {
+ slug: "use-key-press",
+ name: "Use Key Press",
+ description:
+ "A hook that allows you to know if an element is in the viewport",
+ releaseDateCategory: new Date("2024-09-28"),
+ icon: KeyboardIcon,
+ previewCategory: {
+ component: ,
+ previewScale: 0.8,
+ },
+ component: {
+ lastUpdatedDateComponent: new Date("2024-09-28"),
+ sizePreview: "sm",
+ isIframed: false,
+ variantList: [
+ {
+ name: "variant 1",
+ component: ,
+ slugComponentFile: "use-key-press",
+ slugPreviewFile: "preview-use-key-press",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-key-press/preview-use-key-press.tsx b/src/ui/cuicui/hooks/use-key-press/preview-use-key-press.tsx
new file mode 100644
index 00000000..e5dc34a3
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-key-press/preview-use-key-press.tsx
@@ -0,0 +1,37 @@
+"use client";
+import { useKeyPress } from "#/src/ui/cuicui/hooks/use-key-press/use-key-press";
+import { useState } from "react";
+
+export const PreviewUseKeyPress = () => {
+ // Call our hook for each key that we'd like to monitor
+ const [isAbcKeyPressed, setIsAbcKeyPressed] = useState(false);
+
+ useKeyPress({
+ keyPressItems: [
+ {
+ keys: ["KeyA", "Meta"],
+ event: () => setIsAbcKeyPressed((prev) => !prev),
+ },
+ {
+ keys: ["KeyA", "Control"],
+ event: () => setIsAbcKeyPressed((prev) => !prev),
+ },
+ ],
+ });
+
+ return (
+
+
+
+ "cmd + a"
+ {" "}
+ or{" "}
+ "ctrl + a"
+ {isAbcKeyPressed ? " to hide" : " to show"}
+
+ {isAbcKeyPressed && (
+
+ )}
+
+ );
+};
diff --git a/src/ui/cuicui/hooks/use-key-press/use-key-press.ts b/src/ui/cuicui/hooks/use-key-press/use-key-press.ts
new file mode 100644
index 00000000..0ff6f976
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-key-press/use-key-press.ts
@@ -0,0 +1,178 @@
+import { useEffect } from "react";
+
+const Keys = {
+ Backspace: "Backspace",
+ Tab: "Tab",
+ Enter: "Enter",
+ Shift: "Shift",
+ Control: "Control",
+ Alt: "Alt",
+ Pause: "Pause",
+ CapsLock: "CapsLock",
+ Escape: "Escape",
+ Space: " ",
+ PageUp: "PageUp",
+ PageDown: "PageDown",
+ End: "End",
+ Home: "Home",
+ LeftArrow: "ArrowLeft",
+ UpArrow: "ArrowUp",
+ RightArrow: "ArrowRight",
+ DownArrow: "ArrowDown",
+ Insert: "Insert",
+ Delete: "Delete",
+ Meta: "Meta",
+ Key0: "0",
+ Key1: "1",
+ Key2: "2",
+ Key3: "3",
+ Key4: "4",
+ Key5: "5",
+ Key6: "6",
+ Key7: "7",
+ Key8: "8",
+ Key9: "9",
+ KeyA: "a",
+ KeyB: "b",
+ KeyC: "c",
+ KeyD: "d",
+ KeyE: "e",
+ KeyF: "f",
+ KeyG: "g",
+ KeyH: "h",
+ KeyI: "i",
+ KeyJ: "j",
+ KeyK: "k",
+ KeyL: "l",
+ KeyM: "m",
+ KeyN: "n",
+ KeyO: "o",
+ KeyP: "p",
+ KeyQ: "q",
+ KeyR: "r",
+ KeyS: "s",
+ KeyT: "t",
+ KeyU: "u",
+ KeyV: "v",
+ KeyW: "w",
+ KeyX: "x",
+ KeyY: "y",
+ KeyZ: "z",
+ LeftMeta: "Meta",
+ RightMeta: "Meta",
+ Select: "Select",
+ Numpad0: "0",
+ Numpad1: "1",
+ Numpad2: "2",
+ Numpad3: "3",
+ Numpad4: "4",
+ Numpad5: "5",
+ Numpad6: "6",
+ Numpad7: "7",
+ Numpad8: "8",
+ Numpad9: "9",
+ Multiply: "*",
+ Add: "+",
+ Subtract: "-",
+ Decimal: ".",
+ Divide: "/",
+ F1: "F1",
+ F2: "F2",
+ F3: "F3",
+ F4: "F4",
+ F5: "F5",
+ F6: "F6",
+ F7: "F7",
+ F8: "F8",
+ F9: "F9",
+ F10: "F10",
+ F11: "F11",
+ F12: "F12",
+ NumLock: "NumLock",
+ ScrollLock: "ScrollLock",
+ Semicolon: ";",
+ Equals: "=",
+ Comma: ",",
+ Dash: "-",
+ Period: ".",
+ ForwardSlash: "/",
+ GraveAccent: "`",
+ OpenBracket: "[",
+ BackSlash: "\\",
+ CloseBracket: "]",
+ Quote: "'",
+};
+
+export interface KeyPressItem {
+ keys: Array;
+ event: (event: KeyboardEvent) => void;
+ preventDefault?: boolean;
+}
+
+function checkCombination(
+ event: KeyboardEvent,
+ keys: Array,
+): boolean {
+ return keys.every((key) => {
+ if (key === "Meta") {
+ return event.metaKey;
+ }
+ if (key === "Control") {
+ return event.ctrlKey;
+ }
+ if (key === "Shift") {
+ return event.shiftKey;
+ }
+ if (key === "Alt") {
+ return event.altKey;
+ }
+ return event.key === Keys[key];
+ });
+}
+
+export function useKeyPress({
+ keyPressItems,
+ tagsToIgnore = ["INPUT", "TEXTAREA", "SELECT"],
+ triggerOnContentEditable = false,
+}: {
+ keyPressItems: KeyPressItem[];
+ tagsToIgnore?: string[];
+ triggerOnContentEditable?: boolean;
+}) {
+ useEffect(() => {
+ const keydownListener = (event: KeyboardEvent) => {
+ for (const keyPressItem of keyPressItems) {
+ const {
+ keys,
+ event: triggerEvent,
+ preventDefault = true,
+ } = keyPressItem;
+ if (
+ checkCombination(event, keys) &&
+ shouldFireEvent(event, tagsToIgnore, triggerOnContentEditable)
+ ) {
+ if (preventDefault) {
+ event.preventDefault();
+ }
+
+ triggerEvent(event);
+ }
+ }
+ };
+
+ document.addEventListener("keydown", keydownListener);
+ return () => document.removeEventListener("keydown", keydownListener);
+ }, [keyPressItems, tagsToIgnore, triggerOnContentEditable]);
+}
+
+function shouldFireEvent(
+ event: KeyboardEvent,
+ tagsToIgnore: string[],
+ triggerOnContentEditable: boolean,
+): boolean {
+ const target = event.target as HTMLElement;
+ return !(
+ (target.isContentEditable && !triggerOnContentEditable) ||
+ tagsToIgnore.includes(target.tagName)
+ );
+}
diff --git a/src/ui/cuicui/hooks/use-konami-code/category.use-konami-code.tsx b/src/ui/cuicui/hooks/use-konami-code/category.use-konami-code.tsx
new file mode 100644
index 00000000..530804c6
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-konami-code/category.use-konami-code.tsx
@@ -0,0 +1,31 @@
+import { GamepadIcon } from "lucide-react";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { PreviewUseKonamiCode } from "#/src/ui/cuicui/hooks/use-konami-code/preview-use-konami-code";
+
+export const useKonamiCodeCategory: SingleComponentCategoryType = {
+ slug: "use-konami-code",
+ name: "Use Konami Code",
+ description:
+ "A hook that detects the Konami Code sequence and triggers actions accordingly",
+ releaseDateCategory: new Date("2024-09-28"),
+ icon: GamepadIcon,
+ previewCategory: {
+ component: ,
+ previewScale: 0.8,
+ },
+ component: {
+ lastUpdatedDateComponent: new Date("2024-09-28"),
+ sizePreview: "sm",
+
+ isIframed: false,
+
+ variantList: [
+ {
+ name: "default variant",
+ component: ,
+ slugComponentFile: "use-konami-code",
+ slugPreviewFile: "preview-use-konami-code",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-konami-code/preview-use-konami-code.tsx b/src/ui/cuicui/hooks/use-konami-code/preview-use-konami-code.tsx
new file mode 100644
index 00000000..86610680
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-konami-code/preview-use-konami-code.tsx
@@ -0,0 +1,44 @@
+"use client";
+import useKonamiCode from "#/src/ui/cuicui/hooks/use-konami-code/use-konami-code";
+import { useState } from "react";
+
+export const PreviewUseKonamiCode = () => {
+ const [isKonamiCodeActivated, setIsKonamiCodeActivated] = useState(false);
+
+ // const konamiSequenceByCode = [
+ // "ArrowUp",
+ // "ArrowDown",
+ // "ArrowLeft",
+ // "ArrowRight",
+ // "KeyB",
+ // "KeyA",
+ // ];
+
+ const konamiSequenceByKey = [
+ "ArrowUp",
+ "ArrowDown",
+ "ArrowLeft",
+ "ArrowRight",
+ "b",
+ "a",
+ ];
+
+ const handleKonamiCode = () => {
+ setIsKonamiCodeActivated(true);
+ };
+
+ useKonamiCode(konamiSequenceByKey, handleKonamiCode, {
+ matchMode: "key",
+ });
+
+ return (
+
+
Welcome to the Konami Code Demo
+
Try entering the Konami Code using your keyboard!
+
+ The Konami Code is: ↑ ↓ ← → b a
+
+ {isKonamiCodeActivated &&
Konami Code activated!
}
+
+ );
+};
diff --git a/src/ui/cuicui/hooks/use-konami-code/use-konami-code.ts b/src/ui/cuicui/hooks/use-konami-code/use-konami-code.ts
new file mode 100644
index 00000000..57a8dbd9
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-konami-code/use-konami-code.ts
@@ -0,0 +1,108 @@
+"use client";
+
+import { useEffect, useState, useCallback, useMemo } from "react";
+
+type KonamiCallback = () => void;
+
+type MatchMode = "code" | "key" | "codeOrKey";
+
+interface UseKonamiCodeOptions {
+ /**
+ * Determines how key presses are matched against the sequence.
+ * - "code": Matches using `KeyboardEvent.code`.
+ * - "key": Matches using `KeyboardEvent.key`.
+ * - "codeOrKey": Matches if either `code` or `key` matches.
+ */
+ matchMode?: MatchMode;
+ /**
+ * Specifies whether the sequence should be case-insensitive.
+ * Applicable only when `matchMode` is "key" or "codeOrKey".
+ */
+ caseInsensitive?: boolean;
+}
+
+/**
+ * Custom hook to detect a specific key sequence.
+ *
+ * @param sequence - An array of strings representing the desired key sequence.
+ * @param callback - A function to be executed when the key sequence is successfully entered.
+ * @param options - Optional configuration for matching mode and case sensitivity.
+ * @returns The current sequence of keys pressed.
+ */
+const useKonamiCode = (
+ sequence: string[],
+ callback: KonamiCallback,
+ options?: UseKonamiCodeOptions,
+): string[] => {
+ const { matchMode = "code", caseInsensitive = false } = options || {};
+ const [currentSequence, setCurrentSequence] = useState([]);
+
+ // Memoize the target sequence based on matchMode and case sensitivity
+ const targetSequence = useMemo(() => {
+ if (matchMode === "key" || matchMode === "codeOrKey") {
+ return caseInsensitive
+ ? sequence.map((key) => key.toLowerCase())
+ : sequence;
+ }
+ return sequence;
+ }, [sequence, matchMode, caseInsensitive]);
+
+ // Handler for keydown events
+ const handleKeyDown = useCallback(
+ (event: KeyboardEvent) => {
+ let identifier: string[] = [];
+
+ if (matchMode === "code") {
+ identifier = [event.code];
+ } else if (matchMode === "key") {
+ identifier = [caseInsensitive ? event.key.toLowerCase() : event.key];
+ } else if (matchMode === "codeOrKey") {
+ identifier = [
+ event.code,
+ caseInsensitive ? event.key.toLowerCase() : event.key,
+ ];
+ }
+
+ setCurrentSequence((prevSequence) => {
+ let updatedSequence = [...prevSequence];
+
+ for (const id of identifier) {
+ updatedSequence = [...updatedSequence, id];
+
+ // Keep the sequence length within the target sequence length
+ if (updatedSequence.length > targetSequence.length) {
+ updatedSequence.shift();
+ }
+
+ // Check if the updated sequence matches the target sequence
+ const isMatch =
+ updatedSequence.length === targetSequence.length &&
+ targetSequence.every(
+ (target, index) => target === updatedSequence[index],
+ );
+
+ if (isMatch) {
+ callback();
+ updatedSequence = []; // Reset the sequence after successful entry
+ }
+ }
+
+ return updatedSequence;
+ });
+ },
+ [matchMode, caseInsensitive, targetSequence, callback],
+ );
+
+ // Effect to add and clean up the event listener
+ useEffect(() => {
+ window.addEventListener("keydown", handleKeyDown);
+
+ return () => {
+ window.removeEventListener("keydown", handleKeyDown);
+ };
+ }, [handleKeyDown]);
+
+ return currentSequence;
+};
+
+export default useKonamiCode;
diff --git a/src/ui/cuicui/hooks/use-local-storage/category.use-local-storage.tsx b/src/ui/cuicui/hooks/use-local-storage/category.use-local-storage.tsx
new file mode 100644
index 00000000..7a37925b
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-local-storage/category.use-local-storage.tsx
@@ -0,0 +1,29 @@
+import { DatabaseIcon } from "lucide-react";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { PreviewUseLocalStorage } from "#/src/ui/cuicui/hooks/use-local-storage/preview-use-local-storage";
+
+export const useLocalStorageCategory: SingleComponentCategoryType = {
+ slug: "use-local-storage",
+ name: "Use Local Storage",
+ description:
+ "A hook that allows you to manage and persist state using localStorage",
+ releaseDateCategory: new Date("2024-09-28"),
+ icon: DatabaseIcon,
+ previewCategory: {
+ component: ,
+ previewScale: 0.8,
+ },
+ component: {
+ lastUpdatedDateComponent: new Date("2024-09-28"),
+ sizePreview: "sm",
+ isIframed: false,
+ variantList: [
+ {
+ name: "Default Variant",
+ component: ,
+ slugComponentFile: "use-local-storage",
+ slugPreviewFile: "preview-use-local-storage",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-local-storage/preview-use-local-storage.tsx b/src/ui/cuicui/hooks/use-local-storage/preview-use-local-storage.tsx
new file mode 100644
index 00000000..00f15888
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-local-storage/preview-use-local-storage.tsx
@@ -0,0 +1,36 @@
+"use client";
+import { useLocalStorage } from "#/src/ui/cuicui/hooks/use-local-storage/use-local-storage";
+
+export function PreviewUseLocalStorage() {
+ const [value, setValue, removeValue] = useLocalStorage("test-key", 0);
+
+ return (
+
+
Count: {value}
+
+
+
+
+ );
+}
diff --git a/src/ui/cuicui/hooks/use-local-storage/use-local-storage.ts b/src/ui/cuicui/hooks/use-local-storage/use-local-storage.ts
new file mode 100644
index 00000000..0215e7dc
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-local-storage/use-local-storage.ts
@@ -0,0 +1,167 @@
+import { useEventCallback } from "#/src/ui/cuicui/hooks/use-event-callback/use-event-callback";
+import { useEventListener } from "#/src/ui/cuicui/hooks/use-event-listener/use-event-listener";
+import { useCallback, useEffect, useState } from "react";
+
+import type { Dispatch, SetStateAction } from "react";
+
+declare global {
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
+ interface WindowEventMap {
+ "local-storage": CustomEvent;
+ }
+}
+
+type UseLocalStorageOptions = {
+ serializer?: (value: T) => string;
+ deserializer?: (value: string) => T;
+ initializeWithValue?: boolean;
+};
+
+const IS_SERVER = typeof window === "undefined";
+
+export function useLocalStorage(
+ key: string,
+ initialValue: T | (() => T),
+ options: UseLocalStorageOptions = {},
+): [T, Dispatch>, () => void] {
+ const { initializeWithValue = true } = options;
+
+ const serializer = useCallback<(value: T) => string>(
+ (value) => {
+ if (options.serializer) {
+ return options.serializer(value);
+ }
+
+ return JSON.stringify(value);
+ },
+ [options],
+ );
+
+ const deserializer = useCallback<(value: string) => T>(
+ (value) => {
+ if (options.deserializer) {
+ return options.deserializer(value);
+ }
+ // Support 'undefined' as a value
+ if (value === "undefined") {
+ return undefined as unknown as T;
+ }
+
+ const defaultValue =
+ initialValue instanceof Function ? initialValue() : initialValue;
+
+ let parsed: unknown;
+ try {
+ parsed = JSON.parse(value);
+ } catch (error) {
+ console.error("Error parsing JSON:", error);
+ return defaultValue; // Return initialValue if parsing fails
+ }
+
+ return parsed as T;
+ },
+ [options, initialValue],
+ );
+
+ // Get from local storage then
+ // parse stored json or return initialValue
+ const readValue = useCallback((): T => {
+ const initialValueToUse =
+ initialValue instanceof Function ? initialValue() : initialValue;
+
+ // Prevent build error "window is undefined" but keep working
+ if (IS_SERVER) {
+ return initialValueToUse;
+ }
+
+ try {
+ const raw = window.localStorage.getItem(key);
+ return raw ? deserializer(raw) : initialValueToUse;
+ } catch (error) {
+ console.warn(`Error reading localStorage key “${key}”:`, error);
+ return initialValueToUse;
+ }
+ }, [initialValue, key, deserializer]);
+
+ const [storedValue, setStoredValue] = useState(() => {
+ if (initializeWithValue) {
+ return readValue();
+ }
+
+ return initialValue instanceof Function ? initialValue() : initialValue;
+ });
+
+ // Return a wrapped version of useState's setter function that ...
+ // ... persists the new value to localStorage.
+ const setValue: Dispatch> = useEventCallback((value) => {
+ // Prevent build error "window is undefined" but keeps working
+ if (IS_SERVER) {
+ console.warn(
+ `Tried setting localStorage key “${key}” even though environment is not a client`,
+ );
+ }
+
+ try {
+ // Allow value to be a function so we have the same API as useState
+ const newValue = value instanceof Function ? value(readValue()) : value;
+
+ // Save to local storage
+ window.localStorage.setItem(key, serializer(newValue));
+
+ // Save state
+ setStoredValue(newValue);
+
+ // We dispatch a custom event so every similar useLocalStorage hook is notified
+ window.dispatchEvent(new StorageEvent("local-storage", { key }));
+ } catch (error) {
+ console.warn(`Error setting localStorage key “${key}”:`, error);
+ }
+ });
+
+ const removeValue = useEventCallback(() => {
+ // Prevent build error "window is undefined" but keeps working
+ if (IS_SERVER) {
+ console.warn(
+ `Tried removing localStorage key “${key}” even though environment is not a client`,
+ );
+ }
+
+ const defaultValue =
+ initialValue instanceof Function ? initialValue() : initialValue;
+
+ // Remove the key from local storage
+ window.localStorage.removeItem(key);
+
+ // Save state with default value
+ setStoredValue(defaultValue);
+
+ // We dispatch a custom event so every similar useLocalStorage hook is notified
+ window.dispatchEvent(new StorageEvent("local-storage", { key }));
+ });
+
+ //!! To improve to not ignore the exhaustive-deps
+ // biome-ignore lint/correctness/useExhaustiveDependencies:
+ useEffect(() => {
+ setStoredValue(readValue());
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [key]);
+
+ const handleStorageChange = useCallback(
+ (event: StorageEvent | CustomEvent) => {
+ if ((event as StorageEvent).key && (event as StorageEvent).key !== key) {
+ return;
+ }
+ setStoredValue(readValue());
+ },
+ [key, readValue],
+ );
+
+ // this only works for other documents, not the current one
+ useEventListener("storage", handleStorageChange);
+
+ // this is a custom event, triggered in writeValueToLocalStorage
+ // See: useLocalStorage()
+ useEventListener("local-storage", handleStorageChange);
+
+ return [storedValue, setValue, removeValue];
+}
diff --git a/src/ui/cuicui/hooks/use-location/category.use-location.tsx b/src/ui/cuicui/hooks/use-location/category.use-location.tsx
new file mode 100644
index 00000000..98a53bad
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-location/category.use-location.tsx
@@ -0,0 +1,29 @@
+import { MapIcon } from "lucide-react";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { PreviewUseLocation } from "#/src/ui/cuicui/hooks/use-location/preview-use-location";
+
+export const useLocationCategory: SingleComponentCategoryType = {
+ slug: "use-location",
+ name: "Use Location",
+ description:
+ "A hook that allows you to access and manage the current location in your application",
+ releaseDateCategory: new Date("2024-09-28"),
+ icon: MapIcon,
+ previewCategory: {
+ component: ,
+ previewScale: 0.8,
+ },
+ component: {
+ lastUpdatedDateComponent: new Date("2024-09-28"),
+ sizePreview: "xl",
+ isIframed: false,
+ variantList: [
+ {
+ name: "variant 1",
+ component: ,
+ slugComponentFile: "use-location",
+ slugPreviewFile: "preview-use-location",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-location/preview-use-location.tsx b/src/ui/cuicui/hooks/use-location/preview-use-location.tsx
new file mode 100644
index 00000000..c0f15149
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-location/preview-use-location.tsx
@@ -0,0 +1,8 @@
+"use client";
+import useLocation from "#/src/ui/cuicui/hooks/use-location/use-location";
+
+export function PreviewUseLocation() {
+ const location = useLocation();
+
+ return {JSON.stringify(location, null, 2)}
;
+}
diff --git a/src/ui/cuicui/hooks/use-location/use-location.ts b/src/ui/cuicui/hooks/use-location/use-location.ts
new file mode 100644
index 00000000..03d554ed
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-location/use-location.ts
@@ -0,0 +1,147 @@
+"use client";
+import { useState, useEffect } from "react";
+
+const isClient = typeof window === "object";
+
+type HistoryMethod = "pushState" | "replaceState";
+
+declare global {
+ interface WindowEventMap {
+ pushstate: CustomEvent<{ state: unknown }>;
+ replacestate: CustomEvent<{ state: unknown }>;
+ }
+}
+
+const on = (
+ obj: Window,
+ type: keyof WindowEventMap,
+ listener: (event: Event) => void,
+) => obj.addEventListener(type, listener);
+
+const off = (
+ obj: Window,
+ type: keyof WindowEventMap,
+ listener: (event: Event) => void,
+) => obj.removeEventListener(type, listener);
+
+const patchHistoryMethod = (method: HistoryMethod) => {
+ const original = history[method];
+
+ history[method] = function (
+ this: History,
+ data: unknown,
+ title: string,
+ url?: string | null,
+ ) {
+ const result = original.apply(this, [data, title, url]);
+ const event = new CustomEvent<{ state: unknown }>(method.toLowerCase(), {
+ detail: { state: data },
+ });
+ window.dispatchEvent(event);
+ return result;
+ };
+};
+
+if (isClient) {
+ patchHistoryMethod("pushState");
+ patchHistoryMethod("replaceState");
+}
+
+interface LocationState {
+ trigger: string;
+ state: unknown;
+ length: number;
+ hash: string;
+ host: string;
+ hostname: string;
+ href: string;
+ origin: string;
+ pathname: string;
+ port: string;
+ protocol: string;
+ search: string;
+}
+
+const defaultLocationState: LocationState = {
+ trigger: "load",
+ state: null,
+ length: 1,
+ hash: "",
+ host: "",
+ hostname: "",
+ href: "",
+ origin: "",
+ pathname: "",
+ port: "",
+ protocol: "",
+ search: "",
+};
+
+const useLocation = (): LocationState => {
+ const buildState = (trigger: string): LocationState => {
+ if (!isClient) {
+ return defaultLocationState;
+ }
+
+ const { state, length } = history;
+ const {
+ hash,
+ host,
+ hostname,
+ href,
+ origin,
+ pathname,
+ port,
+ protocol,
+ search,
+ } = window.location;
+
+ return {
+ trigger,
+ state,
+ length,
+ hash,
+ host,
+ hostname,
+ href,
+ origin,
+ pathname,
+ port,
+ protocol,
+ search,
+ };
+ };
+
+ const [locationState, setLocationState] = useState(
+ buildState("load"),
+ );
+
+ // !! Should try to use the `useEffect` hook with an exhaustive dependency array
+ // biome-ignore lint/correctness/useExhaustiveDependencies:
+ useEffect(() => {
+ if (!isClient) {
+ return;
+ }
+ const onChange = (trigger: string) => {
+ setLocationState(buildState(trigger));
+ };
+
+ const handlePopState = () => onChange("popstate");
+ const handlePushState = () => onChange("pushstate");
+ const handleReplaceState = () => onChange("replacestate");
+
+ on(window, "popstate", handlePopState);
+ on(window, "pushstate", handlePushState);
+ on(window, "replacestate", handleReplaceState);
+
+ return () => {
+ off(window, "popstate", handlePopState);
+ off(window, "pushstate", handlePushState);
+ off(window, "replacestate", handleReplaceState);
+ };
+ }, []);
+
+ return locationState;
+};
+
+export default useLocation;
diff --git a/src/ui/cuicui/hooks/use-mouse/use-mouse.category.tsx b/src/ui/cuicui/hooks/use-mouse/category.use-mouse.tsx
similarity index 59%
rename from src/ui/cuicui/hooks/use-mouse/use-mouse.category.tsx
rename to src/ui/cuicui/hooks/use-mouse/category.use-mouse.tsx
index 5c3e5d75..a611c797 100644
--- a/src/ui/cuicui/hooks/use-mouse/use-mouse.category.tsx
+++ b/src/ui/cuicui/hooks/use-mouse/category.use-mouse.tsx
@@ -1,12 +1,12 @@
import { MousePointer2Icon } from "lucide-react";
-import type { CategoryType } from "#/src/lib/types/component";
-import { useMouseComponent } from "#/src/ui/cuicui/hooks/use-mouse/hook/use-mouse.component";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { useMouseComponent } from "#/src/ui/cuicui/hooks/use-mouse/component.use-mouse";
-export const useMouseCategory: CategoryType = {
+export const useMouseCategory: SingleComponentCategoryType = {
slug: "use-mouse",
name: "Use Mouse",
icon: MousePointer2Icon,
releaseDateCategory: new Date("2024-09-24"),
description: "A simple hook to get the mouse position.",
- componentList: [useMouseComponent],
+ component: useMouseComponent,
};
diff --git a/src/ui/cuicui/hooks/use-mouse/hook/use-mouse.component.tsx b/src/ui/cuicui/hooks/use-mouse/component.use-mouse.tsx
similarity index 83%
rename from src/ui/cuicui/hooks/use-mouse/hook/use-mouse.component.tsx
rename to src/ui/cuicui/hooks/use-mouse/component.use-mouse.tsx
index cf16f667..b3c75199 100644
--- a/src/ui/cuicui/hooks/use-mouse/hook/use-mouse.component.tsx
+++ b/src/ui/cuicui/hooks/use-mouse/component.use-mouse.tsx
@@ -1,5 +1,5 @@
import type { ComponentType } from "#/src/lib/types/component";
-import PreviewUseMouse from "#/src/ui/cuicui/hooks/use-mouse/hook/preview-use-mouse";
+import PreviewUseMouse from "#/src/ui/cuicui/hooks/use-mouse/preview-use-mouse";
export const useMouseComponent: ComponentType = {
sizePreview: "xs",
diff --git a/src/ui/cuicui/hooks/use-mouse/hook/preview-use-mouse.tsx b/src/ui/cuicui/hooks/use-mouse/preview-use-mouse.tsx
similarity index 89%
rename from src/ui/cuicui/hooks/use-mouse/hook/preview-use-mouse.tsx
rename to src/ui/cuicui/hooks/use-mouse/preview-use-mouse.tsx
index dbd7b3f2..be9cfadf 100644
--- a/src/ui/cuicui/hooks/use-mouse/hook/preview-use-mouse.tsx
+++ b/src/ui/cuicui/hooks/use-mouse/preview-use-mouse.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useMouse } from "#/src/ui/cuicui/hooks/use-mouse/hook/use-mouse";
+import { useMouse } from "#/src/ui/cuicui/hooks/use-mouse/use-mouse";
export default function PreviewUseMouse() {
const [mouse, parentRef] = useMouse();
diff --git a/src/ui/cuicui/hooks/use-mouse/hook/use-mouse.tsx b/src/ui/cuicui/hooks/use-mouse/use-mouse.ts
similarity index 100%
rename from src/ui/cuicui/hooks/use-mouse/hook/use-mouse.tsx
rename to src/ui/cuicui/hooks/use-mouse/use-mouse.ts
diff --git a/src/ui/cuicui/hooks/use-network-status/category.use-network.tsx b/src/ui/cuicui/hooks/use-network-status/category.use-network.tsx
new file mode 100644
index 00000000..58450b91
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-network-status/category.use-network.tsx
@@ -0,0 +1,29 @@
+import { NetworkIcon } from "lucide-react";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import PreviewUseNetworkStatus from "#/src/ui/cuicui/hooks/use-network-status/preview-use-network-status";
+
+export const useNetworkCategory: SingleComponentCategoryType = {
+ slug: "use-network-status",
+ name: "Use Network Status",
+ description: "A hook to get the network status",
+ releaseDateCategory: new Date("2024-09-16"),
+ icon: NetworkIcon,
+ previewCategory: {
+ component: ,
+ previewScale: 1,
+ },
+ component: {
+ rerenderButton: true,
+ lastUpdatedDateComponent: new Date("2024-09-16"),
+ sizePreview: "lg",
+ isIframed: false,
+ variantList: [
+ {
+ name: "variant 1",
+ component: ,
+ slugComponentFile: "use-network-status",
+ slugPreviewFile: "preview-use-network-status",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-network-status/hook/preview-use-network-status.tsx b/src/ui/cuicui/hooks/use-network-status/preview-use-network-status.tsx
similarity index 93%
rename from src/ui/cuicui/hooks/use-network-status/hook/preview-use-network-status.tsx
rename to src/ui/cuicui/hooks/use-network-status/preview-use-network-status.tsx
index 7b14c50f..32b86683 100644
--- a/src/ui/cuicui/hooks/use-network-status/hook/preview-use-network-status.tsx
+++ b/src/ui/cuicui/hooks/use-network-status/preview-use-network-status.tsx
@@ -3,7 +3,7 @@ import { Fragment, useEffect, useState } from "react";
import {
type NetworkState,
useNetworkStatus,
-} from "#/src/ui/cuicui/hooks/use-network-status/hook/use-network-status";
+} from "#/src/ui/cuicui/hooks/use-network-status/use-network-status";
export default function PreviewUseNetworkStatus() {
const [isMounted, setIsMounted] = useState(false);
diff --git a/src/ui/cuicui/hooks/use-network-status/hook/use-network-status.tsx b/src/ui/cuicui/hooks/use-network-status/use-network-status.ts
similarity index 100%
rename from src/ui/cuicui/hooks/use-network-status/hook/use-network-status.tsx
rename to src/ui/cuicui/hooks/use-network-status/use-network-status.ts
diff --git a/src/ui/cuicui/hooks/use-network-status/use-network.category.tsx b/src/ui/cuicui/hooks/use-network-status/use-network.category.tsx
deleted file mode 100644
index d73289eb..00000000
--- a/src/ui/cuicui/hooks/use-network-status/use-network.category.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { NetworkIcon } from "lucide-react";
-import type { CategoryType } from "#/src/lib/types/component";
-import PreviewUseNetworkStatus from "#/src/ui/cuicui/hooks/use-network-status/hook/preview-use-network-status";
-
-export const useNetworkCategory: CategoryType = {
- slug: "use-network-status",
- name: "Use Network Status",
- description: "A hook to get the network status",
- releaseDateCategory: new Date("2024-09-16"),
- icon: NetworkIcon,
- previewCategory: {
- component: ,
- previewScale: 1,
- },
- componentList: [
- {
- rerenderButton: true,
- lastUpdatedDateComponent: new Date("2024-09-16"),
- sizePreview: "lg",
- slug: "hook",
- isIframed: false,
- title: "Use Network Status",
- description: "A hook to get the network status",
- variantList: [
- {
- name: "variant 1",
- component: ,
- slugComponentFile: "use-network-status",
- slugPreviewFile: "preview-use-network-status",
- },
- ],
- },
- ],
-};
diff --git a/src/ui/cuicui/hooks/use-online-status/category.use-online-status.tsx b/src/ui/cuicui/hooks/use-online-status/category.use-online-status.tsx
new file mode 100644
index 00000000..721eae60
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-online-status/category.use-online-status.tsx
@@ -0,0 +1,29 @@
+import { WifiIcon } from "lucide-react";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { PreviewUseOnlineStatus } from "#/src/ui/cuicui/hooks/use-online-status/preview-use-online-status";
+
+export const useOnlineStatusCategory: SingleComponentCategoryType = {
+ slug: "use-online-status",
+ name: "Use Online Status",
+ description:
+ "A hook that allows you to monitor the online/offline status of the user's device",
+ releaseDateCategory: new Date("2024-09-28"),
+ icon: WifiIcon,
+ previewCategory: {
+ component: ,
+ previewScale: 0.8,
+ },
+ component: {
+ lastUpdatedDateComponent: new Date("2024-09-28"),
+ sizePreview: "sm",
+ isIframed: false,
+ variantList: [
+ {
+ name: "default variant",
+ component: ,
+ slugComponentFile: "use-online-status",
+ slugPreviewFile: "preview-use-online-status",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-online-status/preview-use-online-status.tsx b/src/ui/cuicui/hooks/use-online-status/preview-use-online-status.tsx
new file mode 100644
index 00000000..156e3635
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-online-status/preview-use-online-status.tsx
@@ -0,0 +1,11 @@
+"use client";
+import { useOnlineStatus } from "#/src/ui/cuicui/hooks/use-online-status/use-online-status";
+
+export function PreviewUseOnlineStatus() {
+ const onlineStatus = useOnlineStatus();
+ return (
+
+
You are {onlineStatus ? "Online" : "Offline"}
+
+ );
+}
diff --git a/src/ui/cuicui/hooks/use-online-status/use-online-status.ts b/src/ui/cuicui/hooks/use-online-status/use-online-status.ts
new file mode 100644
index 00000000..7aa0da86
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-online-status/use-online-status.ts
@@ -0,0 +1,30 @@
+"use client";
+import { useEffect, useState } from "react";
+
+function getOnlineStatus() {
+ return typeof navigator !== "undefined" &&
+ typeof navigator.onLine === "boolean"
+ ? navigator.onLine
+ : true;
+}
+
+export function useOnlineStatus() {
+ const [onlineStatus, setOnlineStatus] = useState(getOnlineStatus());
+
+ useEffect(() => {
+ function goOnline() {
+ setOnlineStatus(true);
+ }
+ function goOffline() {
+ setOnlineStatus(false);
+ }
+ window.addEventListener("online", goOnline);
+ window.addEventListener("offline", goOffline);
+ return () => {
+ window.removeEventListener("online", goOnline);
+ window.removeEventListener("offline", goOffline);
+ };
+ }, []);
+
+ return onlineStatus;
+}
diff --git a/src/ui/cuicui/hooks/use-session-storage/category.use-session-storage.tsx b/src/ui/cuicui/hooks/use-session-storage/category.use-session-storage.tsx
new file mode 100644
index 00000000..c9f3fd8a
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-session-storage/category.use-session-storage.tsx
@@ -0,0 +1,31 @@
+// Coming soon
+import { ViewIcon } from "lucide-react";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+// import { PreviewUseSessionStorage } from "#/src/ui/cuicui/hooks/use-session-storage/preview-use-session-storage";
+
+export const useSessionStorageCategory: SingleComponentCategoryType = {
+ slug: "use-session-storage",
+ name: "Use Session Storage",
+ description: "A hook that allows you to manage session storage values",
+ releaseDateCategory: new Date("2024-08-28"),
+ icon: ViewIcon,
+ // previewCategory: {
+ // component: ,
+ // previewScale: 0.8,
+ // },
+ comingSoonCategory: true,
+ component: null,
+ // component: {
+ // lastUpdatedDateComponent: new Date("2024-08-28"),
+ // sizePreview: "sm",
+ // isIframed: false,
+ // variantList: [
+ // {
+ // name: "variant 1",
+ // component: ,
+ // slugComponentFile: "use-session-storage",
+ // slugPreviewFile: "preview-use-session-storage",
+ // },
+ // ],
+ // },
+};
diff --git a/src/ui/cuicui/hooks/use-session-storage/preview-use-session-storage.tsx b/src/ui/cuicui/hooks/use-session-storage/preview-use-session-storage.tsx
new file mode 100644
index 00000000..c2b9d7f2
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-session-storage/preview-use-session-storage.tsx
@@ -0,0 +1 @@
+// Coming soon
diff --git a/src/ui/cuicui/hooks/use-session-storage/use-session-storage.ts b/src/ui/cuicui/hooks/use-session-storage/use-session-storage.ts
new file mode 100644
index 00000000..9bb4e5a4
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-session-storage/use-session-storage.ts
@@ -0,0 +1 @@
+// Coming Soon
diff --git a/src/ui/cuicui/hooks/use-speech-to-text/category.use-speech-to-text.tsx b/src/ui/cuicui/hooks/use-speech-to-text/category.use-speech-to-text.tsx
new file mode 100644
index 00000000..a11d551d
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-speech-to-text/category.use-speech-to-text.tsx
@@ -0,0 +1,32 @@
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { AudioLinesIcon } from "lucide-react";
+
+export const useSpeechToTextCategory: SingleComponentCategoryType = {
+ slug: "use-speech-to-text",
+ name: "Use Speech to Text",
+ description: "A hook that allows you to convert speech to text",
+ releaseDateCategory: new Date("2024-09-28"),
+ icon: AudioLinesIcon,
+ // previewCategory: {
+ // component: ,
+ // previewImage: PreviewImageUseTextToSpeech,
+ // previewScale: 0.8,
+ // },
+ comingSoonCategory: true,
+ component: null,
+ // component: {
+ // lastUpdatedDateComponent: new Date("2024-08-28"),
+ // sizePreview: "lg",
+
+ // isIframed: true,
+
+ // variantList: [
+ // {
+ // name: "variant 1",
+ // component: ,
+ // slugComponentFile: "use-text-to-speech",
+ // slugPreviewFile: "preview-text-to-speech",
+ // },
+ // ],
+ // },
+};
diff --git a/src/ui/cuicui/hooks/use-speech-to-text/preview-use-speech-to-text.tsx b/src/ui/cuicui/hooks/use-speech-to-text/preview-use-speech-to-text.tsx
new file mode 100644
index 00000000..c2b9d7f2
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-speech-to-text/preview-use-speech-to-text.tsx
@@ -0,0 +1 @@
+// Coming soon
diff --git a/src/ui/cuicui/hooks/use-speech-to-text/use-speech-to-text.ts b/src/ui/cuicui/hooks/use-speech-to-text/use-speech-to-text.ts
new file mode 100644
index 00000000..b3cac976
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-speech-to-text/use-speech-to-text.ts
@@ -0,0 +1,139 @@
+// import { useCallback, useEffect, useState } from "react";
+
+// // Define custom types for SpeechRecognition and SpeechRecognitionEvent
+// interface ISpeechRecognitionEvent extends Event {
+// results: SpeechRecognitionResultList;
+// resultIndex: number;
+// }
+
+// interface ISpeechRecognition extends EventTarget {
+// lang: string;
+// continuous: boolean;
+// interimResults: boolean;
+// maxAlternatives: number;
+// start: () => void;
+// stop: () => void;
+// onresult: (event: ISpeechRecognitionEvent) => void;
+// onerror: (event: Event) => void;
+// onend: () => void;
+// }
+
+// declare global {
+// interface Window {
+// speechRecognition: new () => ISpeechRecognition;
+// webkitSpeechRecognition: new () => ISpeechRecognition;
+// }
+// }
+
+// interface UseSpeechToTextProps {
+// lang?: string;
+// continuous?: boolean;
+// interimResults?: boolean;
+// maxAlternatives?: number;
+// onResult?: (result: string) => void;
+// onError?: (error: string) => void;
+// }
+
+// export const useSpeechToText = ({
+// lang = "en-US",
+// continuous = true,
+// interimResults = true,
+// maxAlternatives = 1,
+// onResult,
+// onError,
+// }: UseSpeechToTextProps = {}) => {
+// const [isListening, setIsListening] = useState(false);
+// const [transcript, setTranscript] = useState("");
+// const [lastProcessedIndex, setLastProcessedIndex] = useState(0);
+
+// const recognition: ISpeechRecognition | null =
+// typeof window !== "undefined" &&
+// (window.speechRecognition || window.webkitSpeechRecognition)
+// ? new (window.speechRecognition || window.webkitSpeechRecognition)()
+// : null;
+
+// const handleResult = useCallback(
+// (event: ISpeechRecognitionEvent) => {
+// let interimTranscript = "";
+// let finalTranscript = "";
+
+// // Iterate through all the current results
+// for (let i = lastProcessedIndex; i < event.results.length; i++) {
+// const result = event.results[i];
+// // If the result is final, append to the final transcript
+// if (result.isFinal) {
+// finalTranscript += `${result[0].transcript} `;
+// setLastProcessedIndex(i + 1);
+// } else {
+// // Otherwise, append to the interim transcript
+// interimTranscript += `${result[0].transcript} `;
+// }
+// }
+
+// // Update the transcript state with a combination of the final and interim results
+// setTranscript(transcript + finalTranscript + interimTranscript);
+
+// // Invoke callback with the latest transcript
+// onResult?.(transcript + finalTranscript + interimTranscript);
+// },
+// [onResult, transcript, lastProcessedIndex],
+// );
+
+// // start and stop functions using useCallback
+// const start = useCallback(() => {
+// if (!recognition || isListening) {
+// return;
+// }
+// setTranscript("");
+// setLastProcessedIndex(0);
+// setIsListening(true);
+// recognition.start();
+// }, [recognition, isListening]);
+
+// const stop = useCallback(() => {
+// if (!(recognition && isListening)) {
+// return;
+// }
+// recognition.stop();
+// setIsListening(false);
+// }, [recognition, isListening]);
+
+// useEffect(() => {
+// if (!recognition) {
+// onError &&
+// onError("Speech recognition is not supported in this browser.");
+// return;
+// }
+
+// recognition.lang = lang;
+// recognition.continuous = continuous;
+// recognition.interimResults = interimResults;
+// recognition.maxAlternatives = maxAlternatives;
+// recognition.onresult = handleResult;
+// recognition.onerror = (event) => onError?.(event.type);
+// recognition.onend = () => {
+// setIsListening(false);
+// };
+
+// return () => {
+// if (isListening) {
+// recognition.stop();
+// }
+// };
+// }, [
+// lang,
+// continuous,
+// interimResults,
+// maxAlternatives,
+// handleResult,
+// onError,
+// recognition,
+// start,
+// stop,
+// isListening,
+// ]);
+
+// return { start, stop, transcript, isListening };
+// };
+
+// export default useSpeechToText;
diff --git a/src/ui/cuicui/hooks/use-step/category.use-step.tsx b/src/ui/cuicui/hooks/use-step/category.use-step.tsx
new file mode 100644
index 00000000..a1df15da
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-step/category.use-step.tsx
@@ -0,0 +1,25 @@
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { PreviewUseStep } from "#/src/ui/cuicui/hooks/use-step/preview-use-step";
+import { ListEndIcon } from "lucide-react";
+
+export const useStepCategory: SingleComponentCategoryType = {
+ name: "Use Step",
+ slug: "use-step",
+ description: "Create a step component",
+ releaseDateCategory: new Date("2024-09-26"),
+ icon: ListEndIcon,
+ component: {
+ rerenderButton: true,
+ lastUpdatedDateComponent: new Date("2024-09-26"),
+ sizePreview: "md",
+ isIframed: false,
+ variantList: [
+ {
+ name: "variant 1",
+ component: ,
+ slugComponentFile: "use-step",
+ slugPreviewFile: "preview-use-step",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-step/preview-use-step.tsx b/src/ui/cuicui/hooks/use-step/preview-use-step.tsx
new file mode 100644
index 00000000..711b2ca6
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-step/preview-use-step.tsx
@@ -0,0 +1,40 @@
+"use client";
+import { useStep } from "#/src/ui/cuicui/hooks/use-step/use-step";
+
+export function PreviewUseStep() {
+ const [currentStep, helpers] = useStep(5);
+
+ const {
+ canGoToPrevStep,
+ canGoToNextStep,
+ goToNextStep,
+ goToPrevStep,
+ reset,
+ setStep,
+ } = helpers;
+
+ return (
+ <>
+ Current step is {currentStep}
+ Can go to previous step {canGoToPrevStep ? "yes" : "no"}
+ Can go to next step {canGoToNextStep ? "yes" : "no"}
+
+
+
+
+ >
+ );
+}
diff --git a/src/ui/cuicui/hooks/use-step/use-step.ts b/src/ui/cuicui/hooks/use-step/use-step.ts
new file mode 100644
index 00000000..57081b16
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-step/use-step.ts
@@ -0,0 +1,65 @@
+"use client";
+import { useCallback, useState } from "react";
+
+import type { Dispatch, SetStateAction } from "react";
+
+type UseStepActions = {
+ goToNextStep: () => void;
+ goToPrevStep: () => void;
+ reset: () => void;
+ canGoToNextStep: boolean;
+ canGoToPrevStep: boolean;
+ setStep: Dispatch>;
+};
+
+type SetStepCallbackType = (step: number | ((step: number) => number)) => void;
+
+export function useStep(maxStep: number): [number, UseStepActions] {
+ const [currentStep, setCurrentStep] = useState(1);
+
+ const canGoToNextStep = currentStep + 1 <= maxStep;
+ const canGoToPrevStep = currentStep - 1 > 0;
+
+ const setStep = useCallback(
+ (step) => {
+ // Allow value to be a function so we have the same API as useState
+ const newStep = step instanceof Function ? step(currentStep) : step;
+
+ if (newStep >= 1 && newStep <= maxStep) {
+ setCurrentStep(newStep);
+ return;
+ }
+
+ throw new Error("Step not valid");
+ },
+ [maxStep, currentStep],
+ );
+
+ const goToNextStep = useCallback(() => {
+ if (canGoToNextStep) {
+ setCurrentStep((step) => step + 1);
+ }
+ }, [canGoToNextStep]);
+
+ const goToPrevStep = useCallback(() => {
+ if (canGoToPrevStep) {
+ setCurrentStep((step) => step - 1);
+ }
+ }, [canGoToPrevStep]);
+
+ const reset = useCallback(() => {
+ setCurrentStep(1);
+ }, []);
+
+ return [
+ currentStep,
+ {
+ goToNextStep,
+ goToPrevStep,
+ canGoToNextStep,
+ canGoToPrevStep,
+ setStep,
+ reset,
+ },
+ ];
+}
diff --git a/src/ui/cuicui/hooks/use-text-to-speech/category.use-text-to-speech.tsx b/src/ui/cuicui/hooks/use-text-to-speech/category.use-text-to-speech.tsx
new file mode 100644
index 00000000..9f9a91c6
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-text-to-speech/category.use-text-to-speech.tsx
@@ -0,0 +1,32 @@
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { SpeechIcon } from "lucide-react";
+
+export const useTextToSpeechCategory: SingleComponentCategoryType = {
+ slug: "use-text-to-speech",
+ name: "Use Text to Speech",
+ description: "A hook that allows you to convert text to speech",
+ releaseDateCategory: new Date("2024-09-28"),
+ icon: SpeechIcon,
+ // previewCategory: {
+ // component: ,
+ // previewImage: PreviewImageUseTextToSpeech,
+ // previewScale: 0.8,
+ // },
+ comingSoonCategory: true,
+ component: null,
+ // component: {
+ // lastUpdatedDateComponent: new Date("2024-08-28"),
+ // sizePreview: "lg",
+
+ // isIframed: true,
+
+ // variantList: [
+ // {
+ // name: "variant 1",
+ // component: ,
+ // slugComponentFile: "use-text-to-speech",
+ // slugPreviewFile: "preview-text-to-speech",
+ // },
+ // ],
+ // },
+};
diff --git a/src/ui/cuicui/hooks/use-text-to-speech/preview-use-text-to-speech.tsx b/src/ui/cuicui/hooks/use-text-to-speech/preview-use-text-to-speech.tsx
new file mode 100644
index 00000000..c2b9d7f2
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-text-to-speech/preview-use-text-to-speech.tsx
@@ -0,0 +1 @@
+// Coming soon
diff --git a/src/ui/cuicui/hooks/use-text-to-speech/use-text-to-speech.ts b/src/ui/cuicui/hooks/use-text-to-speech/use-text-to-speech.ts
new file mode 100644
index 00000000..b975d2ab
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-text-to-speech/use-text-to-speech.ts
@@ -0,0 +1,33 @@
+// import { useEffect, useRef, useState } from "react";
+
+// const useSetState = (initialState = {}) => {
+// const [state, set] = useState(initialState);
+// const setState = (patch) => {
+// Object.assign(state, patch);
+// set(state);
+// };
+
+// return [state, setState];
+// };
+
+// const useSpeech = (text, opts = {}) => {
+// const [state, setState] = useSetState({
+// isPlaying: false,
+// volume: opts.volume || 1,
+// });
+
+// const uterranceRef = useRef(null);
+
+// useEffect(() => {
+// const utterance = new SpeechSynthesisUtterance(text);
+// utterance.volume = opts.volume || 1;
+// utterance.onstart = () => setState({ isPlaying: true });
+// utterance.onresume = () => setState({ isPlaying: true });
+// utterance.onend = () => setState({ isPlaying: false });
+// utterance.onpause = () => setState({ isPlaying: false });
+// uterranceRef.current = utterance;
+// window.speechSynthesis.speak(uterranceRef.current);
+// }, []);
+
+// return state;
+// };
diff --git a/src/ui/cuicui/hooks/use-throttle/category.use-throttle.tsx b/src/ui/cuicui/hooks/use-throttle/category.use-throttle.tsx
new file mode 100644
index 00000000..36ce35a9
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-throttle/category.use-throttle.tsx
@@ -0,0 +1,29 @@
+import { ViewIcon } from "lucide-react";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { PreviewUseThrottle } from "#/src/ui/cuicui/hooks/use-throttle/preview-use-throttle";
+
+export const useThrottleCategory: SingleComponentCategoryType = {
+ slug: "use-throttle",
+ name: "Use Throttle",
+ description:
+ "A hook that throttles the execution of a function, limiting how often it can be invoked",
+ releaseDateCategory: new Date("2024-09-28"),
+ icon: ViewIcon,
+ previewCategory: {
+ component: ,
+ previewScale: 0.8,
+ },
+ component: {
+ lastUpdatedDateComponent: new Date("2024-09-28"),
+ sizePreview: "sm",
+ isIframed: false,
+ variantList: [
+ {
+ name: "default variant",
+ component: ,
+ slugComponentFile: "use-throttle",
+ slugPreviewFile: "preview-use-throttle",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-throttle/preview-use-throttle.tsx b/src/ui/cuicui/hooks/use-throttle/preview-use-throttle.tsx
new file mode 100644
index 00000000..e224c9ef
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-throttle/preview-use-throttle.tsx
@@ -0,0 +1,29 @@
+"use client";
+import { useThrottle } from "#/src/ui/cuicui/hooks/use-throttle/use-throttle";
+import { useEffect, useState } from "react";
+import { toast } from "sonner";
+
+const performSearch = (searchTerm: string) => {
+ toast(`Searching for: ${searchTerm}`);
+};
+
+export const PreviewUseThrottle = () => {
+ const [searchTerm, setSearchTerm] = useState("");
+ const throttledSearchTerm = useThrottle(searchTerm, 500);
+
+ useEffect(() => {
+ if (throttledSearchTerm) {
+ // Perform the search operation, e.g., API call
+ performSearch(throttledSearchTerm);
+ }
+ }, [throttledSearchTerm]);
+
+ return (
+ setSearchTerm(e.target.value)}
+ placeholder="Search..."
+ />
+ );
+};
diff --git a/src/ui/cuicui/hooks/use-throttle/use-throttle.ts b/src/ui/cuicui/hooks/use-throttle/use-throttle.ts
new file mode 100644
index 00000000..bdb17573
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-throttle/use-throttle.ts
@@ -0,0 +1,38 @@
+"use client";
+import { useState, useEffect, useRef } from "react";
+
+/**
+ * A custom React hook that throttles a value, ensuring that updates occur
+ * no more frequently than the specified limit.
+ *
+ * @param value - The value to be throttled.
+ * @param limit - The time limit in milliseconds for throttling.
+ * @returns The throttled value.
+ *
+ * @example
+ * const throttledValue = useThrottle(inputValue, 1000);
+ */
+export function useThrottle(value: T, limit: number): T {
+ const [throttledValue, setThrottledValue] = useState(value);
+ const lastRan = useRef(Date.now());
+
+ useEffect(() => {
+ const handler = setTimeout(
+ () => {
+ const now = Date.now();
+ if (now - lastRan.current >= limit) {
+ setThrottledValue(value);
+ lastRan.current = now;
+ }
+ },
+ limit - (Date.now() - lastRan.current),
+ );
+
+ // Cleanup the timeout if value or limit changes before the timeout completes
+ return () => {
+ clearTimeout(handler);
+ };
+ }, [value, limit]);
+
+ return throttledValue;
+}
diff --git a/src/ui/cuicui/hooks/use-window-scroll-position/category.use-window-scroll-postion.tsx b/src/ui/cuicui/hooks/use-window-scroll-position/category.use-window-scroll-postion.tsx
new file mode 100644
index 00000000..deec4007
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-window-scroll-position/category.use-window-scroll-postion.tsx
@@ -0,0 +1,28 @@
+import { ScrollIcon } from "lucide-react";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { PreviewUseWindowScrollPosition } from "#/src/ui/cuicui/hooks/use-window-scroll-position/preview-use-window-scroll-position";
+
+export const useWindowScrollPositionCategory: SingleComponentCategoryType = {
+ slug: "use-window-scroll-position",
+ name: "Use Window Scroll Position",
+ description: "A hook that allows you to track the window's scroll position",
+ releaseDateCategory: new Date("2024-09-28"),
+ icon: ScrollIcon,
+ previewCategory: {
+ component: ,
+ previewScale: 0.8,
+ },
+ component: {
+ lastUpdatedDateComponent: new Date("2024-09-28"),
+ sizePreview: "sm",
+ isIframed: false,
+ variantList: [
+ {
+ name: "variant 1",
+ component: ,
+ slugComponentFile: "use-window-scroll-position",
+ slugPreviewFile: "preview-use-window-scroll-position",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-window-scroll-position/preview-use-window-scroll-position.tsx b/src/ui/cuicui/hooks/use-window-scroll-position/preview-use-window-scroll-position.tsx
new file mode 100644
index 00000000..eb24d951
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-window-scroll-position/preview-use-window-scroll-position.tsx
@@ -0,0 +1,21 @@
+"use client";
+import { useWindowScrollPosition } from "#/src/ui/cuicui/hooks/use-window-scroll-position/use-window-scroll-position";
+
+export const PreviewUseWindowScrollPosition = () => {
+ const { y } = useWindowScrollPosition({ throttleMs: 200 });
+
+ return (
+
+ );
+};
diff --git a/src/ui/cuicui/hooks/use-window-scroll-position/use-window-scroll-position.ts b/src/ui/cuicui/hooks/use-window-scroll-position/use-window-scroll-position.ts
new file mode 100644
index 00000000..2cf3bdb7
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-window-scroll-position/use-window-scroll-position.ts
@@ -0,0 +1,82 @@
+"use client";
+import { useEffect, useState, useCallback, useRef } from "react";
+
+// Define the shape of the scroll position
+interface ScrollPosition {
+ x: number;
+ y: number;
+}
+
+// Define the options for the hook
+interface UseWindowScrollPositionOptions {
+ throttleMs?: number;
+}
+
+// Throttle function implementation
+const useThrottle = (callback: () => void, delay: number) => {
+ const lastCall = useRef(0);
+ const timeout = useRef(null);
+
+ const throttledFunction = useCallback(() => {
+ const now = Date.now();
+ const remaining = delay - (now - lastCall.current);
+
+ if (remaining <= 0) {
+ if (timeout.current) {
+ clearTimeout(timeout.current);
+ timeout.current = null;
+ }
+ lastCall.current = now;
+ callback();
+ } else if (!timeout.current) {
+ timeout.current = setTimeout(() => {
+ lastCall.current = Date.now();
+ timeout.current = null;
+ callback();
+ }, remaining);
+ }
+ }, [callback, delay]);
+
+ return throttledFunction;
+};
+
+export const useWindowScrollPosition = (
+ options: UseWindowScrollPositionOptions = {},
+): ScrollPosition => {
+ const { throttleMs = 100 } = options;
+
+ // Initialize state with (0,0) for SSR compatibility
+ const [scroll, setScroll] = useState({
+ x: typeof window !== "undefined" ? window.scrollY : 0,
+ y: typeof window !== "undefined" ? window.scrollY : 0,
+ });
+
+ // Update scroll position
+ const updateScroll = useCallback(() => {
+ setScroll({
+ x: window.scrollX,
+ y: window.scrollY,
+ });
+ }, []);
+
+ // Create a throttled version of the update function
+ const throttledUpdateScroll = useThrottle(updateScroll, throttleMs);
+
+ useEffect(() => {
+ // Ensure window is available (avoids issues with SSR)
+ if (typeof window === "undefined") {
+ return;
+ }
+
+ window.addEventListener("scroll", throttledUpdateScroll);
+
+ // Initial update in case the user has already scrolled
+ throttledUpdateScroll();
+
+ return () => {
+ window.removeEventListener("scroll", throttledUpdateScroll);
+ };
+ }, [throttledUpdateScroll]);
+
+ return scroll;
+};
diff --git a/src/ui/cuicui/hooks/use-window-size/category.use-window-size.tsx b/src/ui/cuicui/hooks/use-window-size/category.use-window-size.tsx
new file mode 100644
index 00000000..e133b731
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-window-size/category.use-window-size.tsx
@@ -0,0 +1,30 @@
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import { PreviewUseWindowSize } from "#/src/ui/cuicui/hooks/use-window-size/preview-use-window-size";
+import { AppWindowMacIcon } from "lucide-react";
+
+export const useWindowSizeCategory: SingleComponentCategoryType = {
+ slug: "use-window-size",
+ name: "Use Window Size",
+ description: "A hook that allows you to track the size of the browser window",
+ releaseDateCategory: new Date("2024-08-28"),
+ icon: AppWindowMacIcon,
+ previewCategory: {
+ component: ,
+ previewScale: 0.8,
+ },
+ component: {
+ lastUpdatedDateComponent: new Date("2024-08-28"),
+ sizePreview: "sm",
+
+ isIframed: false,
+
+ variantList: [
+ {
+ name: "Default Variant",
+ component: ,
+ slugComponentFile: "use-window-size",
+ slugPreviewFile: "preview-use-window-size",
+ },
+ ],
+ },
+};
diff --git a/src/ui/cuicui/hooks/use-window-size/preview-use-window-size.tsx b/src/ui/cuicui/hooks/use-window-size/preview-use-window-size.tsx
new file mode 100644
index 00000000..58110665
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-window-size/preview-use-window-size.tsx
@@ -0,0 +1,7 @@
+"use client";
+import { useWindowSize } from "#/src/ui/cuicui/hooks/use-window-size/use-window-size";
+
+export function PreviewUseWindowSize() {
+ const windowSize = useWindowSize();
+ return {JSON.stringify(windowSize, null, 2)}
;
+}
diff --git a/src/ui/cuicui/hooks/use-window-size/use-window-size.ts b/src/ui/cuicui/hooks/use-window-size/use-window-size.ts
new file mode 100644
index 00000000..bc5f730a
--- /dev/null
+++ b/src/ui/cuicui/hooks/use-window-size/use-window-size.ts
@@ -0,0 +1,42 @@
+"use client";
+import { useEffect, useState } from "react";
+
+function getSize() {
+ // Check if 'window' is defined (i.e., the code is running in the browser)
+ if (typeof window !== "undefined") {
+ return {
+ innerHeight: window.innerHeight,
+ innerWidth: window.innerWidth,
+ outerHeight: window.outerHeight,
+ outerWidth: window.outerWidth,
+ };
+ }
+ // Provide default values for SSR
+ return {
+ innerHeight: 0,
+ innerWidth: 0,
+ outerHeight: 0,
+ outerWidth: 0,
+ };
+}
+
+export function useWindowSize() {
+ const [windowSize, setWindowSize] = useState(getSize());
+
+ useEffect(() => {
+ if (typeof window === "undefined") {
+ return;
+ }
+
+ function handleResize() {
+ setWindowSize(getSize());
+ }
+
+ window.addEventListener("resize", handleResize);
+ return () => {
+ window.removeEventListener("resize", handleResize);
+ };
+ }, []);
+
+ return windowSize;
+}
diff --git a/src/ui/cuicui/other/cursors/dynamic-cards/card-mouse-highlighting-border.tsx b/src/ui/cuicui/other/cursors/dynamic-cards/card-mouse-highlighting-border.tsx
index 46ca9304..fadaa05e 100644
--- a/src/ui/cuicui/other/cursors/dynamic-cards/card-mouse-highlighting-border.tsx
+++ b/src/ui/cuicui/other/cursors/dynamic-cards/card-mouse-highlighting-border.tsx
@@ -1,4 +1,4 @@
-import { useMouse } from "#/src/ui/cuicui/hooks/use-mouse/hook/use-mouse";
+import { useMouse } from "#/src/ui/cuicui/hooks/use-mouse/use-mouse";
export const CardMouseHighlightingBorder = ({
title,
diff --git a/src/ui/cuicui/other/cursors/dynamic-cards/gradient-card.tsx b/src/ui/cuicui/other/cursors/dynamic-cards/gradient-card.tsx
index 69c4c9f3..876fecd7 100644
--- a/src/ui/cuicui/other/cursors/dynamic-cards/gradient-card.tsx
+++ b/src/ui/cuicui/other/cursors/dynamic-cards/gradient-card.tsx
@@ -1,7 +1,7 @@
"use client";
import { ArrowUpRightIcon } from "lucide-react";
import type { ReactNode } from "react";
-import { useMouse } from "#/src/ui/cuicui/hooks/use-mouse/hook/use-mouse";
+import { useMouse } from "#/src/ui/cuicui/hooks/use-mouse/use-mouse";
import { cn } from "#/src/utils/cn";
export const GradientCard = ({
diff --git a/src/ui/cuicui/other/cursors/follow-cursor/replace-cursor.tsx b/src/ui/cuicui/other/cursors/follow-cursor/replace-cursor.tsx
index d527f021..151223c3 100644
--- a/src/ui/cuicui/other/cursors/follow-cursor/replace-cursor.tsx
+++ b/src/ui/cuicui/other/cursors/follow-cursor/replace-cursor.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useMouse } from "#/src/ui/cuicui/hooks/use-mouse/hook/use-mouse";
+import { useMouse } from "#/src/ui/cuicui/hooks/use-mouse/use-mouse";
export default function FollowCursorHideCursor() {
const [mouse, parentRef] = useMouse();
diff --git a/src/ui/cuicui/other/cursors/follow-cursor/with-cursor.tsx b/src/ui/cuicui/other/cursors/follow-cursor/with-cursor.tsx
index cf64f5a8..e78858bd 100644
--- a/src/ui/cuicui/other/cursors/follow-cursor/with-cursor.tsx
+++ b/src/ui/cuicui/other/cursors/follow-cursor/with-cursor.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useMouse } from "#/src/ui/cuicui/hooks/use-mouse/hook/use-mouse";
+import { useMouse } from "#/src/ui/cuicui/hooks/use-mouse/use-mouse";
export default function FollowCursorWithCursor() {
const [mouse, parentRef] = useMouse();
diff --git a/src/ui/cuicui/other/other.section.tsx b/src/ui/cuicui/other/other.section.tsx
index bab9c126..0364516f 100644
--- a/src/ui/cuicui/other/other.section.tsx
+++ b/src/ui/cuicui/other/other.section.tsx
@@ -7,6 +7,8 @@ import { transitionWrappersCategory } from "#/src/ui/cuicui/other/transition-wra
export const otherSection: SectionType = {
name: "Other",
slug: "other",
+ type: "multiple-component",
+
categoriesList: [
cursorCategory,
mockUpsCategory,
diff --git a/src/ui/cuicui/other/qr-code/qr-code-generator/qr-code-generator.tsx b/src/ui/cuicui/other/qr-code/qr-code-generator/qr-code-generator.tsx
index 14b1fc2b..204a3cd2 100644
--- a/src/ui/cuicui/other/qr-code/qr-code-generator/qr-code-generator.tsx
+++ b/src/ui/cuicui/other/qr-code/qr-code-generator/qr-code-generator.tsx
@@ -3,7 +3,7 @@ import { QRCodeSVG } from "qrcode.react";
import { type RefObject, useRef, useState } from "react";
import { ModernSimpleInput } from "#/src/ui/cuicui/common-ui/inputs/modern-simple-input/modern-simple-input";
-import { useCopyToClipboard } from "#/src/ui/cuicui/hooks/use-copy-to-clipboard/hook/use-copy-to-clipboard";
+import { useCopyToClipboard } from "#/src/ui/cuicui/hooks/use-copy-to-clipboard/use-copy-to-clipboard";
function downloadStringAsFile(data: string, filename: string) {
const a = document.createElement("a");
diff --git a/src/ui/cuicui/other/transition-wrappers/slide-in-on-scroll/slide-in-on-scroll.component.tsx b/src/ui/cuicui/other/transition-wrappers/slide-in-on-scroll/slide-in-on-scroll.component.tsx
index 0b712b6c..e5ae0e29 100644
--- a/src/ui/cuicui/other/transition-wrappers/slide-in-on-scroll/slide-in-on-scroll.component.tsx
+++ b/src/ui/cuicui/other/transition-wrappers/slide-in-on-scroll/slide-in-on-scroll.component.tsx
@@ -12,7 +12,8 @@ export const SlideInOnScrollComponent: ComponentType = {
{
name: "Slide in on scroll",
component: ,
- slugPreviewFile: "slide-in-on-scroll",
+ slugPreviewFile: "preview-slide-in-on-scroll",
+ slugComponentFile: "slide-in-on-scroll",
},
],
// supportedBrowers: ["chromium"],
diff --git a/src/ui/cuicui/utils/cn/cn.category.tsx b/src/ui/cuicui/utils/cn/cn.category.tsx
index a1b40ff3..064da491 100644
--- a/src/ui/cuicui/utils/cn/cn.category.tsx
+++ b/src/ui/cuicui/utils/cn/cn.category.tsx
@@ -1,34 +1,31 @@
-import type { CategoryType } from "#/src/lib/types/component";
-import PreviewCn from "#/src/ui/cuicui/utils/cn/cn/preview-cn";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import PreviewCn from "#/src/ui/cuicui/utils/cn/preview-cn";
import { ChartNoAxesGanttIcon } from "lucide-react";
-export const cnCategory: CategoryType = {
- name: "CN",
+export const cnCategory: SingleComponentCategoryType = {
+ name: "ClassNames",
slug: "cn",
previewCategory: {
previewScale: 0.5,
component: ,
},
- componentList: [
- {
- sizePreview: "sm",
- slug: "cn",
- isIframed: false,
- title: "Classnames more known by cn()",
- description: "A utility function to merge classnames in a clean way",
- releaseDateComponent: new Date("2024-09-26"),
- lastUpdatedDateComponent: new Date("2024-09-26"),
- variantList: [
- {
- name: "Default",
- slugPreviewFile: "preview-cn",
- slugComponentFile: "cn",
- component: ,
- },
- ],
- },
- ],
- description: "",
+ component: {
+ sizePreview: "sm",
+
+ isIframed: false,
+
+ releaseDateComponent: new Date("2024-09-26"),
+ lastUpdatedDateComponent: new Date("2024-09-26"),
+ variantList: [
+ {
+ name: "Default",
+ slugPreviewFile: "preview-cn",
+ slugComponentFile: "cn",
+ component: ,
+ },
+ ],
+ },
+ description: "A utility function to merge classnames in a clean way",
icon: ChartNoAxesGanttIcon,
releaseDateCategory: new Date("2024-09-26"),
};
diff --git a/src/ui/cuicui/utils/cn/cn/cn.ts b/src/ui/cuicui/utils/cn/cn.ts
similarity index 100%
rename from src/ui/cuicui/utils/cn/cn/cn.ts
rename to src/ui/cuicui/utils/cn/cn.ts
diff --git a/src/ui/cuicui/utils/cn/cn/preview-cn.tsx b/src/ui/cuicui/utils/cn/preview-cn.tsx
similarity index 95%
rename from src/ui/cuicui/utils/cn/cn/preview-cn.tsx
rename to src/ui/cuicui/utils/cn/preview-cn.tsx
index ad0ecb30..6dea8883 100644
--- a/src/ui/cuicui/utils/cn/cn/preview-cn.tsx
+++ b/src/ui/cuicui/utils/cn/preview-cn.tsx
@@ -1,5 +1,5 @@
"use client";
-import { cn } from "#/src/ui/cuicui/utils/cn/cn/cn";
+import { cn } from "#/src/ui/cuicui/utils/cn/cn";
import { useState } from "react";
export default function PreviewCn() {
diff --git a/src/ui/cuicui/utils/sleep/sleep/preview-sleep.tsx b/src/ui/cuicui/utils/sleep/preview-sleep.tsx
similarity index 100%
rename from src/ui/cuicui/utils/sleep/sleep/preview-sleep.tsx
rename to src/ui/cuicui/utils/sleep/preview-sleep.tsx
diff --git a/src/ui/cuicui/utils/sleep/sleep.category.tsx b/src/ui/cuicui/utils/sleep/sleep.category.tsx
index f1eb0f6d..fe324260 100644
--- a/src/ui/cuicui/utils/sleep/sleep.category.tsx
+++ b/src/ui/cuicui/utils/sleep/sleep.category.tsx
@@ -1,33 +1,29 @@
-import type { CategoryType } from "#/src/lib/types/component";
-import PreviewSleep from "#/src/ui/cuicui/utils/sleep/sleep/preview-sleep";
+import type { SingleComponentCategoryType } from "#/src/lib/types/component";
+import PreviewSleep from "#/src/ui/cuicui/utils/sleep/preview-sleep";
import { HourglassIcon } from "lucide-react";
-export const sleepCategory: CategoryType = {
+export const sleepCategory: SingleComponentCategoryType = {
name: "Sleep",
slug: "sleep",
previewCategory: {
previewScale: 0.5,
component: ,
},
- componentList: [
- {
- sizePreview: "sm",
- slug: "sleep",
- title: "Sleep",
- description: "A utility function to manage sleep",
- releaseDateComponent: new Date("2024-09-26"),
- lastUpdatedDateComponent: new Date("2024-09-26"),
- variantList: [
- {
- name: "Default",
- slugPreviewFile: "preview-sleep",
- slugComponentFile: "sleep",
- component: ,
- },
- ],
- },
- ],
- description: "",
+ component: {
+ sizePreview: "sm",
+
+ releaseDateComponent: new Date("2024-09-26"),
+ lastUpdatedDateComponent: new Date("2024-09-26"),
+ variantList: [
+ {
+ name: "Default",
+ slugPreviewFile: "preview-sleep",
+ slugComponentFile: "sleep",
+ component: ,
+ },
+ ],
+ },
+ description: "A utility function to manage sleep",
icon: HourglassIcon,
releaseDateCategory: new Date("2024-09-26"),
};
diff --git a/src/ui/cuicui/utils/sleep/sleep/sleep.ts b/src/ui/cuicui/utils/sleep/sleep.ts
similarity index 100%
rename from src/ui/cuicui/utils/sleep/sleep/sleep.ts
rename to src/ui/cuicui/utils/sleep/sleep.ts
diff --git a/src/ui/cuicui/utils/utils.section.tsx b/src/ui/cuicui/utils/utils.section.tsx
index 521f7e3f..837fc87d 100644
--- a/src/ui/cuicui/utils/utils.section.tsx
+++ b/src/ui/cuicui/utils/utils.section.tsx
@@ -3,6 +3,7 @@ import { cnCategory } from "#/src/ui/cuicui/utils/cn/cn.category";
import { sleepCategory } from "#/src/ui/cuicui/utils/sleep/sleep.category";
export const utilsSection: SectionType = {
+ type: "single-component",
name: "Utils",
slug: "utils",
categoriesList: [cnCategory, sleepCategory],
diff --git a/src/ui/navigation/animated-background.tsx b/src/ui/navigation/animated-background.tsx
new file mode 100644
index 00000000..caf2b857
--- /dev/null
+++ b/src/ui/navigation/animated-background.tsx
@@ -0,0 +1,93 @@
+"use client";
+
+import { cn } from "#/src/utils/cn";
+import { AnimatePresence, type Transition, motion } from "framer-motion";
+import {
+ Children,
+ cloneElement,
+ type ReactElement,
+ useEffect,
+ useState,
+ useId,
+} from "react";
+
+type AnimatedBackgroundProps = {
+ children:
+ | ReactElement<{ "data-id": string }>[]
+ | ReactElement<{ "data-id": string }>;
+ defaultValue?: string;
+ onValueChange?: (newActiveId: string | null) => void;
+ className?: string;
+ transition?: Transition;
+ enableHover?: boolean;
+};
+
+export default function AnimatedBackground({
+ children,
+ defaultValue,
+ onValueChange,
+ className,
+ transition,
+ enableHover = false,
+}: AnimatedBackgroundProps) {
+ const [activeId, setActiveId] = useState(null);
+ const uniqueId = useId();
+
+ const handleSetActiveId = (id: string | null) => {
+ setActiveId(id);
+
+ if (onValueChange) {
+ onValueChange(id);
+ }
+ };
+
+ useEffect(() => {
+ if (defaultValue !== undefined) {
+ setActiveId(defaultValue);
+ }
+ }, [defaultValue]);
+
+ return Children.map(children, (child: any, index) => {
+ const id = child.props["data-id"];
+
+ const interactionProps = enableHover
+ ? {
+ onMouseEnter: () => handleSetActiveId(id),
+ onMouseLeave: () => handleSetActiveId(null),
+ }
+ : {
+ onClick: () => handleSetActiveId(id),
+ };
+
+ return cloneElement(
+ child,
+ {
+ //TODO: TRY TO FIX THIS KEY
+ key: index,
+ className: cn("relative inline-flex", child.props.className),
+ "aria-selected": activeId === id,
+ "data-checked": activeId === id ? "true" : "false",
+ ...interactionProps,
+ },
+ <>
+
+ {activeId === id && (
+
+ )}
+
+ {child.props.children}
+ >,
+ );
+ });
+}
diff --git a/src/ui/navigation/desktop-menu.tsx b/src/ui/navigation/desktop-menu.tsx
index 6f199ca1..41267b7b 100644
--- a/src/ui/navigation/desktop-menu.tsx
+++ b/src/ui/navigation/desktop-menu.tsx
@@ -13,7 +13,7 @@ import { ScrollArea } from "../shadcn/scrollarea";
import StarGithubProjectButton from "../star-github-project-button";
import ThemeSwitcher from "../theme-switcher";
import NavigationMenu from "./navigation-menu";
-import { SearchMenu } from "./search-menu";
+import { SearchMenu } from "../../components/search-menu/search-menu";
export function DesktopSideMenu({
className,
diff --git a/src/ui/navigation/navigation-item.tsx b/src/ui/navigation/navigation-item.tsx
new file mode 100644
index 00000000..be71dc8c
--- /dev/null
+++ b/src/ui/navigation/navigation-item.tsx
@@ -0,0 +1,108 @@
+import GradientContainer from "#/src/ui/gradient-container";
+import GradientText from "#/src/ui/gradient-text";
+import { cn } from "#/src/utils/cn";
+import { differenceInDays } from "date-fns";
+import { ArrowUpRightIcon, type LucideIcon } from "lucide-react";
+import Link from "next/link";
+import { useSelectedLayoutSegments } from "next/navigation";
+import type { ReactNode } from "react";
+
+export function GlobalNavItem({
+ isMobile,
+ href,
+ name,
+ Icon,
+ isComingSoon = false,
+ releaseDate,
+ updatedDate,
+ target = "sameWindow",
+}: Readonly<{
+ isMobile?: boolean;
+ href: string;
+ name: string;
+ // biome-ignore lint/style/useNamingConvention:
+ Icon: LucideIcon | null;
+ isComingSoon?: boolean;
+ releaseDate?: Date | null;
+ updatedDate?: Date | null;
+ target?: "newWindow" | "sameWindow";
+}>) {
+ const segments = useSelectedLayoutSegments();
+ const splittedHref = href.split("/");
+ const lastSegment = splittedHref[splittedHref.length - 1];
+
+ let isActive = false;
+ if (segments) {
+ isActive = segments.some((segment) => segment === lastSegment);
+ }
+
+ const isNew = differenceInDays(new Date(), releaseDate ?? 0) < 21;
+ const isUpdated = differenceInDays(new Date(), updatedDate ?? 0) < 14;
+
+ function getTagValue() {
+ if (isComingSoon) {
+ return "Coming soon";
+ }
+ if (isNew) {
+ return "New";
+ }
+ if (isUpdated) {
+ return "Updated";
+ }
+ return null;
+ }
+
+ return (
+
+
+ {Icon && (
+
+ )}
+
+ {name}
+
+
+ {getTagValue() && (
+
+ {getTagValue()}
+
+ )}
+
+ {target === "newWindow" && (
+
+ )}
+
+ );
+}
+
+export const SectionWrapper = ({
+ children,
+ name,
+}: {
+ children: ReactNode;
+ name: string;
+}) => {
+ return (
+
+ );
+};
diff --git a/src/ui/navigation/navigation-menu.tsx b/src/ui/navigation/navigation-menu.tsx
index 5c2b633f..4f57d51b 100644
--- a/src/ui/navigation/navigation-menu.tsx
+++ b/src/ui/navigation/navigation-menu.tsx
@@ -1,45 +1,17 @@
-"use client";
-import { differenceInDays } from "date-fns";
-import { AnimatePresence, motion } from "framer-motion";
-import {
- ArrowUpRightIcon,
- LayoutListIcon,
- PaletteIcon,
- TangentIcon,
- type LucideIcon,
-} from "lucide-react";
-import Link from "next/link";
-import { useSelectedLayoutSegments } from "next/navigation";
-import { type ReactNode, useState } from "react";
+import { LayoutListIcon, PaletteIcon, TangentIcon } from "lucide-react";
import { SectionsList } from "#/src/lib/cuicui-components/sections-list";
import { firstMenuSection } from "#/src/lib/first-menu-section";
import { cn } from "#/src/utils/cn";
-
-import GradientContainer from "../gradient-container";
-import GradientText from "../gradient-text";
+import AnimatedBackground from "#/src/ui/navigation/animated-background";
+import {
+ GlobalNavItem,
+ SectionWrapper,
+} from "#/src/ui/navigation/navigation-item";
export default function NavigationMenu({
isMobile,
className,
}: Readonly<{ isMobile?: boolean; className?: string }>) {
- const [elementFocused, setElementFocused] = useState(
- null,
- );
- const handleHoverButton = (index: number | string | null) => {
- setElementFocused(index);
- };
-
- function getAlphabeticallySortedSectionList() {
- return SectionsList.map((section) => {
- return {
- ...section,
- items: section.categoriesList.toSorted((a, b) =>
- a.name.localeCompare(b.name),
- ),
- };
- });
- }
-
function getClosestUpdatedComponentDate(dateList: (Date | null)[]) {
return dateList.reduce((acc, date) => {
if (!date) {
@@ -52,51 +24,75 @@ export default function NavigationMenu({
}, null);
}
- const alphabeticallySortedSectionList = getAlphabeticallySortedSectionList();
-
return (
-