From bdd416428d31d917eb7565e60555433da261aa2c Mon Sep 17 00:00:00 2001 From: Denis Yakshov Date: Tue, 22 Apr 2025 18:24:54 +0300 Subject: [PATCH] feat(Header): add new component --- .../react/src/components/Button/Button.tsx | 2 +- .../src/components/Header/Header.api.mdx | 37 ++++ .../src/components/Header/Header.classes.ts | 13 ++ .../src/components/Header/Header.stories.tsx | 204 ++++++++++++++++++ .../react/src/components/Header/Header.tsx | 41 ++++ .../src/components/Header/Header.types.ts | 15 ++ .../HeaderActions/HeaderActions.api.mdx | 37 ++++ .../HeaderActions/HeaderActions.classes.ts | 13 ++ .../Header/HeaderActions/HeaderActions.tsx | 46 ++++ .../HeaderActions/HeaderActions.types.ts | 15 ++ .../components/Header/HeaderActions/index.ts | 3 + .../Header/HeaderLine/HeaderLine.api.mdx | 37 ++++ .../Header/HeaderLine/HeaderLine.classes.ts | 13 ++ .../Header/HeaderLine/HeaderLine.tsx | 47 ++++ .../Header/HeaderLine/HeaderLine.types.ts | 15 ++ .../src/components/Header/HeaderLine/index.ts | 3 + .../Header/HeaderLogo/HeaderLogo.api.mdx | 37 ++++ .../Header/HeaderLogo/HeaderLogo.classes.ts | 13 ++ .../Header/HeaderLogo/HeaderLogo.tsx | 53 +++++ .../Header/HeaderLogo/HeaderLogo.types.ts | 26 +++ .../src/components/Header/HeaderLogo/index.ts | 3 + .../HeaderNavigation/HeaderNavigation.api.mdx | 37 ++++ .../HeaderNavigation.classes.ts | 18 ++ .../HeaderNavigation/HeaderNavigation.tsx | 154 +++++++++++++ .../HeaderNavigation.types.ts | 15 ++ .../Header/HeaderNavigation/index.ts | 3 + .../Header/HeaderSearch/HeaderSearch.api.mdx | 37 ++++ .../HeaderSearch/HeaderSearch.classes.ts | 13 ++ .../Header/HeaderSearch/HeaderSearch.tsx | 144 +++++++++++++ .../Header/HeaderSearch/HeaderSearch.types.ts | 9 + .../components/Header/HeaderSearch/index.ts | 3 + packages/react/src/components/Header/index.ts | 8 + packages/react/src/components/index.ts | 1 + packages/react/src/icons/IconElonsoft.tsx | 2 +- packages/react/src/overrides.d.ts | 32 +++ 35 files changed, 1147 insertions(+), 2 deletions(-) create mode 100644 packages/react/src/components/Header/Header.api.mdx create mode 100644 packages/react/src/components/Header/Header.classes.ts create mode 100644 packages/react/src/components/Header/Header.stories.tsx create mode 100644 packages/react/src/components/Header/Header.tsx create mode 100644 packages/react/src/components/Header/Header.types.ts create mode 100644 packages/react/src/components/Header/HeaderActions/HeaderActions.api.mdx create mode 100644 packages/react/src/components/Header/HeaderActions/HeaderActions.classes.ts create mode 100644 packages/react/src/components/Header/HeaderActions/HeaderActions.tsx create mode 100644 packages/react/src/components/Header/HeaderActions/HeaderActions.types.ts create mode 100644 packages/react/src/components/Header/HeaderActions/index.ts create mode 100644 packages/react/src/components/Header/HeaderLine/HeaderLine.api.mdx create mode 100644 packages/react/src/components/Header/HeaderLine/HeaderLine.classes.ts create mode 100644 packages/react/src/components/Header/HeaderLine/HeaderLine.tsx create mode 100644 packages/react/src/components/Header/HeaderLine/HeaderLine.types.ts create mode 100644 packages/react/src/components/Header/HeaderLine/index.ts create mode 100644 packages/react/src/components/Header/HeaderLogo/HeaderLogo.api.mdx create mode 100644 packages/react/src/components/Header/HeaderLogo/HeaderLogo.classes.ts create mode 100644 packages/react/src/components/Header/HeaderLogo/HeaderLogo.tsx create mode 100644 packages/react/src/components/Header/HeaderLogo/HeaderLogo.types.ts create mode 100644 packages/react/src/components/Header/HeaderLogo/index.ts create mode 100644 packages/react/src/components/Header/HeaderNavigation/HeaderNavigation.api.mdx create mode 100644 packages/react/src/components/Header/HeaderNavigation/HeaderNavigation.classes.ts create mode 100644 packages/react/src/components/Header/HeaderNavigation/HeaderNavigation.tsx create mode 100644 packages/react/src/components/Header/HeaderNavigation/HeaderNavigation.types.ts create mode 100644 packages/react/src/components/Header/HeaderNavigation/index.ts create mode 100644 packages/react/src/components/Header/HeaderSearch/HeaderSearch.api.mdx create mode 100644 packages/react/src/components/Header/HeaderSearch/HeaderSearch.classes.ts create mode 100644 packages/react/src/components/Header/HeaderSearch/HeaderSearch.tsx create mode 100644 packages/react/src/components/Header/HeaderSearch/HeaderSearch.types.ts create mode 100644 packages/react/src/components/Header/HeaderSearch/index.ts create mode 100644 packages/react/src/components/Header/index.ts diff --git a/packages/react/src/components/Button/Button.tsx b/packages/react/src/components/Button/Button.tsx index d2f1e6725..edc9d1cf5 100644 --- a/packages/react/src/components/Button/Button.tsx +++ b/packages/react/src/components/Button/Button.tsx @@ -392,7 +392,7 @@ const ButtonRoot = styled(ButtonBase, { marginRight: -2, }, [`& > .${svgIconClasses.root}`]: { - margin: '0 -4px', + margin: '0 -6px', }, }, [`&.${buttonClasses.size200}`]: { diff --git a/packages/react/src/components/Header/Header.api.mdx b/packages/react/src/components/Header/Header.api.mdx new file mode 100644 index 000000000..3cdcfeaea --- /dev/null +++ b/packages/react/src/components/Header/Header.api.mdx @@ -0,0 +1,37 @@ +import { Meta } from '@storybook/blocks'; +import LinkTo from '@storybook/addon-links/react'; +import { TableInterface } from '~storybook/components/TableInterface'; + + + +# Header API + +```js +import { Header } from '@esfront/react'; +``` + +## Component name + +The name `ESHeader` can be used when providing default props in the theme. + +## Props + + + +
+ +## CSS + + + +
+ +## Demos + +
    +
  • + + Header + +
  • +
diff --git a/packages/react/src/components/Header/Header.classes.ts b/packages/react/src/components/Header/Header.classes.ts new file mode 100644 index 000000000..e6e13d1fd --- /dev/null +++ b/packages/react/src/components/Header/Header.classes.ts @@ -0,0 +1,13 @@ +import { generateUtilityClass, generateUtilityClasses } from '@mui/material'; + +export type HeaderClasses = { + /** Styles applied to the root element. */ + root: string; +}; +export type HeaderClassKey = keyof HeaderClasses; + +export function getHeaderUtilityClass(slot: string): string { + return generateUtilityClass('ESHeader', slot); +} + +export const headerClasses: HeaderClasses = generateUtilityClasses('ESHeader', ['root']); diff --git a/packages/react/src/components/Header/Header.stories.tsx b/packages/react/src/components/Header/Header.stories.tsx new file mode 100644 index 000000000..b521328b6 --- /dev/null +++ b/packages/react/src/components/Header/Header.stories.tsx @@ -0,0 +1,204 @@ +import { Meta, StoryObj } from '@storybook/react'; + +import { InputAdornment } from '@mui/material'; + +import { Header, HeaderActions, HeaderLine, HeaderLogo, HeaderNavigation, HeaderSearch } from '.'; + +import { IconChevronDownW200, IconCloseLineW350, IconElonsoft, IconMagnify, IconMenuDownW400 } from '../../icons'; +import { Button } from '../Button'; +import { SvgIcon, SvgIconProps } from '../SvgIcon'; + +const meta: Meta = { + tags: ['autodocs'], + component: Header, + parameters: { + references: ['Header'], + }, + argTypes: {}, +}; + +export default meta; + +type Story = StoryObj; + +const IconMenuLineW500 = (props: SvgIconProps) => { + return ( + + + + + + ); +}; + +const IconMapMarker = (props: SvgIconProps) => { + return ( + + + + ); +}; + +export const Demo: Story = { + render: () => { + return ( +
+ + + + + + + + + + + + + + + + + + + + + {false && ( + + {false && } + {true && ( + + )} + + ), + endAdornment: ( + + + {false && ( + + )} + {true && ( + + )} + + ), + }} + placeholder="Поиск" + /> + )} + + + + + ), + endAdornment: ( + + + + ), + }} + placeholder="Поиск" + variant="borderless" + /> + + + + + + + + + + + + + + + + + + + + +
+ ); + }, +}; diff --git a/packages/react/src/components/Header/Header.tsx b/packages/react/src/components/Header/Header.tsx new file mode 100644 index 000000000..9e00c85e4 --- /dev/null +++ b/packages/react/src/components/Header/Header.tsx @@ -0,0 +1,41 @@ +import { HeaderProps } from './Header.types'; + +import clsx from 'clsx'; +import { getHeaderUtilityClass } from './Header.classes'; + +import { unstable_composeClasses as composeClasses } from '@mui/base'; + +import { styled, useThemeProps } from '@mui/material/styles'; + +type HeaderOwnerState = { + classes?: HeaderProps['classes']; +}; + +const useUtilityClasses = (ownerState: HeaderOwnerState) => { + const { classes } = ownerState; + + const slots = { + root: ['root'], + }; + + return composeClasses(slots, getHeaderUtilityClass, classes); +}; + +const HeaderRoot = styled('header', { + name: 'ESHeader', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})({}); + +export const Header = (inProps: HeaderProps) => { + const { children, className, sx, ...props } = useThemeProps({ props: inProps, name: 'ESHeader' }); + + const ownerState = { ...props }; + const classes = useUtilityClasses(ownerState); + + return ( + + {children} + + ); +}; diff --git a/packages/react/src/components/Header/Header.types.ts b/packages/react/src/components/Header/Header.types.ts new file mode 100644 index 000000000..8b55f45b4 --- /dev/null +++ b/packages/react/src/components/Header/Header.types.ts @@ -0,0 +1,15 @@ +import { ReactNode } from 'react'; + +import { HeaderClasses } from './Header.classes'; + +import { SxProps, Theme } from '@mui/material'; + +export interface HeaderProps { + children?: ReactNode; + /** Override or extend the styles applied to the component. */ + classes?: Partial; + /** Class applied to the root element. */ + className?: string; + /** The system prop that allows defining system overrides as well as additional CSS styles. */ + sx?: SxProps; +} diff --git a/packages/react/src/components/Header/HeaderActions/HeaderActions.api.mdx b/packages/react/src/components/Header/HeaderActions/HeaderActions.api.mdx new file mode 100644 index 000000000..1c674415d --- /dev/null +++ b/packages/react/src/components/Header/HeaderActions/HeaderActions.api.mdx @@ -0,0 +1,37 @@ +import { Meta } from '@storybook/blocks'; +import LinkTo from '@storybook/addon-links/react'; +import { TableInterface } from '~storybook/components/TableInterface'; + + + +# HeaderActions API + +```js +import { HeaderActions } from '@esfront/react'; +``` + +## Component name + +The name `ESHeaderActions` can be used when providing default props in the theme. + +## Props + + + +
+ +## CSS + + + +
+ +## Demos + +
    +
  • + + Header + +
  • +
