Skip to content

Commit

Permalink
feat(Title): add component (#58)
Browse files Browse the repository at this point in the history
* feat(Title): add component

* feat(Settings): use Title

* fix: move uikit/icons pkg to devDeps

* chore: reexport Title
  • Loading branch information
Lunory authored Jun 9, 2023
1 parent 077a57f commit 17cfa36
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 41 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@commitlint/cli": "^17.0.0",
"@commitlint/config-conventional": "^17.0.0",
"@gravity-ui/eslint-config": "^1.0.0",
"@gravity-ui/icons": "^2.2.0",
"@gravity-ui/prettier-config": "^1.0.0",
"@gravity-ui/stylelint-config": "^1.0.0",
"@gravity-ui/tsconfig": "^1.0.0",
Expand Down Expand Up @@ -84,6 +85,7 @@
},
"peerDependencies": {
"@gravity-ui/uikit": "^4.1.0",
"@gravity-ui/icons": "^2.2.0",
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
},
Expand Down
1 change: 1 addition & 0 deletions src/components/Settings/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ The components provides layouting functionality of settings panel with the follo
| initialPage | string | | | Inititial page in `/groupId/pageId` format |
| view | 'normal', 'mobile' | | 'normal' | Change view for Mobile |
| onPageChange | Function | | | Page change handler |
| onClose | Function | | | Settings close handler |

#### Settings.Group

Expand Down
8 changes: 5 additions & 3 deletions src/components/Settings/Settings.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ $block: '.#{variables.$ns}settings';
}

#{$block}__page {
padding: 20px #{$content-padding};
overflow-y: visible;
}

Expand Down Expand Up @@ -132,14 +131,17 @@ $block: '.#{variables.$ns}settings';
}

&__search {
margin: 12px 20px 16px;
margin: 0 20px 16px;
}

&__page {
padding: 20px;
overflow-y: auto;
}

&__content {
padding: 20px;
}

