From 2a999b74fa00eaf090e2b975464179f3b4b0fc15 Mon Sep 17 00:00:00 2001 From: "eric.crowell" Date: Sat, 15 Jun 2024 22:44:55 +0200 Subject: [PATCH] dev: Adding search to navbar --- .storybook/preview.tsx | 6 +-- package.json | 3 +- packages/ui/package.json | 3 +- .../ui/src/Navigation/Navigation.stories.tsx | 23 +++++++++ packages/ui/src/Navigation/Navigation.tsx | 19 +++++-- .../ui/src/Navigation/NavigationIsland.tsx | 16 +++--- .../ui/src/Navigation/NavigationStandard.tsx | 15 +++++- .../Navigation/parts/NavigationPart_Brand.tsx | 24 ++++++++- .../Navigation/parts/NavigationPart_Links.tsx | 50 +++++++++++-------- .../parts/NavigationPart_Search.tsx | 28 +++++++++++ .../src/SearchInput/SearchInput.stories.tsx | 15 ++++++ packages/ui/src/SearchInput/SearchInput.tsx | 4 +- .../src/ThemeSwitch/ThemeSwitch.stories.tsx | 14 ++++++ packages/ui/src/ThemeSwitch/ThemeSwitch.tsx | 29 +++++++++++ packages/ui/src/context.ts | 39 +++++++++++++++ packages/ui/src/hooks.ts | 1 + packages/ui/src/hooks/useMode.ts | 49 ++++++++++++++++++ packages/ui/src/index.ts | 1 + packages/ui/src/provider.tsx | 44 +++++++++------- packages/ui/src/types.ts | 5 ++ pnpm-lock.yaml | 12 +---- tailwind.config.ts | 2 +- 22 files changed, 330 insertions(+), 72 deletions(-) create mode 100644 packages/ui/src/Navigation/parts/NavigationPart_Search.tsx create mode 100644 packages/ui/src/SearchInput/SearchInput.stories.tsx create mode 100644 packages/ui/src/ThemeSwitch/ThemeSwitch.stories.tsx create mode 100644 packages/ui/src/ThemeSwitch/ThemeSwitch.tsx create mode 100644 packages/ui/src/context.ts create mode 100644 packages/ui/src/hooks.ts create mode 100644 packages/ui/src/hooks/useMode.ts diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index b6abd3c..46595aa 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,7 +1,7 @@ import React from 'react'; import type { Preview } from '@storybook/react'; import { withThemeByClassName } from '@storybook/addon-themes'; -import { DoobProvider } from '../packages/ui/src/provider'; +import { DoobUiProvider } from '../packages/ui/src/provider'; import '../global.css'; const preview: Preview = { @@ -24,9 +24,9 @@ export const decorators = [ defaultTheme: 'light', }), (Story) => ( - + - + ), ]; diff --git a/package.json b/package.json index 90b4a7e..de072dc 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,9 @@ "author": "", "license": "MIT", "devDependencies": { - "@do-ob/core": "^1.0.0", + "@do-ob/core": "link:../core/packages/core", "@do-ob/eslint-config": "^2.3.0", "@do-ob/ts-config": "^2.0.0", - "@do-ob/ui": "workspace:*", "@do-ob/vite-lib-config": "^3.0.1", "@heroicons/react": "^2.1.3", "@nextui-org/react": "^2.4.1", diff --git a/packages/ui/package.json b/packages/ui/package.json index 35e6e92..6658fdd 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -12,7 +12,8 @@ "import": "./dist/*.js", "types": "./dist/*.d.ts" }, - "./index": null + "./index": null, + "./package.json": "./package.json" }, "keywords": [ "component", diff --git a/packages/ui/src/Navigation/Navigation.stories.tsx b/packages/ui/src/Navigation/Navigation.stories.tsx index 98baf5a..f9c0e27 100644 --- a/packages/ui/src/Navigation/Navigation.stories.tsx +++ b/packages/ui/src/Navigation/Navigation.stories.tsx @@ -22,6 +22,16 @@ const links: Link[] = [ { title: 'About', url: '#about', + links: [ + { + title: 'Company', + url: '#company', + }, + { + title: 'Team', + url: '#team', + }, + ], }, { title: 'Contact', @@ -67,6 +77,8 @@ export const Standard: Story = { args: { title: 'Navigation', links, + search: '#search', + className: 'bg-foreground/10', }, }; @@ -75,5 +87,16 @@ export const Island: Story = { title: 'Navigation', variant: 'island', links, + search: '#search', + className: 'bg-foreground/10', + }, +}; + +export const WithClassName: Story = { + args: { + title: 'Navigation', + variant: 'standard', + className: 'bg-red-500', + links, }, }; diff --git a/packages/ui/src/Navigation/Navigation.tsx b/packages/ui/src/Navigation/Navigation.tsx index c4912de..3573b23 100644 --- a/packages/ui/src/Navigation/Navigation.tsx +++ b/packages/ui/src/Navigation/Navigation.tsx @@ -5,10 +5,15 @@ import { NavigationIsland } from './NavigationIsland'; export interface NavigationProps { /** - * Title of the navigation + * The brand title to display */ title?: string; + /** + * The brand image to display + */ + image?: string; + /** * The theme color of the navigation */ @@ -18,6 +23,16 @@ export interface NavigationProps { * The links of the navigation */ links?: Link[]; + + /** + * Class name for the navigation + */ + className?: string; + + /** + * The search form action URL + */ + search?: string; } export interface NavigationVariantProps extends NavigationProps { @@ -39,5 +54,3 @@ export function Navigation({ return ; } } - -export default Navigation; diff --git a/packages/ui/src/Navigation/NavigationIsland.tsx b/packages/ui/src/Navigation/NavigationIsland.tsx index 83ac14a..4ac42d8 100644 --- a/packages/ui/src/Navigation/NavigationIsland.tsx +++ b/packages/ui/src/Navigation/NavigationIsland.tsx @@ -1,15 +1,19 @@ import { Navbar, NavbarContent } from '@nextui-org/react'; import { twColors } from '@do-ob/ui/utility'; -import { clsx } from '@do-ob/core'; +import { clsx, clmg } from '@do-ob/core'; import type { NavigationProps } from './Navigation'; import { NavigationPart_Brand } from './parts/NavigationPart_Brand'; import { NavigationPart_Links } from './parts/NavigationPart_Links'; +import { NavigationPart_Search } from './parts/NavigationPart_Search'; export function NavigationIsland({ title, + image, color, links, + className, + search, }: NavigationProps) { const [ colors ] = twColors(color); @@ -17,18 +21,18 @@ export function NavigationIsland({ return ( - + -
+
- + {search ? ( + + ) : null} ); } - -export default NavigationIsland; diff --git a/packages/ui/src/Navigation/NavigationStandard.tsx b/packages/ui/src/Navigation/NavigationStandard.tsx index e40fd56..877f8e0 100644 --- a/packages/ui/src/Navigation/NavigationStandard.tsx +++ b/packages/ui/src/Navigation/NavigationStandard.tsx @@ -1,20 +1,23 @@ import { Navbar, NavbarContent } from '@nextui-org/react'; import { twColors } from '@do-ob/ui/utility'; -import { clsx } from '@do-ob/core'; +import { clsx, clmg } from '@do-ob/core'; import type { NavigationProps } from './Navigation'; import { NavigationPart_Brand } from './parts/NavigationPart_Brand'; import { NavigationPart_Links } from './parts/NavigationPart_Links'; +import { NavigationPart_Search } from './parts/NavigationPart_Search'; export function NavigationStandard({ title, color, links, + className, + search }: NavigationProps) { const [ colors ] = twColors(color); return ( - + @@ -23,6 +26,14 @@ export function NavigationStandard({ + +
+ {search ? ( + + ) : null} +
+
+
); } diff --git a/packages/ui/src/Navigation/parts/NavigationPart_Brand.tsx b/packages/ui/src/Navigation/parts/NavigationPart_Brand.tsx index 01e3a92..a23f0b1 100644 --- a/packages/ui/src/Navigation/parts/NavigationPart_Brand.tsx +++ b/packages/ui/src/Navigation/parts/NavigationPart_Brand.tsx @@ -1,4 +1,6 @@ -import { NavbarBrand, Link } from '@nextui-org/react'; +import React from 'react'; +import { NavbarBrand, Link, Image } from '@nextui-org/react'; +import { DoobUiContext } from '@do-ob/ui/context'; /** * Navigation Brand properties @@ -8,6 +10,11 @@ export interface NavigationPart_BrandProps { * The branding title to display. */ title?: string; + + /** + * The branding image to display. + */ + image?: string; } /** @@ -15,10 +22,23 @@ export interface NavigationPart_BrandProps { */ export function NavigationPart_Brand({ title, + image, }: NavigationPart_BrandProps) { + + const { image: imageNode } = React.useContext(DoobUiContext); + return ( - {title} + {image ? ( + {title} + ) : null} + {title} ); } diff --git a/packages/ui/src/Navigation/parts/NavigationPart_Links.tsx b/packages/ui/src/Navigation/parts/NavigationPart_Links.tsx index 6b8e719..aab0292 100644 --- a/packages/ui/src/Navigation/parts/NavigationPart_Links.tsx +++ b/packages/ui/src/Navigation/parts/NavigationPart_Links.tsx @@ -1,4 +1,4 @@ -import { NavbarMenuItem, Link, Button, Popover, PopoverTrigger, PopoverContent } from '@nextui-org/react'; +import { NavbarMenuItem, Link, Button, Popover, PopoverTrigger, PopoverContent, Divider } from '@nextui-org/react'; import { ChevronDownIcon } from '@heroicons/react/24/solid'; import { Link as LinkType } from '@do-ob/ui/types'; import { clsx } from '@do-ob/core'; @@ -20,12 +20,12 @@ export interface NavigationPart_LinksProps { function LinkLeaf({ link }: { link: LinkType }) { return ( - + @@ -38,27 +38,34 @@ function LinkBranch({ links, level }: { links: LinkType[], level: number }) { const classes: string[] = []; if (level === 1) { classes.push('font-bold'); - classes.push('text-sm'); - } - - if (level === 2) { - classes.push('text-sm'); } - if(level > 2) { - classes.push('text-xs'); - } + const pl = level * 1; - return links.map((link) => (<> - + return links.map((link) => (
+ + {link.title} {link.links?.length && ( -
+
)} - )); +
)); } function LinkTrunk({ link, colors }: { link: LinkType, colors?: string }) { @@ -66,21 +73,22 @@ function LinkTrunk({ link, colors }: { link: LinkType, colors?: string }) { - + - - + + + @@ -104,5 +112,3 @@ export function NavigationPart_Links({ ); } - -export default NavigationPart_Links; diff --git a/packages/ui/src/Navigation/parts/NavigationPart_Search.tsx b/packages/ui/src/Navigation/parts/NavigationPart_Search.tsx new file mode 100644 index 0000000..d442876 --- /dev/null +++ b/packages/ui/src/Navigation/parts/NavigationPart_Search.tsx @@ -0,0 +1,28 @@ +import { MagnifyingGlassIcon } from '@heroicons/react/24/solid'; +import { Button, Modal, useDisclosure } from '@nextui-org/react'; + +/** + * Navigation Brand properties + */ +export interface NavigationPart_SearchProps { + /** + * The search form action URL. + */ + search?: string; +} + +/** + * Navigation Search component + */ +export function NavigationPart_Search({ + search = '#' +}: NavigationPart_SearchProps) { + + const { isOpen, onOpen } = useDisclosure(); + + return ( + + ); +} diff --git a/packages/ui/src/SearchInput/SearchInput.stories.tsx b/packages/ui/src/SearchInput/SearchInput.stories.tsx new file mode 100644 index 0000000..007b2b5 --- /dev/null +++ b/packages/ui/src/SearchInput/SearchInput.stories.tsx @@ -0,0 +1,15 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { SearchInput } from './SearchInput'; + +const meta = { + component: SearchInput, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: {} +}; diff --git a/packages/ui/src/SearchInput/SearchInput.tsx b/packages/ui/src/SearchInput/SearchInput.tsx index f27e25e..032e25a 100644 --- a/packages/ui/src/SearchInput/SearchInput.tsx +++ b/packages/ui/src/SearchInput/SearchInput.tsx @@ -20,10 +20,10 @@ export function SearchInput({ return ( ; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/packages/ui/src/ThemeSwitch/ThemeSwitch.tsx b/packages/ui/src/ThemeSwitch/ThemeSwitch.tsx new file mode 100644 index 0000000..50a5595 --- /dev/null +++ b/packages/ui/src/ThemeSwitch/ThemeSwitch.tsx @@ -0,0 +1,29 @@ +'use client'; + +import { useContext } from 'react'; +import { Switch } from '@nextui-org/react'; +import { MoonIcon, SunIcon } from '@heroicons/react/24/solid'; + +import { DoobUiContext } from '@do-ob/ui/context'; + +/** + * This switch is used to toggle the theme of the application. It toggles between 'light' and 'dark' + * class names that are applied to the html element of the document. + */ +export function ThemeSwitch() { + + const { mode, modeToggle } = useContext(DoobUiContext); + + return ( + } + endContent={} + > + Theme + + ); +} + +export default ThemeSwitch; diff --git a/packages/ui/src/context.ts b/packages/ui/src/context.ts new file mode 100644 index 0000000..deacc94 --- /dev/null +++ b/packages/ui/src/context.ts @@ -0,0 +1,39 @@ +import { ThemeMode } from '@do-ob/ui/types'; +import { nop } from '@do-ob/core'; +import { createContext } from 'react'; + +/** + * Context properties for the do-ob ui provider + */ +export interface DoobUiContextProps { + /** + * The image component to utilize for optimization + * + * This is useful in NextJS to pass in the Image component. + */ + image?: React.ElementType; + + /** + * The theme to use for the application. + */ + mode?: ThemeMode; + + /** + * Toggle the theme mode. + */ + modeToggle?: () => void; +} + +/** + * Default properties for the do-ob ui context + */ +export const doobUiContextDefaultProps: DoobUiContextProps = { + image: undefined, + mode: 'light', + modeToggle: nop, +}; + +/** + * The do-ob user interface (ui) context + */ +export const DoobUiContext = createContext(doobUiContextDefaultProps); \ No newline at end of file diff --git a/packages/ui/src/hooks.ts b/packages/ui/src/hooks.ts new file mode 100644 index 0000000..f2b6833 --- /dev/null +++ b/packages/ui/src/hooks.ts @@ -0,0 +1 @@ +export * from './hooks/useMode'; \ No newline at end of file diff --git a/packages/ui/src/hooks/useMode.ts b/packages/ui/src/hooks/useMode.ts new file mode 100644 index 0000000..3a97cb0 --- /dev/null +++ b/packages/ui/src/hooks/useMode.ts @@ -0,0 +1,49 @@ +import React from 'react'; +import type { ThemeMode } from '@do-ob/ui/types'; + +/** + * Provide methods to manage the mode of the application + */ +export function useMode(prefer: ThemeMode = 'light') { + const [ mode, modeSet ] = React.useState(prefer); + + React.useLayoutEffect(() => { + // Observe the theme mode class name of the html element + const observer = new MutationObserver(() => { + const next = document.documentElement.classList.contains('dark') ? 'dark' : 'light'; + if (next !== mode) { + modeSet(next); + } + }); + // Start observing the theme mode class name of the html element + observer.observe(document.documentElement, { attributes: true, attributeFilter: [ 'class' ] }); + + // Check if the html element already has a mode class name. + if (document.documentElement.classList.contains('light') || document.documentElement.classList.contains('dark')) { + const documentTheme = document.documentElement.classList.contains('dark') ? 'dark' : 'light'; + modeSet(documentTheme); + } + + // Clean up the observer + return () => observer.disconnect(); + }, []); + + const modeToggle = () => { + const next = mode === 'light' ? 'dark' : 'light'; + modeSet(next); + }; + + // Every time the mode changes, store it in the local storage + React.useEffect(() => { + document.documentElement.classList.remove(mode === 'light' ? 'dark' : 'light'); + document.documentElement.classList.add(mode); + }, [ mode ]); + + return { + mode, + modeSet, + modeToggle + }; +} + +export default useMode; \ No newline at end of file diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index dd14015..acdc5ff 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -1,3 +1,4 @@ export { Navigation } from './Navigation/Navigation'; export { NavigationStandard } from './Navigation/NavigationStandard'; export { NavigationIsland } from './Navigation/NavigationIsland'; +export { SearchInput } from './SearchInput/SearchInput'; \ No newline at end of file diff --git a/packages/ui/src/provider.tsx b/packages/ui/src/provider.tsx index f2188f3..e066df6 100644 --- a/packages/ui/src/provider.tsx +++ b/packages/ui/src/provider.tsx @@ -1,22 +1,23 @@ import { NextUIProvider, NextUIProviderProps } from '@nextui-org/react'; -import React from 'react'; +import { DoobUiContext } from '@do-ob/ui/context'; +import type { ThemeMode } from '@do-ob/ui/types'; +import { useMode } from '@do-ob/ui/hooks'; -export interface doobContextProps { +export interface DoobUiProviderProps { /** - * The image component to utilize for optimization - * - * This is useful in NextJS to pass in the Image component. + * Set the image component to utilize for optimization */ - imageNode?: React.ReactNode; -} - -export const doobContextDefaultProps: doobContextProps = { - imageNode: undefined -}; + image?: React.ElementType; -export const DoobContext = React.createContext(doobContextDefaultProps); + /** + * Set the initial theme mode to use for the application. + * + * Changing this value later will not change the theme mode. + * + * @default 'light' + */ + mode?: ThemeMode; -export interface DoobProviderProps extends doobContextProps { /** * NextUI Provider properties. * @@ -25,20 +26,27 @@ export interface DoobProviderProps extends doobContextProps { nextui?: NextUIProviderProps; } +'use client'; /** * The provider for the doob context */ -export function DoobProvider({ - children, +export function DoobUiProvider({ nextui, + children, ...contextValue -}: React.PropsWithChildren) { +}: React.PropsWithChildren) { + + const { mode, modeToggle } = useMode(contextValue.mode); return ( - + {children} - + ); } diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts index 2c90d05..9b3ec6f 100644 --- a/packages/ui/src/types.ts +++ b/packages/ui/src/types.ts @@ -1,3 +1,8 @@ +/** + * Theme mode. + */ +export type ThemeMode = 'light' | 'dark'; + /** * Theem colors. */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0f6fe76..fb6f2f1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,17 +9,14 @@ importers: .: devDependencies: '@do-ob/core': - specifier: ^1.0.0 - version: 1.0.0 + specifier: link:../core/packages/core + version: link:../core/packages/core '@do-ob/eslint-config': specifier: ^2.3.0 version: 2.3.0(eslint@9.4.0)(tailwindcss@3.4.4)(typescript@5.4.5) '@do-ob/ts-config': specifier: ^2.0.0 version: 2.0.0(typescript@5.4.5) - '@do-ob/ui': - specifier: workspace:* - version: link:packages/ui '@do-ob/vite-lib-config': specifier: ^3.0.1 version: 3.0.1(@types/node@20.14.2)(rollup@4.18.0)(typescript@5.4.5)(vite@5.2.13(@types/node@20.14.2)(terser@5.31.1)) @@ -794,9 +791,6 @@ packages: resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} - '@do-ob/core@1.0.0': - resolution: {integrity: sha512-QO6V4GwZ3AOVp/DEwBz82CpsdCZVgFRfnfms7ZMK07fpUzD/7R39jWkULy5rqGZnxhps11dhKLGbQmI6LX/wlg==} - '@do-ob/eslint-config@2.3.0': resolution: {integrity: sha512-jezw0GnatrViICC8tauRJDAh1qVmwMHL6VTgwJ12HGuYRlwKCRTW0EJuU73IFekR8z7bro0FAaGY9n9MinbJcQ==} engines: {node: '>=20'} @@ -6901,8 +6895,6 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} - '@do-ob/core@1.0.0': {} - '@do-ob/eslint-config@2.3.0(eslint@9.4.0)(tailwindcss@3.4.4)(typescript@5.4.5)': dependencies: '@eslint/js': 9.4.0 diff --git a/tailwind.config.ts b/tailwind.config.ts index 13300cd..5506d90 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -14,7 +14,7 @@ const config: Config = { darkMode: 'class', content: [ './node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}', - './packages/ui/src/**/*.{js,ts,jsx,tsx,mdx}', + './packages/ui/src/**/*.{js,ts,jsx,tsx}', ], plugins: [ nextui(),