diff --git a/packages/react/src/components/Header/HeaderActions/HeaderActions.classes.ts b/packages/react/src/components/Header/HeaderActions/HeaderActions.classes.ts new file mode 100644 index 000000000..7be597cb6 --- /dev/null +++ b/packages/react/src/components/Header/HeaderActions/HeaderActions.classes.ts @@ -0,0 +1,13 @@ +import { generateUtilityClass, generateUtilityClasses } from '@mui/material'; + +export type HeaderActionsClasses = { + /** Styles applied to the root element. */ + root: string; +}; +export type HeaderActionsClassKey = keyof HeaderActionsClasses; + +export function getHeaderActionsUtilityClass(slot: string): string { + return generateUtilityClass('ESHeaderActions', slot); +} + +export const headerActionsClasses: HeaderActionsClasses = generateUtilityClasses('ESHeaderActions', ['root']); diff --git a/packages/react/src/components/Header/HeaderActions/HeaderActions.tsx b/packages/react/src/components/Header/HeaderActions/HeaderActions.tsx new file mode 100644 index 000000000..467107633 --- /dev/null +++ b/packages/react/src/components/Header/HeaderActions/HeaderActions.tsx @@ -0,0 +1,46 @@ +import { HeaderActionsProps } from './HeaderActions.types'; + +import clsx from 'clsx'; +import { getHeaderActionsUtilityClass } from './HeaderActions.classes'; + +import { unstable_composeClasses as composeClasses } from '@mui/base'; + +import { styled, useThemeProps } from '@mui/material/styles'; + +type HeaderActionsOwnerState = { + classes?: HeaderActionsProps['classes']; +}; + +const useUtilityClasses = (ownerState: HeaderActionsOwnerState) => { + const { classes } = ownerState; + + const slots = { + root: ['root'], + }; + + return composeClasses(slots, getHeaderActionsUtilityClass, classes); +}; + +const HeaderActionsRoot = styled('div', { + name: 'ESHeaderActions', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})({ + display: 'flex', + gap: '4px', + marginLeft: 'auto', + padding: '8px 0 8px 16px', +}); + +export const HeaderActions = (inProps: HeaderActionsProps) => { + const { children, className, sx, ...props } = useThemeProps({ props: inProps, name: 'ESHeaderActions' }); + + const ownerState = { ...props }; + const classes = useUtilityClasses(ownerState); + + return ( + + {children} + + ); +}; diff --git a/packages/react/src/components/Header/HeaderActions/HeaderActions.types.ts b/packages/react/src/components/Header/HeaderActions/HeaderActions.types.ts new file mode 100644 index 000000000..822eee54b --- /dev/null +++ b/packages/react/src/components/Header/HeaderActions/HeaderActions.types.ts @@ -0,0 +1,15 @@ +import { ReactNode } from 'react'; + +import { HeaderActionsClasses } from './HeaderActions.classes'; + +import { SxProps, Theme } from '@mui/material'; + +export interface HeaderActionsProps { + children?: ReactNode; + /** Override or extend the styles applied to the component. */ + classes?: Partial; + /** Class applied to the root element. */ + className?: string; + /** The system prop that allows defining system overrides as well as additional CSS styles. */ + sx?: SxProps; +} diff --git a/packages/react/src/components/Header/HeaderActions/index.ts b/packages/react/src/components/Header/HeaderActions/index.ts new file mode 100644 index 000000000..5a940bf8a --- /dev/null +++ b/packages/react/src/components/Header/HeaderActions/index.ts @@ -0,0 +1,3 @@ +export { HeaderActions } from './HeaderActions'; +export { HeaderActionsClasses, headerActionsClasses, HeaderActionsClassKey } from './HeaderActions.classes'; +export { HeaderActionsProps } from './HeaderActions.types'; diff --git a/packages/react/src/components/Header/HeaderLine/HeaderLine.api.mdx b/packages/react/src/components/Header/HeaderLine/HeaderLine.api.mdx new file mode 100644 index 000000000..b4b97e17b --- /dev/null +++ b/packages/react/src/components/Header/HeaderLine/HeaderLine.api.mdx @@ -0,0 +1,37 @@ +import { Meta } from '@storybook/blocks'; +import LinkTo from '@storybook/addon-links/react'; +import { TableInterface } from '~storybook/components/TableInterface'; + + + +# HeaderLine API + +```js +import { HeaderLine } from '@esfront/react'; +``` + +## Component name + +The name `ESHeaderLine` can be used when providing default props in the theme. + +## Props + + + +
+ +## CSS + + + +
+ +## Demos + +
    +
  • + + Header + +
  • +