&__section {
&-heading {
@include text-subheader-2;
Expand Down
87 changes: 58 additions & 29 deletions src/components/Settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {IconProps, Loader} from '@gravity-ui/uikit';
import {SettingsSearch} from './SettingsSearch/SettingsSearch';
import {SettingsMenu, SettingsMenuInstance} from './SettingsMenu/SettingsMenu';
import {SettingsMenuMobile} from './SettingsMenuMobile/SettingsMenuMobile';
import {Title} from '../Title';

import type {SettingsMenu as SettingsMenuType} from './collect-settings';
import {getSettingsFromChildren} from './collect-settings';
import {escapeStringForRegExp} from './helpers';

Expand All @@ -23,6 +25,7 @@ interface SettingsProps {
loading?: boolean;
dict?: SettingsDict;
view?: 'normal' | 'mobile';
onClose?: () => void;
}
type SettingsDict = Record<SettingsDictKeys, string>;
type SettingsDictKeys = 'heading_settings' | 'placeholder_search' | 'not_found';
Expand Down Expand Up @@ -90,18 +93,30 @@ export function Settings({
);
}

const getPageTitleById = (menu: SettingsMenuType, activePage: string) => {
for (const firstLevel of menu) {
if ('groupTitle' in firstLevel) {
for (const secondLevel of firstLevel.items)
if (secondLevel.id === activePage) return secondLevel.title;
} else if (firstLevel.id === activePage) return firstLevel.title;
}

return '';
};

Settings.defaultProps = {
dict: defaultDict,
};

type SettingsContentProps = Omit<SettingsProps, 'loading' | 'renderLoading'>;
function SettingsContent({
initialPage,
onPageChange,
children,
renderNotFound,
dict,
view,
onPageChange,
onClose,
}: SettingsContentProps) {
const [search, setSearch] = React.useState('');
const {menu, pages} = getSettingsFromChildren(children, search);
Expand Down Expand Up @@ -156,34 +171,48 @@ function SettingsContent({
);
}

return pages[activePage].sections
.filter((section) => !section.hidden)
.map((section) => (
<div key={section.title} className={b('section')}>
{section.showTitle ? (
<h3 className={b('section-heading')}>{section.title}</h3>
) : null}

{section.header ? (
isMobile ? (
<div className={b('section-subheader')}>{section.header}</div>
) : (
section.header
)
) : null}

{section.items.map(({hidden, title, element}) =>
hidden ? null : (
<div key={title} className={b('section-item')}>
{React.cloneElement(element, {
...element.props,
title: search && title ? prepareTitle(title, search) : title,
})}
</div>
),
)}
const filteredSections = pages[activePage].sections.filter((section) => !section.hidden);

return (
<>
{!isMobile && (
<Title hasSeparator onClose={onClose}>
{getPageTitleById(menu, activePage)}
</Title>
)}

<div className={b('content')}>
{filteredSections.map((section) => (
<div key={section.title} className={b('section')}>
{section.showTitle && (
<h3 className={b('section-heading')}>{section.title}</h3>
)}

{section.header &&
(isMobile ? (
<div className={b('section-subheader')}>{section.header}</div>
) : (
section.header
))}

{section.items.map(({hidden, title, element}) =>
hidden ? null : (
<div key={title} className={b('section-item')}>
{React.cloneElement(element, {
...element.props,
title:
search && title
? prepareTitle(title, search)
: title,
})}
</div>
),
)}
</div>
))}
</div>
));
</>
);
};

return (
Expand Down Expand Up @@ -220,7 +249,7 @@ function SettingsContent({
}
}}
>
<h2 className={b('heading')}>{dict?.heading_settings}</h2>
<Title>{dict?.heading_settings}</Title>
<SettingsSearch
inputRef={searchInputRef}
className={b('search')}
Expand Down
23 changes: 20 additions & 3 deletions src/components/Settings/__stories__/SettingsDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, {useReducer} from 'react';
import block from 'bem-cn-lite';

import {Settings} from '../index';
import {HelpPopover, Switch, Checkbox, RadioButton, Radio, Select} from '@gravity-ui/uikit';
import {Button, HelpPopover, Switch, Checkbox, RadioButton, Radio, Select} from '@gravity-ui/uikit';

import featureIcon from '../../../../assets/icons/gear.svg';

Expand Down Expand Up @@ -46,7 +46,15 @@ const defaultSettings = {
};

export const SettingsComponent = React.memo(
({initialPage, withBadge}: {initialPage?: string; withBadge?: boolean}) => {
({
initialPage,
withBadge,
onClose,
}: {
initialPage?: string;
withBadge?: boolean;
onClose: () => void;
}) => {
const [settings, dispatch] = useReducer(reducer, defaultSettings);
const handleChange = (name: string, value: any) => {
dispatch(setSetting(name, value));
Expand All @@ -57,6 +65,7 @@ export const SettingsComponent = React.memo(
onPageChange={(page) => {
console.log({page});
}}
onClose={onClose}
>
<Settings.Group id="arcanum" groupTitle="Arcanum">
<Settings.Page id="features" title="Features" icon={{data: featureIcon}}>
Expand Down Expand Up @@ -145,6 +154,14 @@ export const SettingsComponent = React.memo(
}}
/>
</Settings.Item>
{onClose && (
<Settings.Item
title="Use triggerEvent method"
withBadge={withBadge}
>
<Button onClick={() => onClose?.()}>Save and close</Button>
</Settings.Item>
)}
</Settings.Section>
</Settings.Page>
</Settings.Group>
Expand All @@ -162,7 +179,7 @@ export function SettingsDemo() {
<div className={b('header')}>
<h1>Settings</h1>
</div>
<SettingsComponent withBadge />
<SettingsComponent withBadge onClose={() => alert('Close settings')} />
</div>
);
}
Expand Down
11 changes: 6 additions & 5 deletions src/components/Settings/__stories__/SettingsMobileDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ export const SettingsMobileComponent = React.memo(
({
initialPage,
withBadge,
closeSettings,
onClose,
}: {
initialPage?: string;
withBadge?: boolean;
closeSettings?: () => void;
onClose?: () => void;
}) => {
const [settings, dispatch] = useReducer(reducer, defaultSettings);
const handleChange = (name: string, value: any) => {
Expand All @@ -80,6 +80,7 @@ export const SettingsMobileComponent = React.memo(
onPageChange={(page) => {
console.log({page});
}}
onClose={onClose}
>
<Settings.Group id="arcanum" groupTitle="Arcanum">
<Settings.Page id="features" title="Features">
Expand Down Expand Up @@ -132,9 +133,9 @@ export const SettingsMobileComponent = React.memo(
size="l"
/>
</Settings.Item>
{closeSettings && (
{onClose && (
<Settings.Item title="Use triggerEvent method" mode="row">
<Button onClick={() => closeSettings?.()} size="xl">
<Button onClick={() => onClose?.()} size="xl">
Save and close
</Button>
</Settings.Item>
Expand Down Expand Up @@ -192,7 +193,7 @@ SettingsMobileComponent.displayName = 'SettingsMobileComponent';
export function SettingsMobileDemo() {
return (
<div className={b()}>
<SettingsMobileComponent withBadge />
<SettingsMobileComponent withBadge onClose={() => alert('Close settings')} />
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Settings/collect-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import {IconProps} from '@gravity-ui/uikit';
import {escapeStringForRegExp, invariant} from './helpers';

type SettingsMenu = (SettingsMenuGroup | SettingsMenuItem)[];
export type SettingsMenu = (SettingsMenuGroup | SettingsMenuItem)[];

interface SettingsMenuGroup {
groupTitle: string;
Expand Down
22 changes: 22 additions & 0 deletions src/components/Title/Title.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@use '../variables';
@import '../../../styles/mixins';

$block: '.#{variables.$ns}title';

#{$block} {
box-sizing: border-box;
padding: 14px 10px 14px 20px;
min-height: 64px;
display: flex;
justify-content: space-between;
align-items: center;

&_separator {
border-bottom: 1px solid var(--yc-color-line-generic);
}

&__text {
margin: 0;
margin-right: 20px;
}
}
51 changes: 51 additions & 0 deletions src/components/Title/Title.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import {Button, Icon, Text} from '@gravity-ui/uikit';
import {Xmark} from '@gravity-ui/icons';
import {block} from '../utils/cn';

import './Title.scss';

const b = block('title');

type TitleDictKeys = 'close';
type TitleDict = Record<TitleDictKeys, string>;

const defaultDict: TitleDict = {
close: 'Close',
};

interface TitleProps {
hasSeparator?: boolean;
dict?: TitleDict;
closeIconSize?: number;
onClose?: () => void;
}
export const Title: React.FC<React.PropsWithChildren<TitleProps>> = ({
children,
closeIconSize = 23,
hasSeparator,
dict = defaultDict,
onClose,
}) => {
return (
<div className={b({separator: hasSeparator})}>
<Text className={b('text')} as={'h3'} variant={'subheader-3'}>
{children}
</Text>
{onClose && (
<Button
onClick={onClose}
view="flat"
size="l"
extraProps={{
'aria-label': dict['close'],
}}
>
<Icon data={Xmark} size={closeIconSize} />
</Button>
)}
</div>
);
};

Title.displayName = 'Title';
1 change: 1 addition & 0 deletions src/components/Title/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Title';
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export {ActionBar} from './ActionBar';
export {AsideHeader, AsideHeaderProps} from './AsideHeader/AsideHeader';
export {Drawer, DrawerProps, DrawerItemProps, DrawerItem} from './Drawer/Drawer';
export {FooterItem, FooterItemProps} from './FooterItem/FooterItem';
export * from './Title';
export * from './HotkeysPanel';
export * from './Settings';
export * from './types';

0 comments on commit 17cfa36

Please sign in to comment.