mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
feat(navbar): in progress
This commit is contained in:
parent
7d3289eb52
commit
5be0df4ab3
@ -147,7 +147,7 @@ const InsideScrollTemplate: ComponentStory<typeof Modal> = (args: ModalProps) =>
|
||||
<>
|
||||
<ModalHeader>Modal Title</ModalHeader>
|
||||
<ModalBody>
|
||||
<Lorem size={5} />
|
||||
<Lorem count={5} />
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button onPress={onClose}>Close</Button>
|
||||
@ -168,7 +168,7 @@ const OutsideScrollTemplate: ComponentStory<typeof Modal> = (args: ModalProps) =
|
||||
<>
|
||||
<ModalHeader>Modal Title</ModalHeader>
|
||||
<ModalBody>
|
||||
<Lorem size={5} />
|
||||
<Lorem count={5} />
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button onPress={onClose}>Close</Button>
|
||||
@ -193,7 +193,7 @@ const OpenChangeTemplate: ComponentStory<typeof Modal> = (args: ModalProps) => {
|
||||
<>
|
||||
<ModalHeader>Modal Title</ModalHeader>
|
||||
<ModalBody>
|
||||
<Lorem size={5} />
|
||||
<Lorem count={5} />
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button onPress={onClose}>Close</Button>
|
||||
|
||||
24
packages/components/navbar/README.md
Normal file
24
packages/components/navbar/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# @nextui-org/navbar
|
||||
|
||||
A Quick description of the component
|
||||
|
||||
> This is an internal utility, not intended for public usage.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
yarn add @nextui-org/navbar
|
||||
# or
|
||||
npm i @nextui-org/navbar
|
||||
```
|
||||
|
||||
## Contribution
|
||||
|
||||
Yes please! See the
|
||||
[contributing guidelines](https://github.com/nextui-org/nextui/blob/master/CONTRIBUTING.md)
|
||||
for details.
|
||||
|
||||
## Licence
|
||||
|
||||
This project is licensed under the terms of the
|
||||
[MIT license](https://github.com/nextui-org/nextui/blob/master/LICENSE).
|
||||
19
packages/components/navbar/__tests__/navbar.test.tsx
Normal file
19
packages/components/navbar/__tests__/navbar.test.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import * as React from "react";
|
||||
import {render} from "@testing-library/react";
|
||||
|
||||
import {Navbar} from "../src";
|
||||
|
||||
describe("Navbar", () => {
|
||||
it("should render correctly", () => {
|
||||
const wrapper = render(<Navbar />);
|
||||
|
||||
expect(() => wrapper.unmount()).not.toThrow();
|
||||
});
|
||||
|
||||
it("ref should be forwarded", () => {
|
||||
const ref = React.createRef<HTMLDivElement>();
|
||||
|
||||
render(<Navbar ref={ref} />);
|
||||
expect(ref.current).not.toBeNull();
|
||||
});
|
||||
});
|
||||
66
packages/components/navbar/package.json
Normal file
66
packages/components/navbar/package.json
Normal file
@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "@nextui-org/navbar",
|
||||
"version": "2.0.0-beta.1",
|
||||
"description": "A responsive navigation header positioned on top side of your page that includes support for branding, links, navigation, collapse and more.",
|
||||
"keywords": [
|
||||
"navbar"
|
||||
],
|
||||
"author": "Junior Garcia <jrgarciadev@gmail.com>",
|
||||
"homepage": "https://nextui.org",
|
||||
"license": "MIT",
|
||||
"main": "src/index.ts",
|
||||
"sideEffects": false,
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/nextui-org/nextui.git",
|
||||
"directory": "packages/components/navbar"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextui-org/nextui/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup src --dts",
|
||||
"build:fast": "tsup src",
|
||||
"dev": "yarn build:fast -- --watch",
|
||||
"clean": "rimraf dist .turbo",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"prepack": "clean-package",
|
||||
"postpack": "clean-package restore"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nextui-org/system": "workspace:*",
|
||||
"@nextui-org/theme": "workspace:*",
|
||||
"@nextui-org/shared-utils": "workspace:*",
|
||||
"@nextui-org/dom-utils": "workspace:*",
|
||||
"@nextui-org/use-scroll-position": "workspace:*",
|
||||
"@react-aria/utils": "^3.16.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nextui-org/link": "workspace:*",
|
||||
"@nextui-org/avatar": "workspace:*",
|
||||
"@nextui-org/dropdown": "workspace:*",
|
||||
"@nextui-org/button": "workspace:*",
|
||||
"@nextui-org/input": "workspace:*",
|
||||
"react-lorem-component": "0.13.0",
|
||||
"clean-package": "2.2.0",
|
||||
"react": "^18.0.0"
|
||||
},
|
||||
"clean-package": "../../../clean-package.config.json",
|
||||
"tsup": {
|
||||
"clean": true,
|
||||
"target": "es2019",
|
||||
"format": [
|
||||
"cjs",
|
||||
"esm"
|
||||
]
|
||||
}
|
||||
}
|
||||
17
packages/components/navbar/src/index.ts
Normal file
17
packages/components/navbar/src/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
// export types
|
||||
export type {NavbarProps} from "./navbar";
|
||||
export type {NavbarBrandProps} from "./navbar-brand";
|
||||
export type {NavbarContentProps} from "./navbar-content";
|
||||
export type {NavbarItemProps} from "./navbar-item";
|
||||
|
||||
// export hooks
|
||||
export {useNavbar} from "./use-navbar";
|
||||
|
||||
// export context
|
||||
export * from "./navbar-context";
|
||||
|
||||
// export components
|
||||
export {default as Navbar} from "./navbar";
|
||||
export {default as NavbarBrand} from "./navbar-brand";
|
||||
export {default as NavbarContent} from "./navbar-content";
|
||||
export {default as NavbarItem} from "./navbar-item";
|
||||
30
packages/components/navbar/src/navbar-brand.tsx
Normal file
30
packages/components/navbar/src/navbar-brand.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import {forwardRef, HTMLNextUIProps} from "@nextui-org/system";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
|
||||
import {useNavbarContext} from "./navbar-context";
|
||||
|
||||
export interface NavbarBrandProps extends HTMLNextUIProps<"div"> {
|
||||
children?: React.ReactNode | React.ReactNode[];
|
||||
}
|
||||
|
||||
const NavbarBrand = forwardRef<NavbarBrandProps, "div">((props, ref) => {
|
||||
const {as, className, children, ...otherProps} = props;
|
||||
|
||||
const Component = as || "div";
|
||||
const domRef = useDOMRef(ref);
|
||||
|
||||
const {slots, classNames} = useNavbarContext();
|
||||
|
||||
const styles = clsx(classNames?.brand, className);
|
||||
|
||||
return (
|
||||
<Component ref={domRef} className={slots.brand?.({class: styles})} {...otherProps}>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
});
|
||||
|
||||
NavbarBrand.displayName = "NextUI.NavbarBrand";
|
||||
|
||||
export default NavbarBrand;
|
||||
33
packages/components/navbar/src/navbar-content.tsx
Normal file
33
packages/components/navbar/src/navbar-content.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import {forwardRef, HTMLNextUIProps} from "@nextui-org/system";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
|
||||
import {useNavbarContext} from "./navbar-context";
|
||||
|
||||
export interface NavbarContentProps extends HTMLNextUIProps<"ul"> {
|
||||
/**
|
||||
* The content of the Navbar.Content. It is usually the `NavbarItem`,
|
||||
*/
|
||||
children?: React.ReactNode | React.ReactNode[];
|
||||
}
|
||||
|
||||
const NavbarContent = forwardRef<NavbarContentProps, "ul">((props, ref) => {
|
||||
const {as, className, children, ...otherProps} = props;
|
||||
|
||||
const Component = as || "ul";
|
||||
const domRef = useDOMRef(ref);
|
||||
|
||||
const {slots, classNames} = useNavbarContext();
|
||||
|
||||
const styles = clsx(classNames?.content, className);
|
||||
|
||||
return (
|
||||
<Component ref={domRef} className={slots.content?.({class: styles})} {...otherProps}>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
});
|
||||
|
||||
NavbarContent.displayName = "NextUI.NavbarContent";
|
||||
|
||||
export default NavbarContent;
|
||||
10
packages/components/navbar/src/navbar-context.ts
Normal file
10
packages/components/navbar/src/navbar-context.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import {createContext} from "@nextui-org/shared-utils";
|
||||
|
||||
import {ContextType} from "./use-navbar";
|
||||
|
||||
export const [NavbarProvider, useNavbarContext] = createContext<ContextType>({
|
||||
name: "NavbarContext",
|
||||
strict: true,
|
||||
errorMessage:
|
||||
"useNavbarContext: `context` is undefined. Seems you forgot to wrap component within <Navbar />",
|
||||
});
|
||||
44
packages/components/navbar/src/navbar-item.tsx
Normal file
44
packages/components/navbar/src/navbar-item.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import {forwardRef, HTMLNextUIProps} from "@nextui-org/system";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {clsx, dataAttr} from "@nextui-org/shared-utils";
|
||||
import {useId} from "react";
|
||||
|
||||
import {useNavbarContext} from "./navbar-context";
|
||||
|
||||
export interface NavbarItemProps extends HTMLNextUIProps<"li"> {
|
||||
children?: React.ReactNode;
|
||||
/**
|
||||
* Whether the item is active or not.
|
||||
* @default false
|
||||
*/
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
const NavbarItem = forwardRef<NavbarItemProps, "li">((props, ref) => {
|
||||
const {as, className, children, isActive, ...otherProps} = props;
|
||||
|
||||
const Component = as || "li";
|
||||
const domRef = useDOMRef(ref);
|
||||
|
||||
const itemId = useId();
|
||||
|
||||
const {slots, classNames} = useNavbarContext();
|
||||
|
||||
const styles = clsx(classNames?.item, className);
|
||||
|
||||
return (
|
||||
<Component
|
||||
ref={domRef}
|
||||
className={slots.item?.({class: styles})}
|
||||
data-active={dataAttr(isActive)}
|
||||
id={itemId}
|
||||
{...otherProps}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
});
|
||||
|
||||
NavbarItem.displayName = "NextUI.NavbarItem";
|
||||
|
||||
export default NavbarItem;
|
||||
26
packages/components/navbar/src/navbar.tsx
Normal file
26
packages/components/navbar/src/navbar.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import {forwardRef} from "@nextui-org/system";
|
||||
|
||||
import {UseNavbarProps, useNavbar} from "./use-navbar";
|
||||
import {NavbarProvider} from "./navbar-context";
|
||||
|
||||
export interface NavbarProps extends Omit<UseNavbarProps, "ref"> {
|
||||
children?: React.ReactNode | React.ReactNode[];
|
||||
}
|
||||
|
||||
const Navbar = forwardRef<NavbarProps, "div">((props, ref) => {
|
||||
const {children, ...otherProps} = props;
|
||||
|
||||
const {Component, getBaseProps, getWrapperProps, context} = useNavbar({ref, ...otherProps});
|
||||
|
||||
return (
|
||||
<NavbarProvider value={context}>
|
||||
<Component {...getBaseProps()}>
|
||||
<header {...getWrapperProps()}>{children}</header>
|
||||
</Component>
|
||||
</NavbarProvider>
|
||||
);
|
||||
});
|
||||
|
||||
Navbar.displayName = "NextUI.Navbar";
|
||||
|
||||
export default Navbar;
|
||||
124
packages/components/navbar/src/use-navbar.ts
Normal file
124
packages/components/navbar/src/use-navbar.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import type {NavbarVariantProps, SlotsToClasses, NavbarSlots} from "@nextui-org/theme";
|
||||
|
||||
import {HTMLNextUIProps, mapPropsVariants, PropGetter} from "@nextui-org/system";
|
||||
import {navbar} from "@nextui-org/theme";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {clsx, ReactRef} from "@nextui-org/shared-utils";
|
||||
import {useMemo, useState} from "react";
|
||||
import {mergeProps} from "@react-aria/utils";
|
||||
import {useScrollPosition} from "@nextui-org/use-scroll-position";
|
||||
|
||||
export interface UseNavbarProps extends HTMLNextUIProps<"nav", NavbarVariantProps> {
|
||||
/**
|
||||
* Ref to the DOM node.
|
||||
*/
|
||||
ref?: ReactRef<HTMLElement | null>;
|
||||
/**
|
||||
* The parent element where the navbar is placed within.
|
||||
* This is used to determine the scroll position and whether the navbar should be hidden or not.
|
||||
* @default `window`
|
||||
*/
|
||||
parentRef?: React.RefObject<HTMLElement>;
|
||||
/**
|
||||
* Whether the navbar should hide on scroll or not.
|
||||
* @default false
|
||||
*/
|
||||
shouldHideOnScroll?: boolean;
|
||||
/**
|
||||
* Whether the navbar parent scroll event should be listened to or not.
|
||||
* @default false
|
||||
*/
|
||||
disableScrollHandler?: boolean;
|
||||
/**
|
||||
* The scroll event handler for the navbar. The event fires when the navbar parent element is scrolled.
|
||||
* it only works if `disableScrollHandler` is set to `false` or `shouldHideOnScroll` is set to `true`.
|
||||
*/
|
||||
onScrollPositionChange?: (scrollPosition: number) => void;
|
||||
/**
|
||||
* Classname or List of classes to change the classNames of the element.
|
||||
* if `className` is passed, it will be added to the base slot.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* <Navbar classNames={{
|
||||
* base:"base-classes",
|
||||
* wrapper: "wrapper-classes",
|
||||
* brand: "brand-classes",
|
||||
* }} />
|
||||
* ```
|
||||
*/
|
||||
classNames?: SlotsToClasses<NavbarSlots>;
|
||||
}
|
||||
|
||||
export type ContextType = {
|
||||
slots: ReturnType<typeof navbar>;
|
||||
classNames?: SlotsToClasses<NavbarSlots>;
|
||||
};
|
||||
|
||||
export function useNavbar(originalProps: UseNavbarProps) {
|
||||
const [props, variantProps] = mapPropsVariants(originalProps, navbar.variantKeys);
|
||||
|
||||
const {
|
||||
ref,
|
||||
as,
|
||||
parentRef,
|
||||
shouldHideOnScroll = false,
|
||||
disableScrollHandler = false,
|
||||
onScrollPositionChange,
|
||||
className,
|
||||
classNames,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const Component = as || "nav";
|
||||
|
||||
const domRef = useDOMRef(ref);
|
||||
|
||||
const [isSticky, setIsSticky] = useState(false);
|
||||
|
||||
const slots = useMemo(
|
||||
() =>
|
||||
navbar({
|
||||
...variantProps,
|
||||
position: isSticky ? "sticky" : originalProps?.position,
|
||||
}),
|
||||
[...Object.values(variantProps), isSticky],
|
||||
);
|
||||
|
||||
const context: ContextType = {
|
||||
slots,
|
||||
classNames,
|
||||
};
|
||||
|
||||
const baseStyles = clsx(classNames?.base, className);
|
||||
|
||||
useScrollPosition({
|
||||
elementRef: parentRef,
|
||||
isEnabled: shouldHideOnScroll || !disableScrollHandler,
|
||||
callback: ({prevPos, currPos}) => {
|
||||
onScrollPositionChange?.(currPos.y);
|
||||
if (shouldHideOnScroll) {
|
||||
setIsSticky((prev) => {
|
||||
const next = currPos.y > prevPos.y;
|
||||
|
||||
return next !== prev ? next : prev;
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const getBaseProps: PropGetter = (props = {}) => ({
|
||||
...mergeProps(otherProps, props),
|
||||
ref: domRef,
|
||||
className: slots.base({class: clsx(baseStyles, props?.className)}),
|
||||
});
|
||||
|
||||
const getWrapperProps: PropGetter = (props = {}) => ({
|
||||
...props,
|
||||
className: slots.wrapper({class: clsx(classNames?.wrapper, props?.className)}),
|
||||
});
|
||||
|
||||
return {Component, slots, domRef, context, getBaseProps, getWrapperProps};
|
||||
}
|
||||
|
||||
export type UseNavbarReturn = ReturnType<typeof useNavbar>;
|
||||
116
packages/components/navbar/stories/navbar.stories.tsx
Normal file
116
packages/components/navbar/stories/navbar.stories.tsx
Normal file
@ -0,0 +1,116 @@
|
||||
import React from "react";
|
||||
import {ComponentStory, ComponentMeta} from "@storybook/react";
|
||||
import {navbar} from "@nextui-org/theme";
|
||||
import {Link} from "@nextui-org/link";
|
||||
import {Button} from "@nextui-org/button";
|
||||
import Lorem from "react-lorem-component";
|
||||
|
||||
import {Navbar, NavbarBrand, NavbarContent, NavbarItem, NavbarProps} from "../src";
|
||||
|
||||
export default {
|
||||
title: "Components/Navbar",
|
||||
component: Navbar,
|
||||
argTypes: {
|
||||
position: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["static", "sticky", "floating"],
|
||||
},
|
||||
},
|
||||
maxWidth: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["sm", "md", "lg", "xl", "2xl", "full"],
|
||||
},
|
||||
},
|
||||
isBlurred: {
|
||||
control: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="flex justify-center w-screen h-screen">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
} as ComponentMeta<typeof Navbar>;
|
||||
|
||||
const defaultProps = {
|
||||
...navbar.defaultVariants,
|
||||
};
|
||||
|
||||
const AcmeLogo = () => (
|
||||
<svg fill="none" height="36" viewBox="0 0 32 32" width="36">
|
||||
<path
|
||||
clipRule="evenodd"
|
||||
d="M17.6482 10.1305L15.8785 7.02583L7.02979 22.5499H10.5278L17.6482 10.1305ZM19.8798 14.0457L18.11 17.1983L19.394 19.4511H16.8453L15.1056 22.5499H24.7272L19.8798 14.0457Z"
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const App = React.forwardRef(({children}: any, ref: any) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="max-w-[920px] max-h-[600px] overflow-x-hidden overflow-y-scroll shadow-md relative border border-neutral"
|
||||
>
|
||||
{children}
|
||||
<div className="flex flex-col gap-4 px-10 mt-8">
|
||||
<h1>Lorem ipsum dolor sit ame</h1>
|
||||
{[1, 2, 3, 4, 5, 6, 7].map((i) => (
|
||||
<Lorem key={i} className="mb-5 text-lg" count={1} sentenceUpperBound={40} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
App.displayName = "App";
|
||||
|
||||
const Template: ComponentStory<typeof Navbar> = (args: NavbarProps) => (
|
||||
<App>
|
||||
<Navbar {...args}>
|
||||
<NavbarBrand>
|
||||
<AcmeLogo />
|
||||
<p className="font-bold hidden sm:block text-inherit">ACME</p>
|
||||
</NavbarBrand>
|
||||
<NavbarContent className="hidden sm:flex">
|
||||
<NavbarItem as={Link} color="foreground" href="#">
|
||||
Features
|
||||
</NavbarItem>
|
||||
<NavbarItem isActive as={Link} href="#">
|
||||
Customers
|
||||
</NavbarItem>
|
||||
<NavbarItem as={Link} color="foreground" href="#">
|
||||
Integrations
|
||||
</NavbarItem>
|
||||
<NavbarItem as={Link} color="foreground" href="#">
|
||||
Pricing
|
||||
</NavbarItem>
|
||||
<NavbarItem as={Link} color="foreground" href="#">
|
||||
Company
|
||||
</NavbarItem>
|
||||
</NavbarContent>
|
||||
<NavbarContent>
|
||||
<NavbarItem as={Link} href="#">
|
||||
Login
|
||||
</NavbarItem>
|
||||
<NavbarItem>
|
||||
<Button as={Link} color="primary" href="#" variant="flat">
|
||||
Sign Up
|
||||
</Button>
|
||||
</NavbarItem>
|
||||
</NavbarContent>
|
||||
</Navbar>
|
||||
</App>
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
...defaultProps,
|
||||
};
|
||||
10
packages/components/navbar/tsconfig.json
Normal file
10
packages/components/navbar/tsconfig.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"tailwind-variants": ["../../../node_modules/tailwind-variants"]
|
||||
}
|
||||
},
|
||||
"include": ["src", "index.ts"]
|
||||
}
|
||||
@ -245,7 +245,7 @@ const RadioCard = (props: RadioProps) => {
|
||||
<Component
|
||||
{...getBaseProps()}
|
||||
className={clsx(
|
||||
"inline-flex items-center justify-between hover:bg-content2 flex-row-reverse max-w-[300px] cursor-pointer border-2 border-neutral rounded-lg gap-4 p-4",
|
||||
"group inline-flex items-center justify-between hover:bg-content2 flex-row-reverse max-w-[300px] cursor-pointer border-2 border-neutral rounded-lg gap-4 p-4",
|
||||
{
|
||||
"border-primary": isSelected,
|
||||
},
|
||||
|
||||
@ -29,3 +29,4 @@ export * from "./dropdown-section";
|
||||
export * from "./dropdown-menu";
|
||||
export * from "./image";
|
||||
export * from "./modal";
|
||||
export * from "./navbar";
|
||||
|
||||
130
packages/core/theme/src/components/navbar.ts
Normal file
130
packages/core/theme/src/components/navbar.ts
Normal file
@ -0,0 +1,130 @@
|
||||
import type {VariantProps} from "tailwind-variants";
|
||||
|
||||
import {tv} from "tailwind-variants";
|
||||
|
||||
/**
|
||||
* Card **Tailwind Variants** component
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const {base, wrapper, brand, content, item} = navbar({...})
|
||||
*
|
||||
* <nav className={base()}>
|
||||
* <div className={wrapper()}>
|
||||
* <div className={brand()}>Brand</div>
|
||||
* <ul className={content()}>
|
||||
* <li className={item()}>Item 1</li>
|
||||
* <li className={item()}>Item 2</li>
|
||||
* <li className={item()}>Item 3</li>
|
||||
* </ul>
|
||||
* </div>
|
||||
* </nav>
|
||||
* ```
|
||||
*/
|
||||
const navbar = tv({
|
||||
slots: {
|
||||
base: [
|
||||
"relative",
|
||||
"z-20",
|
||||
"w-full",
|
||||
"h-auto",
|
||||
"flex",
|
||||
"items-center",
|
||||
"justify-center",
|
||||
"border-b",
|
||||
"border-neutral-100",
|
||||
"shadow-lg",
|
||||
],
|
||||
wrapper: [
|
||||
"flex",
|
||||
"flex-row",
|
||||
"flex-nowrap",
|
||||
"items-center",
|
||||
"justify-between",
|
||||
"w-full",
|
||||
"h-16",
|
||||
"px-6",
|
||||
],
|
||||
brand: [
|
||||
"flex",
|
||||
"flex-row",
|
||||
"flex-nowrap",
|
||||
"justify-start",
|
||||
"bg-transparent",
|
||||
"items-center",
|
||||
"no-underline",
|
||||
"text-base",
|
||||
"whitespace-nowrap",
|
||||
"box-border",
|
||||
],
|
||||
content: ["flex", "h-full", "flex-row", "flex-nowrap", "items-center", "gap-4"],
|
||||
item: [
|
||||
"text-base",
|
||||
"whitespace-nowrap",
|
||||
"box-border",
|
||||
// active
|
||||
"data-[active=true]:font-semibold",
|
||||
],
|
||||
},
|
||||
variants: {
|
||||
position: {
|
||||
static: {
|
||||
base: "static",
|
||||
},
|
||||
sticky: {},
|
||||
floating: {
|
||||
base: "shadow-none border-b-0",
|
||||
wrapper: "mt-4 mx-8 shadow-lg border border-neutral-100 rounded-xl",
|
||||
},
|
||||
},
|
||||
maxWidth: {
|
||||
sm: {
|
||||
wrapper: "max-w-[640px]",
|
||||
},
|
||||
md: {
|
||||
wrapper: "max-w-[768px]",
|
||||
},
|
||||
lg: {
|
||||
wrapper: "max-w-[1024px]",
|
||||
},
|
||||
xl: {
|
||||
wrapper: "max-w-[1280px]",
|
||||
},
|
||||
"2xl": {
|
||||
wrapper: "max-w-[1536px]",
|
||||
},
|
||||
full: {
|
||||
wrapper: "max-w-full",
|
||||
},
|
||||
},
|
||||
isBordered: {
|
||||
true: {},
|
||||
},
|
||||
isBlurred: {
|
||||
false: {
|
||||
base: "bg-background",
|
||||
},
|
||||
true: {
|
||||
base: "backdrop-blur-xl backdrop-saturate-200 bg-background/50",
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
maxWidth: "lg",
|
||||
position: "sticky",
|
||||
isBlurred: true,
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
position: ["sticky", "floating"],
|
||||
class: {
|
||||
base: "sticky top-0 inset-x-0",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export type NavbarVariantProps = VariantProps<typeof navbar>;
|
||||
export type NavbarSlots = keyof ReturnType<typeof navbar>;
|
||||
|
||||
export {navbar};
|
||||
24
packages/hooks/use-scroll-position/README.md
Normal file
24
packages/hooks/use-scroll-position/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# @nextui-org/use-scroll-position
|
||||
|
||||
A Quick description of the component
|
||||
|
||||
> This is an internal utility, not intended for public usage.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
yarn add @nextui-org/use-scroll-position
|
||||
# or
|
||||
npm i @nextui-org/use-scroll-position
|
||||
```
|
||||
|
||||
## Contribution
|
||||
|
||||
Yes please! See the
|
||||
[contributing guidelines](https://github.com/nextui-org/nextui/blob/master/CONTRIBUTING.md)
|
||||
for details.
|
||||
|
||||
## Licence
|
||||
|
||||
This project is licensed under the terms of the
|
||||
[MIT license](https://github.com/nextui-org/nextui/blob/master/LICENSE).
|
||||
52
packages/hooks/use-scroll-position/package.json
Normal file
52
packages/hooks/use-scroll-position/package.json
Normal file
@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "@nextui-org/use-scroll-position",
|
||||
"version": "2.0.0-beta.1",
|
||||
"description": "Provides the logic to control the scroll over an element",
|
||||
"keywords": [
|
||||
"use-scroll-position"
|
||||
],
|
||||
"author": "Junior Garcia <jrgarciadev@gmail.com>",
|
||||
"homepage": "https://nextui.org",
|
||||
"license": "MIT",
|
||||
"main": "src/index.ts",
|
||||
"sideEffects": false,
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/nextui-org/nextui.git",
|
||||
"directory": "packages/hooks/use-scroll-position"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextui-org/nextui/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup src --dts",
|
||||
"build:fast": "tsup src",
|
||||
"dev": "yarn build:fast -- --watch",
|
||||
"clean": "rimraf dist .turbo",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"prepack": "clean-package",
|
||||
"postpack": "clean-package restore"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"clean-package": "2.2.0",
|
||||
"react": "^18.0.0"
|
||||
},
|
||||
"clean-package": "../../../clean-package.config.json",
|
||||
"tsup": {
|
||||
"clean": true,
|
||||
"target": "es2019",
|
||||
"format": [
|
||||
"cjs",
|
||||
"esm"
|
||||
]
|
||||
}
|
||||
}
|
||||
78
packages/hooks/use-scroll-position/src/index.ts
Normal file
78
packages/hooks/use-scroll-position/src/index.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import {useRef, useEffect} from "react";
|
||||
|
||||
const isBrowser = typeof window !== "undefined";
|
||||
|
||||
export type ScrollValue = {x: any; y: any};
|
||||
|
||||
function getScrollPosition(element: HTMLElement | undefined | null): ScrollValue {
|
||||
if (!isBrowser) return {x: 0, y: 0};
|
||||
if (!element) {
|
||||
return {x: window.scrollX, y: window.scrollY};
|
||||
}
|
||||
|
||||
return {x: element.scrollLeft, y: element.scrollTop};
|
||||
}
|
||||
|
||||
export interface UseScrollPositionOptions {
|
||||
/**
|
||||
* The wait time in milliseconds before triggering the callback.
|
||||
* @default 30
|
||||
*/
|
||||
delay?: number;
|
||||
/**
|
||||
* Whether the scroll position should be tracked or not.
|
||||
* @default true
|
||||
*/
|
||||
isEnabled?: boolean;
|
||||
/**
|
||||
* The element to track the scroll position for.
|
||||
*/
|
||||
elementRef?: React.RefObject<HTMLElement> | null;
|
||||
/**
|
||||
* The callback function to be called when the scroll position changes.
|
||||
*/
|
||||
callback?: ({prevPos, currPos}: {prevPos: ScrollValue; currPos: ScrollValue}) => void;
|
||||
}
|
||||
|
||||
export const useScrollPosition = (props: UseScrollPositionOptions): ScrollValue => {
|
||||
const {elementRef, delay = 30, callback, isEnabled} = props;
|
||||
|
||||
const position = useRef<ScrollValue>(
|
||||
isEnabled ? getScrollPosition(elementRef?.current) : {x: 0, y: 0},
|
||||
);
|
||||
|
||||
let throttleTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const handler = () => {
|
||||
const currPos = getScrollPosition(elementRef?.current);
|
||||
|
||||
if (typeof callback === "function") {
|
||||
callback({prevPos: position.current, currPos});
|
||||
}
|
||||
|
||||
position.current = currPos;
|
||||
throttleTimeout = null;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEnabled) return;
|
||||
|
||||
const handleScroll = () => {
|
||||
if (delay) {
|
||||
if (throttleTimeout === null) {
|
||||
throttleTimeout = setTimeout(handler, delay);
|
||||
}
|
||||
} else {
|
||||
handler();
|
||||
}
|
||||
};
|
||||
|
||||
const target = elementRef?.current || window;
|
||||
|
||||
target.addEventListener("scroll", handleScroll);
|
||||
|
||||
return () => target.removeEventListener("scroll", handleScroll);
|
||||
}, [elementRef?.current, delay, isEnabled]);
|
||||
|
||||
return position.current;
|
||||
};
|
||||
4
packages/hooks/use-scroll-position/tsconfig.json
Normal file
4
packages/hooks/use-scroll-position/tsconfig.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"include": ["src", "index.ts"]
|
||||
}
|
||||
@ -136,5 +136,5 @@ import {link, button} from "@nextui-org/theme";
|
||||
<br />
|
||||
|
||||
<div class="block text-xs text-neutral-400">
|
||||
Last updated on <time datetime="2023-03-07">April 11, 2023</time>
|
||||
Last updated on <time datetime="2023-03-07">April 16, 2023</time>
|
||||
</div>
|
||||
|
||||
55
pnpm-lock.yaml
generated
55
pnpm-lock.yaml
generated
@ -1077,6 +1077,52 @@ importers:
|
||||
specifier: 0.13.0
|
||||
version: 0.13.0(react@18.2.0)
|
||||
|
||||
packages/components/navbar:
|
||||
dependencies:
|
||||
'@nextui-org/dom-utils':
|
||||
specifier: workspace:*
|
||||
version: link:../../utilities/dom-utils
|
||||
'@nextui-org/shared-utils':
|
||||
specifier: workspace:*
|
||||
version: link:../../utilities/shared-utils
|
||||
'@nextui-org/system':
|
||||
specifier: workspace:*
|
||||
version: link:../../core/system
|
||||
'@nextui-org/theme':
|
||||
specifier: workspace:*
|
||||
version: link:../../core/theme
|
||||
'@nextui-org/use-scroll-position':
|
||||
specifier: workspace:*
|
||||
version: link:../../hooks/use-scroll-position
|
||||
'@react-aria/utils':
|
||||
specifier: ^3.16.0
|
||||
version: 3.16.0(react@18.2.0)
|
||||
devDependencies:
|
||||
'@nextui-org/avatar':
|
||||
specifier: workspace:*
|
||||
version: link:../avatar
|
||||
'@nextui-org/button':
|
||||
specifier: workspace:*
|
||||
version: link:../button
|
||||
'@nextui-org/dropdown':
|
||||
specifier: workspace:*
|
||||
version: link:../dropdown
|
||||
'@nextui-org/input':
|
||||
specifier: workspace:*
|
||||
version: link:../input
|
||||
'@nextui-org/link':
|
||||
specifier: workspace:*
|
||||
version: link:../link
|
||||
clean-package:
|
||||
specifier: 2.2.0
|
||||
version: 2.2.0
|
||||
react:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0
|
||||
react-lorem-component:
|
||||
specifier: 0.13.0
|
||||
version: 0.13.0(react@18.2.0)
|
||||
|
||||
packages/components/pagination:
|
||||
dependencies:
|
||||
'@nextui-org/dom-utils':
|
||||
@ -1816,6 +1862,15 @@ importers:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0
|
||||
|
||||
packages/hooks/use-scroll-position:
|
||||
devDependencies:
|
||||
clean-package:
|
||||
specifier: 2.2.0
|
||||
version: 2.2.0
|
||||
react:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0
|
||||
|
||||
packages/hooks/use-ssr:
|
||||
devDependencies:
|
||||
clean-package:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user