diff --git a/packages/react/src/components/Header/HeaderLine/HeaderLine.classes.ts b/packages/react/src/components/Header/HeaderLine/HeaderLine.classes.ts new file mode 100644 index 000000000..57e975aad --- /dev/null +++ b/packages/react/src/components/Header/HeaderLine/HeaderLine.classes.ts @@ -0,0 +1,13 @@ +import { generateUtilityClass, generateUtilityClasses } from '@mui/material'; + +export type HeaderLineClasses = { + /** Styles applied to the root element. */ + root: string; +}; +export type HeaderLineClassKey = keyof HeaderLineClasses; + +export function getHeaderLineUtilityClass(slot: string): string { + return generateUtilityClass('ESHeaderLine', slot); +} + +export const headerLineClasses: HeaderLineClasses = generateUtilityClasses('ESHeaderLine', ['root']); diff --git a/packages/react/src/components/Header/HeaderLine/HeaderLine.tsx b/packages/react/src/components/Header/HeaderLine/HeaderLine.tsx new file mode 100644 index 000000000..88fd1e609 --- /dev/null +++ b/packages/react/src/components/Header/HeaderLine/HeaderLine.tsx @@ -0,0 +1,47 @@ +import { HeaderLineProps } from './HeaderLine.types'; + +import clsx from 'clsx'; +import { getHeaderLineUtilityClass } from './HeaderLine.classes'; + +import { unstable_composeClasses as composeClasses } from '@mui/base'; + +import { styled, useThemeProps } from '@mui/material/styles'; + +type HeaderLineOwnerState = { + classes?: HeaderLineProps['classes']; +}; + +const useUtilityClasses = (ownerState: HeaderLineOwnerState) => { + const { classes } = ownerState; + + const slots = { + root: ['root'], + }; + + return composeClasses(slots, getHeaderLineUtilityClass, classes); +}; + +const HeaderLineRoot = styled('div', { + name: 'ESHeaderLine', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})({ + alignItems: 'center', + display: 'flex', + margin: '0 auto', + maxWidth: '1440px', + padding: '0 56px', +}); + +export const HeaderLine = (inProps: HeaderLineProps) => { + const { children, className, sx, ...props } = useThemeProps({ props: inProps, name: 'ESHeaderLine' }); + + const ownerState = { ...props }; + const classes = useUtilityClasses(ownerState); + + return ( + + {children} + + ); +}; diff --git a/packages/react/src/components/Header/HeaderLine/HeaderLine.types.ts b/packages/react/src/components/Header/HeaderLine/HeaderLine.types.ts new file mode 100644 index 000000000..ca10abd4c --- /dev/null +++ b/packages/react/src/components/Header/HeaderLine/HeaderLine.types.ts @@ -0,0 +1,15 @@ +import { ReactNode } from 'react'; + +import { HeaderLineClasses } from './HeaderLine.classes'; + +import { SxProps, Theme } from '@mui/material'; + +export interface HeaderLineProps { + children?: ReactNode; + /** Override or extend the styles applied to the component. */ + classes?: Partial; + /** Class applied to the root element. */ + className?: string; + /** The system prop that allows defining system overrides as well as additional CSS styles. */ + sx?: SxProps; +} diff --git a/packages/react/src/components/Header/HeaderLine/index.ts b/packages/react/src/components/Header/HeaderLine/index.ts new file mode 100644 index 000000000..6f2ba427f --- /dev/null +++ b/packages/react/src/components/Header/HeaderLine/index.ts @@ -0,0 +1,3 @@ +export { HeaderLine } from './HeaderLine'; +export { HeaderLineClasses, headerLineClasses, HeaderLineClassKey } from './HeaderLine.classes'; +export { HeaderLineProps } from './HeaderLine.types'; diff --git a/packages/react/src/components/Header/HeaderLogo/HeaderLogo.api.mdx b/packages/react/src/components/Header/HeaderLogo/HeaderLogo.api.mdx new file mode 100644 index 000000000..f63a9ed6d --- /dev/null +++ b/packages/react/src/components/Header/HeaderLogo/HeaderLogo.api.mdx @@ -0,0 +1,37 @@ +import { Meta } from '@storybook/blocks'; +import LinkTo from '@storybook/addon-links/react'; +import { TableInterface } from '~storybook/components/TableInterface'; + + + +# HeaderLogo API + +```js +import { HeaderLogo } from '@esfront/react'; +``` + +## Component name + +The name `ESHeaderLogo` can be used when providing default props in the theme. + +## Props + + + +
+ +## CSS + + + +
+ +## Demos + +
    +
  • + + Header + +
  • +
