From 9ab7d7c86a06a6f94126a87acfb23033709ec8a1 Mon Sep 17 00:00:00 2001 From: "Mr.Dr.Professor Patrick" Date: Wed, 17 Apr 2024 18:00:52 +0200 Subject: [PATCH 1/5] fix: do not call warnOnce function in production (#1520) --- src/components/utils/warn.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/utils/warn.ts b/src/components/utils/warn.ts index 2d1b9b6e4a..a86b0bbee7 100644 --- a/src/components/utils/warn.ts +++ b/src/components/utils/warn.ts @@ -1,7 +1,7 @@ const didWarn = new Map(); export function warnOnce(msg: string) { - if (!msg || didWarn.has(msg)) { + if (!msg || didWarn.has(msg) || process.env.NODE_ENV === 'production') { return; } From 634429aedc553419003d6ac91a97cb68a3fa313e Mon Sep 17 00:00:00 2001 From: Taya Leutina Date: Thu, 18 Apr 2024 12:26:04 +0300 Subject: [PATCH 2/5] chore(Sheet): update README (#1522) --- src/components/Sheet/README.md | 60 +++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/src/components/Sheet/README.md b/src/components/Sheet/README.md index e4bc2edab3..5f158767be 100644 --- a/src/components/Sheet/README.md +++ b/src/components/Sheet/README.md @@ -1,20 +1,50 @@ + + # Sheet -Sheet component for mobile devices - -## PropTypes - -| Name | Type | Required | Default | Description | -| :----------------------- | :--------- | :------: | :---------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| visible | `boolean` | ✓ | | Show/hide sheet | -| allowHideOnContentScroll | `boolean` | | `true` | Enable the behavior in which you can close the sheet window with a swipe down if the content is scrolled to its top (`contentNode.scrollTop === 0`) or has no scroll at all | -| hideTopBar | `boolean` | | | Hide top bar with resize handle | -| id | `string` | | `modal` | ID of the sheet, used as hash in URL. It's important to specify different `id` values if there can be more than one sheet on the page | -| title | `string` | | `undefined` | Title of the sheet window | -| className | `string` | | `undefined` | Class name for the sheet window | -| contentClassName | `string` | | `undefined` | Class name for the sheet content | -| swipeAreaClassName | `string` | | `undefined` | Class name for the swipe area | -| onClose | `function` | | `undefined` | Function called when the sheet is closed (when `visible` sets to `false`) | + + +```tsx +import {Sheet} from '@gravity-ui/uikit'; +``` + +`Sheet` is a component designed to be used in a mobile context as an information or interactive element. You can place content of any size in it - internal scrolling and dynamic resizing are supported. + +On mobile devices, you can move `Sheet` by pulling on its main part or the swipe area. To close it, swipe down or touch the area outside the `Sheet`. + +## Usage + +```tsx +import React from 'react'; +import {Button, Sheet} from '@gravity-ui/uikit'; + +const SheetExample = () => { + const [visible, setVisible] = React.useState(false); + + return ( + + + setVisible(false)} title="Content Sheet"> + Content + + + ); +}; +``` + +## Properties + +| Name | Description | Type | Default | +| :----------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------: | :---------: | +| visible | Manages `Sheet` visibility | `boolean` | `false` | +| allowHideOnContentScroll | Enable the behavior of the sheet window closing by swiping down if the content is scrolled to its top (`content Node.scrollTop === 0`) or has no scroll at all | `boolean` | `true` | +| hideTopBar | Hide top bar with resize handle | `boolean` | | +| id | ID of the sheet, used as hash in URL. It's important to specify different `id` values if there can be more than one sheet on the page | `string` | `modal` | +| title | Title of the sheet window | `string` | `undefined` | +| className | HTML `class` attribute | `string` | `undefined` | +| contentClassName | HTML `class` attribute for the sheet content | `string` | `undefined` | +| swipeAreaClassName | HTML `class` attribute for the swipe area | `string` | `undefined` | +| onClose | Handler for close event | `function` | `undefined` | ## CSS API From 7f66d28d9e3b43ebe388b90b6bc8e2799962d0c0 Mon Sep 17 00:00:00 2001 From: Isaev Alexandr Date: Thu, 18 Apr 2024 15:16:17 +0300 Subject: [PATCH 3/5] feat(useList): added ability to define initial value to useListState (#1483) Co-authored-by: Alexandr Isaev --- src/components/TreeSelect/TreeSelect.tsx | 10 +-- ...pSelectionControlledStateAndCustomIcon.tsx | 13 ++-- .../useList/__stories__/useList.mdx | 64 +++++++++++++------ src/components/useList/hooks/useListState.ts | 42 ++++++++++-- 4 files changed, 95 insertions(+), 34 deletions(-) diff --git a/src/components/TreeSelect/TreeSelect.tsx b/src/components/TreeSelect/TreeSelect.tsx index 1c271ee78b..9a9f0d4cfe 100644 --- a/src/components/TreeSelect/TreeSelect.tsx +++ b/src/components/TreeSelect/TreeSelect.tsx @@ -80,10 +80,12 @@ export const TreeSelect = React.forwardRef(function TreeSelect( }); const listState = useListState({ - expandedById, - disabledById, - activeItemId, - selectedById: selected, + controlledValues: { + expandedById, + disabledById, + activeItemId, + selectedById: selected, + }, }); const setActiveItemId = propsSetActiveItemId ?? listState.setActiveItemId; diff --git a/src/components/TreeSelect/__stories__/components/WithGroupSelectionControlledStateAndCustomIcon.tsx b/src/components/TreeSelect/__stories__/components/WithGroupSelectionControlledStateAndCustomIcon.tsx index 651cc0f0b6..b08c1c0504 100644 --- a/src/components/TreeSelect/__stories__/components/WithGroupSelectionControlledStateAndCustomIcon.tsx +++ b/src/components/TreeSelect/__stories__/components/WithGroupSelectionControlledStateAndCustomIcon.tsx @@ -5,8 +5,8 @@ import {ChevronDown, ChevronUp, Database, PlugConnection} from '@gravity-ui/icon import {Button} from '../../../Button'; import {Icon} from '../../../Icon'; import {Flex, spacing} from '../../../layout'; -import {ListItemView, getListParsedState} from '../../../useList'; -import type {ListItemCommonProps, ListItemId} from '../../../useList'; +import {ListItemView, getListParsedState, useListState} from '../../../useList'; +import type {ListItemCommonProps} from '../../../useList'; import {createRandomizedData} from '../../../useList/__stories__/utils/makeData'; import {TreeSelect} from '../../TreeSelect'; import type {TreeSelectProps} from '../../types'; @@ -40,9 +40,12 @@ export const WithGroupSelectionControlledStateAndCustomIconExample = ({ ); const [value, setValue] = React.useState([]); - const [expandedById, setExpanded] = React.useState>( - () => getListParsedState(items).initialState.expandedById, - ); + + const {expandedById, setExpanded} = useListState({ + initialValues: { + expandedById: getListParsedState(items).initialState.expandedById, + }, + }); return ( diff --git a/src/components/useList/__stories__/useList.mdx b/src/components/useList/__stories__/useList.mdx index df4c58f5ca..189da940b7 100644 --- a/src/components/useList/__stories__/useList.mdx +++ b/src/components/useList/__stories__/useList.mdx @@ -380,6 +380,32 @@ const { } = useListState(); ``` +#### props: + +```tsx +interface UseListStateProps { + /** + * Initial state values + */ + initialValues?: Partial; + /** + * Ability to pass link to another state value + */ + controlledValues?: Partial; +} +``` + +##### controlledValues example: + +```tsx +const listState = useListState(); + +// inside your component +const innerListState = useListState({ + controlledValues: listState, +}); +``` + ## Components: ### ListItemView @@ -389,33 +415,33 @@ Use it even if the functionality of the `useList` hook seems redundant to you ```tsx import { - type unstable_ListItemType as ListItemType, - unstable_ListItemView as ListItemView, + type unstable_ListItemType as ListItemType, + unstable_ListItemView as ListItemView, } from '@gravity-ui/uikit/unstable'; type Entity = {title: stirng, subtitle: string, icon: React.ReactNode}; const items: ListItemType[] = [ - {title: 'some title 1', subtitle: 'some subtitle 1', icon: }, - {title: 'some title 2', subtitle: 'some subtitle 2', icon: }, + {title: 'some title 1', subtitle: 'some subtitle 1', icon: }, + {title: 'some title 2', subtitle: 'some subtitle 2', icon: }, ]; const List = () => { - return ( - <> - {items.map(item, i) => { - return ( - - ) - }} - - ) + return ( + <> + {items.map(item, i) => { + return ( + + ) + }} + + ) }; ``` diff --git a/src/components/useList/hooks/useListState.ts b/src/components/useList/hooks/useListState.ts index 5b3165abe9..2e51d0097e 100644 --- a/src/components/useList/hooks/useListState.ts +++ b/src/components/useList/hooks/useListState.ts @@ -3,7 +3,25 @@ import React from 'react'; import type {ListState} from '../types'; -interface UseListStateProps extends Partial {} +interface UseListStateProps { + /** + * Initial state values + */ + initialValues?: Partial; + /** + * Ability to pass link to another state value + * + * ```tsx + * const listState = useListState() + * + * // inside your component + * const innerListState = useListState({ + * controlledValues: listState + * }) + * ``` + */ + controlledValues?: Partial; +} function useControlledState(value: T, defaultValue: T) { const [state, setState] = React.useState(value || defaultValue); @@ -11,11 +29,23 @@ function useControlledState(value: T, defaultValue: T) { return [value || state, setState] as const; } -export const useListState = (props: UseListStateProps = {}) => { - const [disabledById, setDisabled] = useControlledState(props.disabledById!, {}); - const [selectedById, setSelected] = useControlledState(props.selectedById!, {}); - const [expandedById, setExpanded] = useControlledState(props.expandedById!, {}); - const [activeItemId, setActiveItemId] = useControlledState(props.activeItemId, undefined); +export const useListState = ({initialValues, controlledValues}: UseListStateProps = {}) => { + const [disabledById, setDisabled] = useControlledState( + controlledValues?.disabledById!, + initialValues?.disabledById || {}, + ); + const [selectedById, setSelected] = useControlledState( + controlledValues?.selectedById!, + initialValues?.selectedById || {}, + ); + const [expandedById, setExpanded] = useControlledState( + controlledValues?.expandedById!, + initialValues?.expandedById || {}, + ); + const [activeItemId, setActiveItemId] = useControlledState( + controlledValues?.activeItemId, + initialValues?.activeItemId, + ); return { disabledById, From 648b28b809fcd8a74d813bb96f4ddfd3f6359d36 Mon Sep 17 00:00:00 2001 From: Gravity UI Bot <111915794+gravity-ui-bot@users.noreply.github.com> Date: Thu, 18 Apr 2024 16:32:07 +0300 Subject: [PATCH 4/5] chore(main): release 6.12.0 (#1521) --- CHANGELOG.md | 12 ++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75383bda71..2488a80b4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [6.12.0](https://github.com/gravity-ui/uikit/compare/v6.11.0...v6.12.0) (2024-04-18) + + +### Features + +* **useList:** added ability to define initial value to useListState ([#1483](https://github.com/gravity-ui/uikit/issues/1483)) ([7f66d28](https://github.com/gravity-ui/uikit/commit/7f66d28d9e3b43ebe388b90b6bc8e2799962d0c0)) + + +### Bug Fixes + +* do not call warnOnce function in production ([#1520](https://github.com/gravity-ui/uikit/issues/1520)) ([9ab7d7c](https://github.com/gravity-ui/uikit/commit/9ab7d7c86a06a6f94126a87acfb23033709ec8a1)) + ## [6.11.0](https://github.com/gravity-ui/uikit/compare/v6.10.2...v6.11.0) (2024-04-17) diff --git a/package-lock.json b/package-lock.json index deb4d456e0..eafce34a02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gravity-ui/uikit", - "version": "6.11.0", + "version": "6.12.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gravity-ui/uikit", - "version": "6.11.0", + "version": "6.12.0", "license": "MIT", "dependencies": { "@bem-react/classname": "^1.6.0", diff --git a/package.json b/package.json index 6e589e5901..e925d99f98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gravity-ui/uikit", - "version": "6.11.0", + "version": "6.12.0", "description": "Gravity UI base styling and components", "license": "MIT", "repository": { From d89610a8c7bc952ecf0db4c2548c8c5dde9bc710 Mon Sep 17 00:00:00 2001 From: Isaev Alexandr Date: Fri, 19 Apr 2024 16:22:43 +0300 Subject: [PATCH 5/5] feat(layout): unification of Theme and Layout Providers (#1518) Co-authored-by: Alexandr Isaev --- .../layout/LayoutProvider/LayoutProvider.tsx | 22 ++++--- .../LayoutProvider/__stories__/Layout.mdx | 63 ++++++------------- .../demo/LayoutPresenter/LayoutPresenter.tsx | 6 +- src/components/layout/index.ts | 2 +- src/components/theme/ThemeProvider.tsx | 54 +++++++++------- 5 files changed, 66 insertions(+), 81 deletions(-) diff --git a/src/components/layout/LayoutProvider/LayoutProvider.tsx b/src/components/layout/LayoutProvider/LayoutProvider.tsx index 2152a7dbe1..2df3977a0d 100644 --- a/src/components/layout/LayoutProvider/LayoutProvider.tsx +++ b/src/components/layout/LayoutProvider/LayoutProvider.tsx @@ -6,21 +6,16 @@ import {useCurrentActiveMediaQuery} from '../hooks/useCurrentActiveMediaQuery'; import type {LayoutTheme, MediaType, RecursivePartial} from '../types'; import {makeLayoutDefaultTheme} from '../utils/makeLayoutDefaultTheme'; -interface LayoutProviderProps { +export interface LayoutProviderProps { theme?: RecursivePartial; /** - * During ssr you can override default (`s`) media screen size + * During ssr you can override default (`s`) media screen size if needed */ initialMediaQuery?: MediaType; children: React.ReactNode; } -/** - * Provide context for layout components and current media queries. - * --- - * Storybook - https://preview.gravity-ui.com/uikit/?path=/docs/layout--playground#layoutprovider-and-layouttheme - */ -export function LayoutProvider({ +export function PrivateLayoutProvider({ children, theme: override, initialMediaQuery, @@ -39,3 +34,14 @@ export function LayoutProvider({ ); } + +/** + * @deprecated - already used as part of ThemeProvider. To override layout theme use `layout` prop + * + * Provide context for layout components and current media queries. + * --- + * Storybook - https://preview.gravity-ui.com/uikit/?path=/docs/layout--playground#layoutprovider-and-layouttheme + */ +export function LayoutProvider({children}: LayoutProviderProps) { + return children; +} diff --git a/src/components/layout/LayoutProvider/__stories__/Layout.mdx b/src/components/layout/LayoutProvider/__stories__/Layout.mdx index 1e9ff003ca..3306d2eaf4 100644 --- a/src/components/layout/LayoutProvider/__stories__/Layout.mdx +++ b/src/components/layout/LayoutProvider/__stories__/Layout.mdx @@ -32,7 +32,6 @@ import {Container, Row, Col, Flex} from '@gravity-ui/uikit'; ### Components: -- [LayoutProvider and LayoutTheme](#layoutprovider-and-layouttheme) - [Layout Grid](#layout-grid) - [Row](#row) - [Col](#col) @@ -75,17 +74,17 @@ _You can override default values on project level:_ ``` ```tsx -import {LayoutProvider, LayoutTheme} from '@gravity-ui/uikit'; +import {ThemeProvider, LayoutTheme} from '@gravity-ui/uikit'; -const layoutTheme: LayoutTheme = { +const theme: LayoutTheme = { spaceBaseSize: 5, }; export const App = () => { return ( - + {...} - + ); }; ``` @@ -121,55 +120,29 @@ We use `mobile-first` approach. It means that you should adapt you app to deskto > To override breakpoint use `theme` breakpoints property; ```tsx - export const APP_LAYOUT_THEME: LayoutTheme = { + const APP_LAYOUT_THEME: LayoutTheme = { + spaceBaseSize: 4, + components: { + container: { + gutters: 3, + media: { + l: { + gutters: 5, + }, + }, + }, + }, breakpoints: { s: 320, l: 980, } }; - + {...} - + ``` -## LayoutProvider and LayoutTheme - -Through `LayoutProvider` components can get default props which are corresponding to different screen sizes. - -Usage of `LayoutProvider` is optional. Use it if you need to override default spacing or add default behaviour to connected to theme compoennts (now only `Container`) - -### props: - -- `theme` - partial LayoutTheme onject that will be override original theme; -- `initialMediaQuery` - use can directly pass initial - -```tsx -import {LayoutProvider, LayoutTheme} from '@gravity-ui/uikit'; - -export const APP_LAYOUT_THEME: LayoutTheme = { - spaceBaseSize: 4, - components: { - container: { - gutters: 3, - media: { - l: { - gutters: 5, - }, - }, - }, - }, -}; - -export const App = () => { - return ( - - - - ); -}; -``` - ## Box The `Box` component is a developer friend and basic block to build other components. Aware about spacing, its own sizes and most commonly used CSS properties. diff --git a/src/components/layout/demo/LayoutPresenter/LayoutPresenter.tsx b/src/components/layout/demo/LayoutPresenter/LayoutPresenter.tsx index af3b47e93b..48f9c70944 100644 --- a/src/components/layout/demo/LayoutPresenter/LayoutPresenter.tsx +++ b/src/components/layout/demo/LayoutPresenter/LayoutPresenter.tsx @@ -2,8 +2,8 @@ import React from 'react'; import {Text} from '../../../Text'; import type {LayoutTheme} from '../../../layout'; +import {ThemeProvider} from '../../../theme'; import {Flex} from '../../Flex/Flex'; -import {LayoutProvider} from '../../LayoutProvider/LayoutProvider'; import {useLayoutContext} from '../../hooks/useLayoutContext'; import {sp} from '../../spacing/spacing'; @@ -35,7 +35,7 @@ function Title({title}: {title?: string}) { export const LayoutPresenter = ({children, title, theme}: LayoutPresenterProps) => { return ( - + <div style={{ @@ -46,6 +46,6 @@ export const LayoutPresenter = ({children, title, theme}: LayoutPresenterProps) > {children} </div> - </LayoutProvider> + </ThemeProvider> ); }; diff --git a/src/components/layout/index.ts b/src/components/layout/index.ts index 3a565b5525..51a6a55bd6 100644 --- a/src/components/layout/index.ts +++ b/src/components/layout/index.ts @@ -4,7 +4,7 @@ export * from './Row/Row'; export * from './Flex/Flex'; export * from './Box/Box'; export * from './Container/Container'; -export * from './LayoutProvider/LayoutProvider'; +export {LayoutProvider} from './LayoutProvider/LayoutProvider'; export * from './spacing/spacing'; export * from './hooks/useLayoutContext'; diff --git a/src/components/theme/ThemeProvider.tsx b/src/components/theme/ThemeProvider.tsx index 5fc22bc764..53ba935a54 100644 --- a/src/components/theme/ThemeProvider.tsx +++ b/src/components/theme/ThemeProvider.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import {PrivateLayoutProvider} from '../layout/LayoutProvider/LayoutProvider'; +import type {LayoutProviderProps} from '../layout/LayoutProvider/LayoutProvider'; import {block} from '../utils/cn'; import {ThemeContext} from './ThemeContext'; @@ -26,6 +28,7 @@ export interface ThemeProviderProps extends React.PropsWithChildren<{}> { nativeScrollbar?: boolean; scoped?: boolean; rootClassName?: string; + layout?: Omit<LayoutProviderProps, 'children'>; } export function ThemeProvider({ @@ -37,6 +40,7 @@ export function ThemeProvider({ scoped: scopedProp = false, rootClassName = '', children, + layout, }: ThemeProviderProps) { const parentThemeState = React.useContext(ThemeContext); const systemThemeState = React.useContext(ThemeSettingsContext); @@ -86,30 +90,32 @@ export function ThemeProvider({ ); return ( - <ThemeContext.Provider value={contextValue}> - <ThemeSettingsContext.Provider value={themeSettingsContext}> - {scoped ? ( - <div - className={b( - { - theme: themeValue, - 'native-scrollbar': nativeScrollbar !== false, - }, - rootClassName, - )} - dir={ - hasParentProvider && direction === parentDirection - ? undefined - : direction - } - > - {children} - </div> - ) : ( - children - )} - </ThemeSettingsContext.Provider> - </ThemeContext.Provider> + <PrivateLayoutProvider {...layout}> + <ThemeContext.Provider value={contextValue}> + <ThemeSettingsContext.Provider value={themeSettingsContext}> + {scoped ? ( + <div + className={b( + { + theme: themeValue, + 'native-scrollbar': nativeScrollbar !== false, + }, + rootClassName, + )} + dir={ + hasParentProvider && direction === parentDirection + ? undefined + : direction + } + > + {children} + </div> + ) : ( + children + )} + </ThemeSettingsContext.Provider> + </ThemeContext.Provider> + </PrivateLayoutProvider> ); }