diff --git a/packages/react/src/components/Header/HeaderLogo/HeaderLogo.classes.ts b/packages/react/src/components/Header/HeaderLogo/HeaderLogo.classes.ts new file mode 100644 index 000000000..abda8793d --- /dev/null +++ b/packages/react/src/components/Header/HeaderLogo/HeaderLogo.classes.ts @@ -0,0 +1,13 @@ +import { generateUtilityClass, generateUtilityClasses } from '@mui/material'; + +export type HeaderLogoClasses = { + /** Styles applied to the root element. */ + root: string; +}; +export type HeaderLogoClassKey = keyof HeaderLogoClasses; + +export function getHeaderLogoUtilityClass(slot: string): string { + return generateUtilityClass('ESHeaderLogo', slot); +} + +export const headerLogoClasses: HeaderLogoClasses = generateUtilityClasses('ESHeaderLogo', ['root']); diff --git a/packages/react/src/components/Header/HeaderLogo/HeaderLogo.tsx b/packages/react/src/components/Header/HeaderLogo/HeaderLogo.tsx new file mode 100644 index 000000000..e211a0afb --- /dev/null +++ b/packages/react/src/components/Header/HeaderLogo/HeaderLogo.tsx @@ -0,0 +1,53 @@ +import { HeaderLogoProps, HeaderLogoTypeMap } from './HeaderLogo.types'; + +import clsx from 'clsx'; +import { getHeaderLogoUtilityClass } from './HeaderLogo.classes'; + +import { unstable_composeClasses as composeClasses } from '@mui/base'; + +import { styled, useThemeProps } from '@mui/material/styles'; +import { OverridableComponent } from '@mui/material/OverridableComponent'; + +type HeaderLogoOwnerState = { + classes?: HeaderLogoProps['classes']; +}; + +const useUtilityClasses = (ownerState: HeaderLogoOwnerState) => { + const { classes } = ownerState; + + const slots = { + root: ['root'], + }; + + return composeClasses(slots, getHeaderLogoUtilityClass, classes); +}; + +const HeaderLogoRoot = styled('a', { + name: 'ESHeaderLogo', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})(({ theme }) => ({ + borderRadius: '4px', + color: 'inherit', + display: 'inline-flex', + padding: '12px 8px 12px 8px', + marginRight: '16px', + + '&:focus-visible': { + outline: `2px solid ${theme.vars.palette.monoA[500]}`, + outlineOffset: '-2px', + }, +})); + +export const HeaderLogo: OverridableComponent = (inProps: HeaderLogoProps) => { + const { children, className, classes: inClasses, ...props } = useThemeProps({ props: inProps, name: 'ESHeaderLogo' }); + + const ownerState = { classes: inClasses }; + const classes = useUtilityClasses(ownerState); + + return ( + + {children} + + ); +}; diff --git a/packages/react/src/components/Header/HeaderLogo/HeaderLogo.types.ts b/packages/react/src/components/Header/HeaderLogo/HeaderLogo.types.ts new file mode 100644 index 000000000..c99ae0ec8 --- /dev/null +++ b/packages/react/src/components/Header/HeaderLogo/HeaderLogo.types.ts @@ -0,0 +1,26 @@ +/* eslint-disable @typescript-eslint/no-empty-object-type */ + +import { ReactNode } from 'react'; + +import { HeaderLogoClasses } from './HeaderLogo.classes'; + +import { SxProps, Theme } from '@mui/material'; +import { OverrideProps } from '@mui/material/OverridableComponent'; + +export interface HeaderLogoTypeMap

{ + props: P & { + children?: ReactNode; + /** Override or extend the styles applied to the component. */ + classes?: Partial; + /** Class applied to the root element. */ + className?: string; + /** The system prop that allows defining system overrides as well as additional CSS styles. */ + sx?: SxProps; + }; + defaultComponent: D; +} + +export type HeaderLogoProps< + D extends React.ElementType = HeaderLogoTypeMap['defaultComponent'], + P = {}, +> = OverrideProps, D>; diff --git a/packages/react/src/components/Header/HeaderLogo/index.ts b/packages/react/src/components/Header/HeaderLogo/index.ts new file mode 100644 index 000000000..8c9a8b49e --- /dev/null +++ b/packages/react/src/components/Header/HeaderLogo/index.ts @@ -0,0 +1,3 @@ +export { HeaderLogo } from './HeaderLogo'; +export { HeaderLogoClasses, headerLogoClasses, HeaderLogoClassKey } from './HeaderLogo.classes'; +export { HeaderLogoProps, HeaderLogoTypeMap } from './HeaderLogo.types'; diff --git a/packages/react/src/components/Header/HeaderNavigation/HeaderNavigation.api.mdx b/packages/react/src/components/Header/HeaderNavigation/HeaderNavigation.api.mdx new file mode 100644 index 000000000..434ba9925 --- /dev/null +++ b/packages/react/src/components/Header/HeaderNavigation/HeaderNavigation.api.mdx @@ -0,0 +1,37 @@ +import { Meta } from '@storybook/blocks'; +import LinkTo from '@storybook/addon-links/react'; +import { TableInterface } from '~storybook/components/TableInterface'; + + + +# HeaderNavigation API + +```js +import { HeaderNavigation } from '@esfront/react'; +``` + +## Component name + +The name `ESHeaderNavigation` can be used when providing default props in the theme. + +## Props + + + +
+ +## CSS + + + +
+ +## Demos + +

    +
  • + + Header + +
  • +
diff --git a/packages/react/src/components/Header/HeaderNavigation/HeaderNavigation.classes.ts b/packages/react/src/components/Header/HeaderNavigation/HeaderNavigation.classes.ts new file mode 100644 index 000000000..72d268383 --- /dev/null +++ b/packages/react/src/components/Header/HeaderNavigation/HeaderNavigation.classes.ts @@ -0,0 +1,18 @@ +import { generateUtilityClass, generateUtilityClasses } from '@mui/material'; + +export type HeaderNavigationClasses = { + /** Styles applied to the root element. */ + root: string; + /** Styles applied to the button element. */ + button: string; +}; +export type HeaderNavigationClassKey = keyof HeaderNavigationClasses; + +export function getHeaderNavigationUtilityClass(slot: string): string { + return generateUtilityClass('ESHeaderNavigation', slot); +} + +export const headerNavigationClasses: HeaderNavigationClasses = generateUtilityClasses('ESHeaderNavigation', [ + 'root', + 'button', +]); diff --git a/packages/react/src/components/Header/HeaderNavigation/HeaderNavigation.tsx b/packages/react/src/components/Header/HeaderNavigation/HeaderNavigation.tsx new file mode 100644 index 000000000..a3dcb78ec --- /dev/null +++ b/packages/react/src/components/Header/HeaderNavigation/HeaderNavigation.tsx @@ -0,0 +1,154 @@ +import { Children, isValidElement, useRef, useState } from 'react'; + +import { HeaderNavigationProps } from './HeaderNavigation.types'; + +import clsx from 'clsx'; +import { getHeaderNavigationUtilityClass, headerNavigationClasses } from './HeaderNavigation.classes'; + +import { unstable_composeClasses as composeClasses } from '@mui/base'; + +import { styled, useThemeProps } from '@mui/material/styles'; +import { Menu } from '@mui/material'; +import { unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/utils'; + +import { useMenu, useResizeObserver } from '../../../hooks'; +import { IconChevronDownW200 } from '../../../icons'; +import { Button } from '../../Button'; +import { MenuItem } from '../../MenuItem'; + +type HeaderNavigationOwnerState = { + classes?: HeaderNavigationProps['classes']; +}; + +const useUtilityClasses = (ownerState: HeaderNavigationOwnerState) => { + const { classes } = ownerState; + + const slots = { + root: ['root'], + button: ['button'], + }; + + return composeClasses(slots, getHeaderNavigationUtilityClass, classes); +}; + +const HeaderNavigationRoot = styled('nav', { + name: 'ESHeaderNavigation', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})({ + display: 'flex', + gap: '4px', + position: 'relative', + flexGrow: 1, + + [`.ESButton-root`]: { + whiteSpace: 'nowrap', + flexShrink: 0, + }, +}); + +const HeaderNavigationButton = styled(Button, { + name: 'ESChips', + slot: 'Button', + overridesResolver: (_props, styles) => styles.button, +})({}); + +export const HeaderNavigation = (inProps: HeaderNavigationProps) => { + const { children, className, sx, ...props } = useThemeProps({ props: inProps, name: 'ESHeaderNavigation' }); + + const ref = useRef(null); + const [anchorEl, onMenuClick, onMenuClose] = useMenu(); + + const [lastIndex, setLastIndex] = useState(Children.count(children) - 1); + + const onResize = () => { + if (typeof window !== 'undefined' && ref.current) { + let width = 0; + + const nodes = ref.current.querySelectorAll(`& > *:not(.${headerNavigationClasses.button})`); + const button = ref.current.querySelector(`.${headerNavigationClasses.button}`); + + const containerWidth = ref.current.getBoundingClientRect().width; + const columnGap = parseInt(window.getComputedStyle(ref.current).columnGap); + + nodes.forEach((item) => { + (item as HTMLElement).style.display = 'inline-flex'; + width += (item as HTMLElement).getBoundingClientRect().width + columnGap; + }); + + if (width > containerWidth) { + width += (button as HTMLElement).getBoundingClientRect().width; + } + + let i = nodes.length - 1; + + while (width > containerWidth && i > 0) { + width -= (nodes[i] as HTMLElement).getBoundingClientRect().width; + i--; + } + + for (let j = i + 1; j < nodes.length; j++) { + (nodes[j] as HTMLElement).style.display = 'none'; + } + + onMenuClose(); + setLastIndex(i); + } + }; + + const ownerState = { ...props }; + const classes = useUtilityClasses(ownerState); + + useResizeObserver(ref, onResize); + + useEnhancedEffect(() => { + onResize(); + }, []); + + return ( + <> + + {children} + } + size="300" + style={{ display: lastIndex < Children.count(children) - 1 ? 'flex' : 'none' }} + onClick={onMenuClick} + > + Еще + + + + {Children.map(children, (child, idx) => { + if (!isValidElement(child)) { + return null; + } + + const { children, onClick, ...rest } = child.props; + + if (idx > lastIndex) { + return ( + { + onClick?.(e); + + if (!e.isPropagationStopped()) { + onMenuClose(); + } + }} + > + {children} + + ); + } + + return null; + })} + + + ); +}; diff --git a/packages/react/src/components/Header/HeaderNavigation/HeaderNavigation.types.ts b/packages/react/src/components/Header/HeaderNavigation/HeaderNavigation.types.ts new file mode 100644 index 000000000..536f0be1b --- /dev/null +++ b/packages/react/src/components/Header/HeaderNavigation/HeaderNavigation.types.ts @@ -0,0 +1,15 @@ +import { ReactNode } from 'react'; + +import { HeaderNavigationClasses } from './HeaderNavigation.classes'; + +import { SxProps, Theme } from '@mui/material'; + +export interface HeaderNavigationProps { + children?: ReactNode; + /** Override or extend the styles applied to the component. */ + classes?: Partial; + /** Class applied to the root element. */ + className?: string; + /** The system prop that allows defining system overrides as well as additional CSS styles. */ + sx?: SxProps; +} diff --git a/packages/react/src/components/Header/HeaderNavigation/index.ts b/packages/react/src/components/Header/HeaderNavigation/index.ts new file mode 100644 index 000000000..c74657abb --- /dev/null +++ b/packages/react/src/components/Header/HeaderNavigation/index.ts @@ -0,0 +1,3 @@ +export { HeaderNavigation } from './HeaderNavigation'; +export { HeaderNavigationClasses, headerNavigationClasses, HeaderNavigationClassKey } from './HeaderNavigation.classes'; +export { HeaderNavigationProps } from './HeaderNavigation.types'; diff --git a/packages/react/src/components/Header/HeaderSearch/HeaderSearch.api.mdx b/packages/react/src/components/Header/HeaderSearch/HeaderSearch.api.mdx new file mode 100644 index 000000000..bc1c35f1d --- /dev/null +++ b/packages/react/src/components/Header/HeaderSearch/HeaderSearch.api.mdx @@ -0,0 +1,37 @@ +import { Meta } from '@storybook/blocks'; +import LinkTo from '@storybook/addon-links/react'; +import { TableInterface } from '~storybook/components/TableInterface'; + + + +# HeaderSearch API + +```js +import { HeaderSearch } from '@esfront/react'; +``` + +## Component name + +The name `ESHeaderSearch` can be used when providing default props in the theme. + +## Props + + + +
+ +## CSS + + + +
+ +## Demos + +
    +
  • + + Header + +
  • +
diff --git a/packages/react/src/components/Header/HeaderSearch/HeaderSearch.classes.ts b/packages/react/src/components/Header/HeaderSearch/HeaderSearch.classes.ts new file mode 100644 index 000000000..0293bca92 --- /dev/null +++ b/packages/react/src/components/Header/HeaderSearch/HeaderSearch.classes.ts @@ -0,0 +1,13 @@ +import { generateUtilityClass, generateUtilityClasses } from '@mui/material'; + +export type HeaderSearchClasses = { + /** Styles applied to the root element. */ + root: string; +}; +export type HeaderSearchClassKey = keyof HeaderSearchClasses; + +export function getHeaderSearchUtilityClass(slot: string): string { + return generateUtilityClass('ESHeaderSearch', slot); +} + +export const headerSearchClasses: HeaderSearchClasses = generateUtilityClasses('ESHeaderSearch', ['root']); diff --git a/packages/react/src/components/Header/HeaderSearch/HeaderSearch.tsx b/packages/react/src/components/Header/HeaderSearch/HeaderSearch.tsx new file mode 100644 index 000000000..087e78ac0 --- /dev/null +++ b/packages/react/src/components/Header/HeaderSearch/HeaderSearch.tsx @@ -0,0 +1,144 @@ +import { HeaderSearchProps } from './HeaderSearch.types'; + +import clsx from 'clsx'; +import { getHeaderSearchUtilityClass } from './HeaderSearch.classes'; + +import { unstable_composeClasses as composeClasses } from '@mui/base'; + +import { styled, useThemeProps } from '@mui/material/styles'; +import { inputAdornmentClasses } from '@mui/material/InputAdornment'; +import { inputBaseClasses } from '@mui/material/InputBase'; +import { outlinedInputClasses } from '@mui/material/OutlinedInput'; +import TextField, { textFieldClasses } from '@mui/material/TextField'; + +import { buttonClasses } from '../../Button'; +import { svgIconClasses } from '../../SvgIcon'; + +type HeaderSearchOwnerState = { + classes?: HeaderSearchProps['classes']; + variantProp: NonNullable; +}; + +const useUtilityClasses = (ownerState: HeaderSearchOwnerState) => { + const { classes, variantProp } = ownerState; + + const slots = { + root: ['root', variantProp], + }; + + return composeClasses(slots, getHeaderSearchUtilityClass, classes); +}; + +const HeaderSearchRoot = styled(TextField, { + name: 'ESHeaderSearch', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})<{ ownerState: HeaderSearchOwnerState }>(({ theme }) => ({ + flexGrow: 1, + + variants: [ + { + props: { + variantProp: 'borderless', + }, + style: { + [`& .${outlinedInputClasses.root}`]: { + [`&:has(.${inputAdornmentClasses.positionStart} > .${svgIconClasses.root})`]: { + paddingLeft: '8px', + }, + [`&:has(.${inputAdornmentClasses.positionEnd} .${buttonClasses.variantText}:last-child)`]: { + paddingRight: '4px', + }, + }, + + [`& .${outlinedInputClasses.notchedOutline}`]: { + display: 'none', + }, + + [`& .${inputAdornmentClasses.positionStart}`]: { + [`& > .${svgIconClasses.root}`]: { + color: theme.vars.palette.monoA.A900, + }, + }, + + [`& .${inputAdornmentClasses.positionEnd}`]: { + [`& .${buttonClasses.variantText}`]: { + '--icon': theme.vars.palette.monoA.A500, + }, + }, + }, + }, + { + props: { + variantProp: 'outlined', + }, + style: { + [`& .${outlinedInputClasses.root}`]: { + [`&:has(.${inputAdornmentClasses.positionStart} > .${svgIconClasses.root})`]: { + paddingLeft: '8px', + }, + [`&:has(.${inputAdornmentClasses.positionStart} .${buttonClasses.variantContained})`]: { + paddingLeft: '4px', + }, + [`&:has(.${inputAdornmentClasses.positionEnd} .${buttonClasses.variantContained}:last-child)`]: { + paddingRight: 0, + }, + [`&:has(.${inputAdornmentClasses.positionEnd} .${buttonClasses.variantText}:last-child)`]: { + paddingRight: '4px', + }, + }, + + [`& .${inputAdornmentClasses.positionStart}`]: { + [`& > .${svgIconClasses.root}`]: { + color: theme.vars.palette.monoA.A500, + }, + }, + + [`& .${inputAdornmentClasses.positionEnd}`]: { + [`& .${buttonClasses.variantContained}`]: { + borderBottomLeftRadius: 0, + borderTopLeftRadius: 0, + zIndex: 1, + }, + + [`& .${buttonClasses.variantText}`]: { + '--icon': theme.vars.palette.monoA.A500, + }, + + [`& .${buttonClasses.variantText}.${buttonClasses.size100}`]: { + '--icon': theme.vars.palette.monoA.A400, + marginRight: '8px', + }, + }, + }, + }, + ], +})); + +export const HeaderSearch = (inProps: HeaderSearchProps) => { + const { + classes: inClasses, + className, + variant: variantProp = 'outlined', + + ...props + } = useThemeProps({ + props: inProps, + name: 'ESHeaderSearch', + }); + + const ownerState = { classes: inClasses, variantProp }; + const classes = useUtilityClasses(ownerState); + + return ( + + ); +}; diff --git a/packages/react/src/components/Header/HeaderSearch/HeaderSearch.types.ts b/packages/react/src/components/Header/HeaderSearch/HeaderSearch.types.ts new file mode 100644 index 000000000..fcab67410 --- /dev/null +++ b/packages/react/src/components/Header/HeaderSearch/HeaderSearch.types.ts @@ -0,0 +1,9 @@ +import { TextFieldProps } from '@mui/material'; + +export type HeaderSearchProps = { + /** + * The variant of the component. + * @default 'outlined' + */ + variant?: 'borderless' | 'outlined'; +} & Omit; diff --git a/packages/react/src/components/Header/HeaderSearch/index.ts b/packages/react/src/components/Header/HeaderSearch/index.ts new file mode 100644 index 000000000..8a0d55d16 --- /dev/null +++ b/packages/react/src/components/Header/HeaderSearch/index.ts @@ -0,0 +1,3 @@ +export { HeaderSearch } from './HeaderSearch'; +export { HeaderSearchClasses, headerSearchClasses, HeaderSearchClassKey } from './HeaderSearch.classes'; +export { HeaderSearchProps } from './HeaderSearch.types'; diff --git a/packages/react/src/components/Header/index.ts b/packages/react/src/components/Header/index.ts new file mode 100644 index 000000000..1fe89bf3c --- /dev/null +++ b/packages/react/src/components/Header/index.ts @@ -0,0 +1,8 @@ +export { Header } from './Header'; +export { HeaderClasses, headerClasses, HeaderClassKey } from './Header.classes'; +export { HeaderProps } from './Header.types'; +export * from './HeaderActions'; +export * from './HeaderLine'; +export * from './HeaderLogo'; +export * from './HeaderNavigation'; +export * from './HeaderSearch'; diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index 206915895..45166e221 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -32,6 +32,7 @@ export * from './FormatDate'; export * from './FormatSize'; export * from './FormControlLabel'; export * from './Gallery'; +export * from './Header'; export * from './InformationIcon'; export * from './Kbd'; export * from './LinearProgress'; diff --git a/packages/react/src/icons/IconElonsoft.tsx b/packages/react/src/icons/IconElonsoft.tsx index 0b246d5c1..e2f08adf0 100644 --- a/packages/react/src/icons/IconElonsoft.tsx +++ b/packages/react/src/icons/IconElonsoft.tsx @@ -2,7 +2,7 @@ import { SvgIcon, SvgIconProps } from '../components/SvgIcon'; export const IconElonsoft = (props: SvgIconProps) => { return ( - +