From 12c6b102abe88de1d9f67e2829b911f29e4e1556 Mon Sep 17 00:00:00 2001 From: Gravity UI Bot <111915794+gravity-ui-bot@users.noreply.github.com> Date: Tue, 11 Jun 2024 20:13:33 +0300 Subject: [PATCH 1/3] chore(main): release 6.17.0 (#1626) --- CHANGELOG.md | 17 +++++++++++++++++ package-lock.json | 4 ++-- package.json | 10 +++++----- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36c7833c92..e0e57363f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## [6.17.0](https://github.com/gravity-ui/uikit/compare/v6.16.0...v6.17.0) (2024-06-11) + + +### Features + +* add PinInput component ([#1557](https://github.com/gravity-ui/uikit/issues/1557)) ([7d26272](https://github.com/gravity-ui/uikit/commit/7d26272ed67e7f94f002f501a9ff703f4210a925)) + + +### Bug Fixes + +* **Button:** normal-contrast loading view ([#1630](https://github.com/gravity-ui/uikit/issues/1630)) ([bfa2dbf](https://github.com/gravity-ui/uikit/commit/bfa2dbfa85c2c01e0fcd8c5f07d6e93fa31660ff)) +* **Slider:** export function for preparing initial value ([#1637](https://github.com/gravity-ui/uikit/issues/1637)) ([434949e](https://github.com/gravity-ui/uikit/commit/434949e4f608cde2330cca02cbd0ab99244fd0fb)) +* **Text:** return missing props to TextProps type ([#1631](https://github.com/gravity-ui/uikit/issues/1631)) ([6a70f17](https://github.com/gravity-ui/uikit/commit/6a70f17c04f4af52e49ea5bcbe9c1f886d9c3969)) +* **theme:** bring back root classname helper and fix docs ([#1633](https://github.com/gravity-ui/uikit/issues/1633)) ([ba08c66](https://github.com/gravity-ui/uikit/commit/ba08c66d83dac2d8ff9d44c27f2739f192d68e92)) +* **Toc:** unify item line height ([#1635](https://github.com/gravity-ui/uikit/issues/1635)) ([4612a77](https://github.com/gravity-ui/uikit/commit/4612a7713ee04590a8e4073d831a42ba2e03d9fe)) +* **useList:** add qa property to container ([#1625](https://github.com/gravity-ui/uikit/issues/1625)) ([05c1fc2](https://github.com/gravity-ui/uikit/commit/05c1fc2df61226cc070c007388c1aeb5e5744799)) + ## [6.16.0](https://github.com/gravity-ui/uikit/compare/v6.15.0...v6.16.0) (2024-05-24) diff --git a/package-lock.json b/package-lock.json index f047990186..8221684b60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gravity-ui/uikit", - "version": "6.16.0", + "version": "6.17.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gravity-ui/uikit", - "version": "6.16.0", + "version": "6.17.0", "license": "MIT", "dependencies": { "@bem-react/classname": "^1.6.0", diff --git a/package.json b/package.json index d78fa69cf1..6b93a18b81 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "@gravity-ui/uikit", - "version": "6.16.0", + "version": "6.17.0", "description": "Gravity UI base styling and components", "license": "MIT", "engines": { - "node": ">= 18", - "npm": ">= 9", - "yarn": "Please use npm instead of yarn to install dependencies", - "pnpm": "Please use npm instead of pnpm to install dependencies" + "node": ">= 18", + "npm": ">= 9", + "yarn": "Please use npm instead of yarn to install dependencies", + "pnpm": "Please use npm instead of pnpm to install dependencies" }, "repository": { "type": "git", From 770d787ef5952de5ccc45595065fa50b1f1d48f9 Mon Sep 17 00:00:00 2001 From: Valerii Sidorenko Date: Thu, 13 Jun 2024 12:32:22 +0200 Subject: [PATCH 2/3] feat: support RSC (#1582) --- package-lock.json | 26 ++++++------ package.json | 4 +- .../ActionTooltip/ActionTooltip.tsx | 2 + src/components/Alert/AlertAction.tsx | 2 + src/components/Alert/AlertActions.tsx | 2 + src/components/Alert/AlertContextProvider.tsx | 2 + src/components/Alert/useAlertContext.tsx | 2 + src/components/Avatar/Avatar.tsx | 5 +-- .../Avatar/AvatarIcon/AvatarIcon.tsx | 2 +- .../Avatar/AvatarImage/AvatarImage.tsx | 2 + .../Avatar/AvatarText/AvatarText.tsx | 4 +- src/components/Avatar/AvatarText/utils.ts | 2 +- src/components/Breadcrumbs/Breadcrumbs.tsx | 2 + .../Breadcrumbs/BreadcrumbsItem.tsx | 2 + .../Breadcrumbs/BreadcrumbsMore.tsx | 2 + src/components/Button/Button.tsx | 34 ++++++++++++---- src/components/Button/ButtonIcon.tsx | 28 +++++++------ src/components/Button/index.ts | 1 + src/components/Card/Card.tsx | 2 + src/components/Checkbox/Checkbox.tsx | 2 + .../Checkbox/__stories__/CheckboxShowcase.tsx | 40 +++++++++---------- .../ClipboardButton/ClipboardButton.tsx | 2 + .../CopyToClipboard/CopyToClipboard.tsx | 2 + src/components/Dialog/Dialog.tsx | 2 + .../Dialog/DialogFooter/DialogFooter.tsx | 2 + src/components/Disclosure/Disclosure.tsx | 2 + .../Disclosure/DisclosureContext.tsx | 2 + .../DisclosureDetails/DisclosureDetails.tsx | 2 + src/components/DropdownMenu/DropdownMenu.tsx | 2 + .../DropdownMenu/DropdownMenuItem.tsx | 2 + .../DropdownMenuNavigationContext.tsx | 2 + .../DropdownMenu/DropdownMenuPopup.tsx | 2 + src/components/Hotkey/Hotkey.tsx | 2 + src/components/Label/Label.tsx | 2 + src/components/Link/Link.tsx | 2 + src/components/List/List.tsx | 2 + src/components/List/ListLoadingIndicator.tsx | 2 + src/components/List/components/ListItem.tsx | 2 + .../List/components/SimpleContainer.tsx | 2 + src/components/Menu/Menu.tsx | 2 + src/components/Menu/MenuItem.tsx | 4 +- src/components/Menu/index.ts | 2 + src/components/Modal/Modal.tsx | 2 + src/components/Pagination/Pagination.tsx | 2 + .../PaginationButton/PaginationButton.tsx | 2 + .../PaginationInput/PaginationInput.tsx | 2 + .../PaginationPage/PaginationPage.tsx | 2 + .../PaginationPageSizer.tsx | 2 + src/components/Palette/Palette.tsx | 2 + src/components/Popover/Popover.tsx | 2 + .../Popover/components/Trigger/Trigger.tsx | 2 + src/components/Popup/Popup.tsx | 2 + src/components/Portal/Portal.tsx | 2 + src/components/Radio/Radio.tsx | 2 + .../Radio/__stories__/RadioShowcase.tsx | 32 +++++++-------- src/components/RadioButton/RadioButton.tsx | 9 +++-- .../RadioButton/RadioButtonOption.tsx | 6 ++- .../__stories__/RadioButtonShowcase.tsx | 12 +++--- src/components/RadioButton/index.ts | 2 + src/components/RadioGroup/RadioGroup.tsx | 2 + src/components/Select/Select.tsx | 2 + .../SelectControl/SelectControl.tsx | 2 + .../SelectCounter/SelectCounter.tsx | 9 ++--- .../components/SelectFilter/SelectFilter.tsx | 2 + .../components/SelectList/SelectList.tsx | 2 + .../SelectList/SelectLoadingIndicator.tsx | 2 + .../components/SelectPopup/SelectPopup.tsx | 2 + src/components/Select/index.ts | 2 + src/components/Select/tech-components.tsx | 2 + src/components/Sheet/Sheet.tsx | 2 + src/components/Sheet/SheetContent.tsx | 2 + .../Slider/BaseSlider/BaseSlider.tsx | 2 + src/components/Slider/Slider.tsx | 2 + .../Slider/SliderTooltip/SliderTooltip.tsx | 2 + src/components/Switch/Switch.tsx | 2 + src/components/Table/Table.tsx | 2 + .../hoc/withTableActions/withTableActions.tsx | 2 + .../Table/hoc/withTableCopy/withTableCopy.tsx | 2 + .../withTableSelection/withTableSelection.tsx | 2 + .../TableColumnSetup/TableColumnSetup.tsx | 2 + .../withTableSettings/withTableSettings.tsx | 2 + .../hoc/withTableSorting/withTableSorting.tsx | 2 + .../TableColumnSetup/TableColumnSetup.tsx | 2 + src/components/Tabs/Tabs.tsx | 2 + src/components/Tabs/TabsItem.tsx | 2 + .../Toaster/Provider/ToasterProvider.tsx | 2 + src/components/Toaster/Toast/Toast.tsx | 2 + .../Toaster/ToastList/ToastList.tsx | 2 + .../ToasterComponent/ToasterComponent.tsx | 2 + .../ToasterComponent/ToasterPortal.tsx | 2 + src/components/Toaster/ToasterSingleton.tsx | 2 + src/components/Toc/TocItem/TocItem.tsx | 2 + src/components/Tooltip/Tooltip.tsx | 2 + src/components/TreeList/TreeList.tsx | 2 + src/components/TreeSelect/TreeSelect.tsx | 2 + src/components/controls/TextArea/TextArea.tsx | 2 + .../controls/TextArea/TextAreaControl.tsx | 2 + .../controls/TextInput/TextInput.tsx | 2 + .../common/ClearButton/ClearButton.tsx | 2 + src/components/layout/Col/Col.tsx | 2 + src/components/layout/Container/Container.tsx | 2 + src/components/layout/Flex/Flex.tsx | 2 + .../layout/LayoutProvider/LayoutProvider.tsx | 2 + src/components/layout/Row/Row.tsx | 2 + src/components/mobile/MobileProvider.tsx | 2 + src/components/theme/ThemeProvider.tsx | 2 + src/components/utils/FocusTrap.tsx | 2 + src/components/utils/common.ts | 1 + .../utils/event-broker/useEventBroker.ts | 2 + .../utils/layer-manager/LayerManager.ts | 2 + .../utils/withEventBrokerDomHandlers.tsx | 2 + .../usePortalContainer/PortalProvider.tsx | 2 + 112 files changed, 311 insertions(+), 98 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8221684b60..c8b2cd4d5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,9 +82,9 @@ "npm-run-all": "^4.1.5", "postcss": "^8.4.33", "prettier": "^3.2.4", - "react": "^18.2.0", + "react": "^18.3.1", "react-docgen-typescript": "^2.2.2", - "react-dom": "^18.2.0", + "react-dom": "^18.3.1", "rimraf": "^5.0.5", "sass": "^1.70.0", "sass-loader": "^14.0.0", @@ -22975,9 +22975,9 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dev": true, "dependencies": { "loose-envify": "^1.1.0" @@ -23078,16 +23078,16 @@ } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dev": true, "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-element-to-jsx-string": { @@ -24140,9 +24140,9 @@ } }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dev": true, "dependencies": { "loose-envify": "^1.1.0" diff --git a/package.json b/package.json index 6b93a18b81..865f62934a 100644 --- a/package.json +++ b/package.json @@ -179,9 +179,9 @@ "npm-run-all": "^4.1.5", "postcss": "^8.4.33", "prettier": "^3.2.4", - "react": "^18.2.0", + "react": "^18.3.1", "react-docgen-typescript": "^2.2.2", - "react-dom": "^18.2.0", + "react-dom": "^18.3.1", "rimraf": "^5.0.5", "sass": "^1.70.0", "sass-loader": "^14.0.0", diff --git a/src/components/ActionTooltip/ActionTooltip.tsx b/src/components/ActionTooltip/ActionTooltip.tsx index e52ef2b6a8..c65ec3bdd5 100644 --- a/src/components/ActionTooltip/ActionTooltip.tsx +++ b/src/components/ActionTooltip/ActionTooltip.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useForkRef} from '../../hooks'; diff --git a/src/components/Alert/AlertAction.tsx b/src/components/Alert/AlertAction.tsx index ac42f7577d..136bdd3add 100644 --- a/src/components/Alert/AlertAction.tsx +++ b/src/components/Alert/AlertAction.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {Button} from '../Button'; diff --git a/src/components/Alert/AlertActions.tsx b/src/components/Alert/AlertActions.tsx index a90d87a270..7634e11cf3 100644 --- a/src/components/Alert/AlertActions.tsx +++ b/src/components/Alert/AlertActions.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {Flex} from '../layout'; diff --git a/src/components/Alert/AlertContextProvider.tsx b/src/components/Alert/AlertContextProvider.tsx index 50b83ac1e5..df65e640a4 100644 --- a/src/components/Alert/AlertContextProvider.tsx +++ b/src/components/Alert/AlertContextProvider.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {AlertContext} from './AlertContext'; diff --git a/src/components/Alert/useAlertContext.tsx b/src/components/Alert/useAlertContext.tsx index e63fd26b82..638e7eb0ae 100644 --- a/src/components/Alert/useAlertContext.tsx +++ b/src/components/Alert/useAlertContext.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {AlertContext} from './AlertContext'; diff --git a/src/components/Avatar/Avatar.tsx b/src/components/Avatar/Avatar.tsx index 95190acb8f..61b5cef88c 100644 --- a/src/components/Avatar/Avatar.tsx +++ b/src/components/Avatar/Avatar.tsx @@ -27,10 +27,7 @@ export const Avatar = React.forwardRef((props, ref) qa, } = props; - const style = React.useMemo( - () => ({backgroundColor, color: borderColor, ...styleProp}), - [backgroundColor, borderColor, styleProp], - ); + const style = {backgroundColor, color: borderColor, ...styleProp}; const renderContent = () => { if ('imgUrl' in props && props.imgUrl) { diff --git a/src/components/Avatar/AvatarIcon/AvatarIcon.tsx b/src/components/Avatar/AvatarIcon/AvatarIcon.tsx index f3cd33d9d6..d063035afe 100644 --- a/src/components/Avatar/AvatarIcon/AvatarIcon.tsx +++ b/src/components/Avatar/AvatarIcon/AvatarIcon.tsx @@ -14,7 +14,7 @@ const avatarSizeToIconSize: Record = { }; export const AvatarIcon = ({icon, color, size, className}: AvatarIconProps) => { - const style = React.useMemo(() => ({color}), [color]); + const style = {color}; return (
diff --git a/src/components/Avatar/AvatarImage/AvatarImage.tsx b/src/components/Avatar/AvatarImage/AvatarImage.tsx index 303686b7cd..27fe626bf0 100644 --- a/src/components/Avatar/AvatarImage/AvatarImage.tsx +++ b/src/components/Avatar/AvatarImage/AvatarImage.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {AVATAR_SIZES} from '../constants'; diff --git a/src/components/Avatar/AvatarText/AvatarText.tsx b/src/components/Avatar/AvatarText/AvatarText.tsx index fb491afc27..05f3c9cb92 100644 --- a/src/components/Avatar/AvatarText/AvatarText.tsx +++ b/src/components/Avatar/AvatarText/AvatarText.tsx @@ -4,8 +4,8 @@ import type {AvatarTextProps} from './types'; import {getAvatarDisplayText} from './utils'; export const AvatarText = ({text, color, className}: AvatarTextProps) => { - const style = React.useMemo(() => ({color}), [color]); - const displayText = React.useMemo(() => getAvatarDisplayText(text), [text]); + const style = {color}; + const displayText = getAvatarDisplayText(text); return (
diff --git a/src/components/Avatar/AvatarText/utils.ts b/src/components/Avatar/AvatarText/utils.ts index 97e3e504fb..1ab9966299 100644 --- a/src/components/Avatar/AvatarText/utils.ts +++ b/src/components/Avatar/AvatarText/utils.ts @@ -1,5 +1,5 @@ export const getAvatarDisplayText = (text: string) => { - const words = text.split(' '); + const words = text.split(/\s+/); const result = words.length > 1 ? [words[0][0], words[1][0]].filter(Boolean).join('') : text.slice(0, 2); diff --git a/src/components/Breadcrumbs/Breadcrumbs.tsx b/src/components/Breadcrumbs/Breadcrumbs.tsx index c7e216f0f8..ac9a9e2f10 100644 --- a/src/components/Breadcrumbs/Breadcrumbs.tsx +++ b/src/components/Breadcrumbs/Breadcrumbs.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import _throttle from 'lodash/throttle'; diff --git a/src/components/Breadcrumbs/BreadcrumbsItem.tsx b/src/components/Breadcrumbs/BreadcrumbsItem.tsx index 652413e46a..ade8680eab 100644 --- a/src/components/Breadcrumbs/BreadcrumbsItem.tsx +++ b/src/components/Breadcrumbs/BreadcrumbsItem.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {Link} from '../Link'; diff --git a/src/components/Breadcrumbs/BreadcrumbsMore.tsx b/src/components/Breadcrumbs/BreadcrumbsMore.tsx index 6effc9708b..c5eb217af4 100644 --- a/src/components/Breadcrumbs/BreadcrumbsMore.tsx +++ b/src/components/Breadcrumbs/BreadcrumbsMore.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {DropdownMenu} from '../DropdownMenu'; diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index babe0860ba..02b295d71c 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -1,12 +1,14 @@ +'use client'; + import React from 'react'; import type {DOMProps, QAProps} from '../types'; import {block} from '../utils/cn'; -import {isIcon} from '../utils/common'; +import {isIcon, isSvg} from '../utils/common'; import {eventBroker} from '../utils/event-broker'; import {isOfType} from '../utils/isOfType'; -import {ButtonIcon} from './ButtonIcon'; +import {ButtonIcon, getIconSide} from './ButtonIcon'; import './Button.scss'; @@ -193,16 +195,22 @@ ButtonWithHandlers.displayName = 'Button'; export const Button = Object.assign(ButtonWithHandlers, {Icon: ButtonIcon}); const isButtonIconComponent = isOfType(ButtonIcon); +const isSpan = isOfType<{className?: string}>('span'); +const buttonIconClassRe = RegExp(`^${b('icon')}($|\\s+\\w)`); +// eslint-disable-next-line complexity function prepareChildren(children: React.ReactNode) { const items = React.Children.toArray(children); if (items.length === 1) { const onlyItem = items[0]; + const isButtonIconElement = + isButtonIconComponent(onlyItem) || + (isSpan(onlyItem) && buttonIconClassRe.test(onlyItem.props.className || '')); - if (isButtonIconComponent(onlyItem)) { + if (isButtonIconElement) { return onlyItem; - } else if (isIcon(onlyItem)) { + } else if (isIcon(onlyItem) || isSvg(onlyItem)) { return {onlyItem}; } else { return ( @@ -216,10 +224,12 @@ function prepareChildren(children: React.ReactNode) { const content = []; for (const item of items) { - const isIconElement = isIcon(item); + const isIconElement = isIcon(item) || isSvg(item); const isButtonIconElement = isButtonIconComponent(item); + const isRenderedButtonIconElement = + isSpan(item) && buttonIconClassRe.test(item.props.className || ''); - if (isIconElement || isButtonIconElement) { + if (isIconElement || isButtonIconElement || isRenderedButtonIconElement) { if (!startIcon && content.length === 0) { const key = 'icon-start'; const side = 'start'; @@ -229,10 +239,14 @@ function prepareChildren(children: React.ReactNode) { {item} ); - } else { + } else if (isButtonIconElement) { startIcon = React.cloneElement(item, { side, }); + } else { + startIcon = React.cloneElement(item, { + className: b('icon', {side: getIconSide(side)}, item.props.className), + }); } } else if (!endIcon && content.length !== 0) { const key = 'icon-end'; @@ -243,10 +257,14 @@ function prepareChildren(children: React.ReactNode) { {item} ); - } else { + } else if (isButtonIconElement) { endIcon = React.cloneElement(item, { side, }); + } else { + endIcon = React.cloneElement(item, { + className: b('icon', {side: getIconSide(side)}, item.props.className), + }); } } } else { diff --git a/src/components/Button/ButtonIcon.tsx b/src/components/Button/ButtonIcon.tsx index 790b4d7c37..d3dfc97560 100644 --- a/src/components/Button/ButtonIcon.tsx +++ b/src/components/Button/ButtonIcon.tsx @@ -17,23 +17,12 @@ function warnAboutPhysicalValues() { } export const ButtonIcon = ({side, className, children}: Props) => { - let sideMod = side; - - if (sideMod === 'left') { - warnAboutPhysicalValues(); - sideMod = 'start'; - } - if (sideMod === 'right') { - warnAboutPhysicalValues(); - sideMod = 'end'; - } - return ( { }; ButtonIcon.displayName = 'Button.Icon'; + +export function getIconSide(side?: 'left' | 'right' | 'start' | 'end') { + let sideMod = side; + + if (sideMod === 'left') { + warnAboutPhysicalValues(); + sideMod = 'start'; + } + if (sideMod === 'right') { + warnAboutPhysicalValues(); + sideMod = 'end'; + } + + return sideMod; +} diff --git a/src/components/Button/index.ts b/src/components/Button/index.ts index 8b166a86e4..32e9f49703 100644 --- a/src/components/Button/index.ts +++ b/src/components/Button/index.ts @@ -1 +1,2 @@ export * from './Button'; +export {ButtonIcon} from './ButtonIcon'; diff --git a/src/components/Card/Card.tsx b/src/components/Card/Card.tsx index e7be071d1b..fa12db13ed 100644 --- a/src/components/Card/Card.tsx +++ b/src/components/Card/Card.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useActionHandlers} from '../../hooks'; diff --git a/src/components/Checkbox/Checkbox.tsx b/src/components/Checkbox/Checkbox.tsx index 98bd55ed01..958962fd0c 100644 --- a/src/components/Checkbox/Checkbox.tsx +++ b/src/components/Checkbox/Checkbox.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useCheckbox} from '../../hooks/private'; diff --git a/src/components/Checkbox/__stories__/CheckboxShowcase.tsx b/src/components/Checkbox/__stories__/CheckboxShowcase.tsx index e5588e3326..0da525b35c 100644 --- a/src/components/Checkbox/__stories__/CheckboxShowcase.tsx +++ b/src/components/Checkbox/__stories__/CheckboxShowcase.tsx @@ -8,50 +8,50 @@ export function CheckboxShowcase() { return ( -

+

-

-

+

+
-

+
-

+

-

-

+

+
-

-

+

+
-

+
-

+

-

-

+

+
-

+
-

+

-

-

+

+
-

-

+

+
-

+
); diff --git a/src/components/ClipboardButton/ClipboardButton.tsx b/src/components/ClipboardButton/ClipboardButton.tsx index e260292e37..78bfb58218 100644 --- a/src/components/ClipboardButton/ClipboardButton.tsx +++ b/src/components/ClipboardButton/ClipboardButton.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {ActionTooltip} from '../ActionTooltip'; diff --git a/src/components/CopyToClipboard/CopyToClipboard.tsx b/src/components/CopyToClipboard/CopyToClipboard.tsx index e54c4daf95..6265056c99 100644 --- a/src/components/CopyToClipboard/CopyToClipboard.tsx +++ b/src/components/CopyToClipboard/CopyToClipboard.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import ReactCopyToClipboard from 'react-copy-to-clipboard'; diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index f47663123a..15198af2f5 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {Modal} from '../Modal'; diff --git a/src/components/Dialog/DialogFooter/DialogFooter.tsx b/src/components/Dialog/DialogFooter/DialogFooter.tsx index 1834b5f848..5443699078 100644 --- a/src/components/Dialog/DialogFooter/DialogFooter.tsx +++ b/src/components/Dialog/DialogFooter/DialogFooter.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {Button} from '../../Button'; diff --git a/src/components/Disclosure/Disclosure.tsx b/src/components/Disclosure/Disclosure.tsx index 7353f0513f..33efdf70d9 100644 --- a/src/components/Disclosure/Disclosure.tsx +++ b/src/components/Disclosure/Disclosure.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import type {QAProps} from '../types'; diff --git a/src/components/Disclosure/DisclosureContext.tsx b/src/components/Disclosure/DisclosureContext.tsx index b048ac548a..f7b7d682e5 100644 --- a/src/components/Disclosure/DisclosureContext.tsx +++ b/src/components/Disclosure/DisclosureContext.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useUniqId} from '../../hooks'; diff --git a/src/components/Disclosure/DisclosureDetails/DisclosureDetails.tsx b/src/components/Disclosure/DisclosureDetails/DisclosureDetails.tsx index 5d861723a7..f2d0d6fda6 100644 --- a/src/components/Disclosure/DisclosureDetails/DisclosureDetails.tsx +++ b/src/components/Disclosure/DisclosureDetails/DisclosureDetails.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {CSSTransition} from 'react-transition-group'; diff --git a/src/components/DropdownMenu/DropdownMenu.tsx b/src/components/DropdownMenu/DropdownMenu.tsx index 74b9467a73..d8268ca2eb 100644 --- a/src/components/DropdownMenu/DropdownMenu.tsx +++ b/src/components/DropdownMenu/DropdownMenu.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {Ellipsis} from '@gravity-ui/icons'; diff --git a/src/components/DropdownMenu/DropdownMenuItem.tsx b/src/components/DropdownMenu/DropdownMenuItem.tsx index e9a4d4a04d..a202c91288 100644 --- a/src/components/DropdownMenu/DropdownMenuItem.tsx +++ b/src/components/DropdownMenu/DropdownMenuItem.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {ChevronLeft, ChevronRight} from '@gravity-ui/icons'; diff --git a/src/components/DropdownMenu/DropdownMenuNavigationContext.tsx b/src/components/DropdownMenu/DropdownMenuNavigationContext.tsx index fcce8d56fa..1638da5604 100644 --- a/src/components/DropdownMenu/DropdownMenuNavigationContext.tsx +++ b/src/components/DropdownMenu/DropdownMenuNavigationContext.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; export type DropdownMenuNavigationContextType = { diff --git a/src/components/DropdownMenu/DropdownMenuPopup.tsx b/src/components/DropdownMenu/DropdownMenuPopup.tsx index f0e38d0720..6d356ce9dc 100644 --- a/src/components/DropdownMenu/DropdownMenuPopup.tsx +++ b/src/components/DropdownMenu/DropdownMenuPopup.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useListNavigation} from '../../hooks'; diff --git a/src/components/Hotkey/Hotkey.tsx b/src/components/Hotkey/Hotkey.tsx index 36a4c0a8e1..d622b418a9 100644 --- a/src/components/Hotkey/Hotkey.tsx +++ b/src/components/Hotkey/Hotkey.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import type {DOMProps, QAProps} from '../types'; diff --git a/src/components/Label/Label.tsx b/src/components/Label/Label.tsx index f6b8816d09..1d450ec112 100644 --- a/src/components/Label/Label.tsx +++ b/src/components/Label/Label.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {Xmark} from '@gravity-ui/icons'; diff --git a/src/components/Link/Link.tsx b/src/components/Link/Link.tsx index 7118386124..2be2983f5a 100644 --- a/src/components/Link/Link.tsx +++ b/src/components/Link/Link.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import type {DOMProps, QAProps} from '../types'; diff --git a/src/components/List/List.tsx b/src/components/List/List.tsx index b69f8a7539..32e18fb0cb 100644 --- a/src/components/List/List.tsx +++ b/src/components/List/List.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import isEqual from 'lodash/isEqual'; diff --git a/src/components/List/ListLoadingIndicator.tsx b/src/components/List/ListLoadingIndicator.tsx index 885a002af8..0eb7830688 100644 --- a/src/components/List/ListLoadingIndicator.tsx +++ b/src/components/List/ListLoadingIndicator.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useIntersection} from '../../hooks'; diff --git a/src/components/List/components/ListItem.tsx b/src/components/List/components/ListItem.tsx index 4a88a4b700..39921bc332 100644 --- a/src/components/List/components/ListItem.tsx +++ b/src/components/List/components/ListItem.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {Grip} from '@gravity-ui/icons'; diff --git a/src/components/List/components/SimpleContainer.tsx b/src/components/List/components/SimpleContainer.tsx index 2d39243b25..07b67e42bd 100644 --- a/src/components/List/components/SimpleContainer.tsx +++ b/src/components/List/components/SimpleContainer.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import _range from 'lodash/range'; diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx index 50c1ffbfdf..c5d856af7c 100644 --- a/src/components/Menu/Menu.tsx +++ b/src/components/Menu/Menu.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import type {DOMProps, QAProps} from '../types'; diff --git a/src/components/Menu/MenuItem.tsx b/src/components/Menu/MenuItem.tsx index a3336fad69..340d545d82 100644 --- a/src/components/Menu/MenuItem.tsx +++ b/src/components/Menu/MenuItem.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useActionHandlers} from '../../hooks'; @@ -72,7 +74,7 @@ export const MenuItem = React.forwardRef(function Me tabIndex: disabled ? -1 : 0, className: b( 'item', - {disabled, active, selected, theme, interactive: Boolean(onClick)}, + {disabled, active, selected, theme, interactive: Boolean(onClick) || Boolean(href)}, className, ), 'data-qa': qa, diff --git a/src/components/Menu/index.ts b/src/components/Menu/index.ts index 629d3d0aa1..2b5f8fdd98 100644 --- a/src/components/Menu/index.ts +++ b/src/components/Menu/index.ts @@ -1 +1,3 @@ export * from './Menu'; +export {MenuItem} from './MenuItem'; +export {MenuGroup} from './MenuGroup'; diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx index 7d14950d1b..bd98cb7bcb 100644 --- a/src/components/Modal/Modal.tsx +++ b/src/components/Modal/Modal.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {CSSTransition} from 'react-transition-group'; diff --git a/src/components/Pagination/Pagination.tsx b/src/components/Pagination/Pagination.tsx index 929e43ce1f..c730a8ed1f 100644 --- a/src/components/Pagination/Pagination.tsx +++ b/src/components/Pagination/Pagination.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useMobile} from '../mobile'; diff --git a/src/components/Pagination/components/PaginationButton/PaginationButton.tsx b/src/components/Pagination/components/PaginationButton/PaginationButton.tsx index 537829a183..df1e1a3a2a 100644 --- a/src/components/Pagination/components/PaginationButton/PaginationButton.tsx +++ b/src/components/Pagination/components/PaginationButton/PaginationButton.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {ChevronLeft, ChevronRight, ChevronsLeft} from '@gravity-ui/icons'; diff --git a/src/components/Pagination/components/PaginationInput/PaginationInput.tsx b/src/components/Pagination/components/PaginationInput/PaginationInput.tsx index 58a9abf11c..d611efbfd7 100644 --- a/src/components/Pagination/components/PaginationInput/PaginationInput.tsx +++ b/src/components/Pagination/components/PaginationInput/PaginationInput.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {KeyCode} from '../../../../constants'; diff --git a/src/components/Pagination/components/PaginationPage/PaginationPage.tsx b/src/components/Pagination/components/PaginationPage/PaginationPage.tsx index e55fc8fe8a..057dba0f4c 100644 --- a/src/components/Pagination/components/PaginationPage/PaginationPage.tsx +++ b/src/components/Pagination/components/PaginationPage/PaginationPage.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {Button} from '../../../Button'; diff --git a/src/components/Pagination/components/PaginationPageSizer/PaginationPageSizer.tsx b/src/components/Pagination/components/PaginationPageSizer/PaginationPageSizer.tsx index e0033a7406..8c9a7509e7 100644 --- a/src/components/Pagination/components/PaginationPageSizer/PaginationPageSizer.tsx +++ b/src/components/Pagination/components/PaginationPageSizer/PaginationPageSizer.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {Select} from '../../../Select'; diff --git a/src/components/Palette/Palette.tsx b/src/components/Palette/Palette.tsx index 22908e9906..7c55673c08 100644 --- a/src/components/Palette/Palette.tsx +++ b/src/components/Palette/Palette.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useSelect} from '../../hooks'; diff --git a/src/components/Popover/Popover.tsx b/src/components/Popover/Popover.tsx index 1d844c9ea7..8461e5e836 100644 --- a/src/components/Popover/Popover.tsx +++ b/src/components/Popover/Popover.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {Xmark} from '@gravity-ui/icons'; diff --git a/src/components/Popover/components/Trigger/Trigger.tsx b/src/components/Popover/components/Trigger/Trigger.tsx index aa38c79265..dce4d5f430 100644 --- a/src/components/Popover/components/Trigger/Trigger.tsx +++ b/src/components/Popover/components/Trigger/Trigger.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useActionHandlers} from '../../../../hooks'; diff --git a/src/components/Popup/Popup.tsx b/src/components/Popup/Popup.tsx index b25b2e03cb..15e48f3b2d 100644 --- a/src/components/Popup/Popup.tsx +++ b/src/components/Popup/Popup.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {CSSTransition} from 'react-transition-group'; diff --git a/src/components/Portal/Portal.tsx b/src/components/Portal/Portal.tsx index 28c3bcb8be..dee2817682 100644 --- a/src/components/Portal/Portal.tsx +++ b/src/components/Portal/Portal.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import ReactDOM from 'react-dom'; diff --git a/src/components/Radio/Radio.tsx b/src/components/Radio/Radio.tsx index aa1b1d423a..5604a586d1 100644 --- a/src/components/Radio/Radio.tsx +++ b/src/components/Radio/Radio.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useRadio} from '../../hooks/private'; diff --git a/src/components/Radio/__stories__/RadioShowcase.tsx b/src/components/Radio/__stories__/RadioShowcase.tsx index 84d2e4734e..c5ee53d4c1 100644 --- a/src/components/Radio/__stories__/RadioShowcase.tsx +++ b/src/components/Radio/__stories__/RadioShowcase.tsx @@ -8,19 +8,19 @@ export function RadioShowcase() { return ( -

+

-

-

+

+
-

+
-

+

-

-

+

+
-

+
-

+

-

-

+

+
-

+
-

+

-

-

+

+
-

+
); diff --git a/src/components/RadioButton/RadioButton.tsx b/src/components/RadioButton/RadioButton.tsx index 9758560d0a..c236c8b5d9 100644 --- a/src/components/RadioButton/RadioButton.tsx +++ b/src/components/RadioButton/RadioButton.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useRadioGroup} from '../../hooks/private'; @@ -84,10 +86,9 @@ export const RadioButton = React.forwardRef(function RadioButton = - React.useCallback((event) => { - event.currentTarget.hidden = true; - }, []); + const handlePlateTransitionEnd: React.TransitionEventHandler = (event) => { + event.currentTarget.hidden = true; + }; const {containerProps, optionsProps} = useRadioGroup({...props, options}); diff --git a/src/components/RadioButton/RadioButtonOption.tsx b/src/components/RadioButton/RadioButtonOption.tsx index 5d69c28832..8c5915c8e2 100644 --- a/src/components/RadioButton/RadioButtonOption.tsx +++ b/src/components/RadioButton/RadioButtonOption.tsx @@ -1,9 +1,11 @@ +'use client'; + import React from 'react'; import {useRadio} from '../../hooks/private'; import type {ControlProps} from '../types'; import {block} from '../utils/cn'; -import {isIcon} from '../utils/common'; +import {isIcon, isSvg} from '../utils/common'; const b = block('radio-button'); @@ -25,7 +27,7 @@ export const RadioButtonOption = React.forwardRef(function RadioButtonOption
-

+

-

-

+

+
-

-

+

+
-

+
diff --git a/src/components/RadioButton/index.ts b/src/components/RadioButton/index.ts index 35533913ec..d813dc5132 100644 --- a/src/components/RadioButton/index.ts +++ b/src/components/RadioButton/index.ts @@ -1 +1,3 @@ export * from './RadioButton'; +// FIXME rename types RadioButtonOption to RadioButtonOptionProps +export {RadioButtonOption as RadioButtonItem} from './RadioButtonOption'; diff --git a/src/components/RadioGroup/RadioGroup.tsx b/src/components/RadioGroup/RadioGroup.tsx index 085c140c0e..2024862df2 100644 --- a/src/components/RadioGroup/RadioGroup.tsx +++ b/src/components/RadioGroup/RadioGroup.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useRadioGroup} from '../../hooks/private'; diff --git a/src/components/Select/Select.tsx b/src/components/Select/Select.tsx index a495e4cbb3..1b86f938c5 100644 --- a/src/components/Select/Select.tsx +++ b/src/components/Select/Select.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {KeyCode} from '../../constants'; diff --git a/src/components/Select/components/SelectControl/SelectControl.tsx b/src/components/Select/components/SelectControl/SelectControl.tsx index 873937dbc2..c54f72a5bf 100644 --- a/src/components/Select/components/SelectControl/SelectControl.tsx +++ b/src/components/Select/components/SelectControl/SelectControl.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {ChevronDown, TriangleExclamation} from '@gravity-ui/icons'; diff --git a/src/components/Select/components/SelectCounter/SelectCounter.tsx b/src/components/Select/components/SelectCounter/SelectCounter.tsx index aa9ccfbe17..4e1932b62e 100644 --- a/src/components/Select/components/SelectCounter/SelectCounter.tsx +++ b/src/components/Select/components/SelectCounter/SelectCounter.tsx @@ -8,12 +8,9 @@ import './SelectCounter.scss'; const b = block('select-counter'); -export const SelectCounter = React.forwardRef(function SelectCouner( - {count, size, disabled}: SelectCounterProps, - ref: React.ForwardedRef, -) { +export function SelectCounter({count, size, disabled}: SelectCounterProps) { return ( -
+
); -}); +} diff --git a/src/components/Select/components/SelectFilter/SelectFilter.tsx b/src/components/Select/components/SelectFilter/SelectFilter.tsx index 6923ce0b77..d7739d2ec3 100644 --- a/src/components/Select/components/SelectFilter/SelectFilter.tsx +++ b/src/components/Select/components/SelectFilter/SelectFilter.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {TextInput} from '../../../controls'; diff --git a/src/components/Select/components/SelectList/SelectList.tsx b/src/components/Select/components/SelectList/SelectList.tsx index 51ecf4c691..b98dfc8108 100644 --- a/src/components/Select/components/SelectList/SelectList.tsx +++ b/src/components/Select/components/SelectList/SelectList.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {List} from '../../../List'; diff --git a/src/components/Select/components/SelectList/SelectLoadingIndicator.tsx b/src/components/Select/components/SelectList/SelectLoadingIndicator.tsx index 860a72ac04..e23fedddd6 100644 --- a/src/components/Select/components/SelectList/SelectLoadingIndicator.tsx +++ b/src/components/Select/components/SelectList/SelectLoadingIndicator.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useIntersection} from '../../../../hooks'; diff --git a/src/components/Select/components/SelectPopup/SelectPopup.tsx b/src/components/Select/components/SelectPopup/SelectPopup.tsx index 1997c7c31c..c66ef12b76 100644 --- a/src/components/Select/components/SelectPopup/SelectPopup.tsx +++ b/src/components/Select/components/SelectPopup/SelectPopup.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import type {PopperPlacement} from '../../../../hooks/private'; diff --git a/src/components/Select/index.ts b/src/components/Select/index.ts index 59757f3f25..2bed911ccf 100644 --- a/src/components/Select/index.ts +++ b/src/components/Select/index.ts @@ -3,3 +3,5 @@ export * from './types'; export {SelectQa} from './constants'; export * from './hooks-public'; export {isSelectGroupTitle} from './utils'; +// FIXME: rename types SelectOption and SelectOptionGroup to SelectOptionProps and SelectOptionGroupProps respectively +export {Option as SelectItem, OptionGroup as SelectItemGroup} from './tech-components'; diff --git a/src/components/Select/tech-components.tsx b/src/components/Select/tech-components.tsx index 9a271f34b1..1ac43fb3e3 100644 --- a/src/components/Select/tech-components.tsx +++ b/src/components/Select/tech-components.tsx @@ -1,3 +1,5 @@ +'use client'; + import type {SelectOption, SelectOptionGroup} from './types'; export const Option = ( diff --git a/src/components/Sheet/Sheet.tsx b/src/components/Sheet/Sheet.tsx index e2ecad0d93..3cf14bd329 100644 --- a/src/components/Sheet/Sheet.tsx +++ b/src/components/Sheet/Sheet.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useBodyScrollLock} from '../../hooks'; diff --git a/src/components/Sheet/SheetContent.tsx b/src/components/Sheet/SheetContent.tsx index d4241e3ee0..a8ed9e2169 100644 --- a/src/components/Sheet/SheetContent.tsx +++ b/src/components/Sheet/SheetContent.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {Platform, withMobile} from '../mobile'; diff --git a/src/components/Slider/BaseSlider/BaseSlider.tsx b/src/components/Slider/BaseSlider/BaseSlider.tsx index 56f26775d4..ef18676dd2 100644 --- a/src/components/Slider/BaseSlider/BaseSlider.tsx +++ b/src/components/Slider/BaseSlider/BaseSlider.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import Slider from 'rc-slider'; diff --git a/src/components/Slider/Slider.tsx b/src/components/Slider/Slider.tsx index 15311bdd46..e41b7a3188 100644 --- a/src/components/Slider/Slider.tsx +++ b/src/components/Slider/Slider.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import debounce from 'lodash/debounce'; diff --git a/src/components/Slider/SliderTooltip/SliderTooltip.tsx b/src/components/Slider/SliderTooltip/SliderTooltip.tsx index 33b7167c2f..d6e5324f3a 100644 --- a/src/components/Slider/SliderTooltip/SliderTooltip.tsx +++ b/src/components/Slider/SliderTooltip/SliderTooltip.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {block} from '../../utils/cn'; diff --git a/src/components/Switch/Switch.tsx b/src/components/Switch/Switch.tsx index ea5db7d70c..020070d873 100644 --- a/src/components/Switch/Switch.tsx +++ b/src/components/Switch/Switch.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useCheckbox} from '../../hooks/private'; diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index d6b109f5b9..b5eb92cd51 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import _get from 'lodash/get'; diff --git a/src/components/Table/hoc/withTableActions/withTableActions.tsx b/src/components/Table/hoc/withTableActions/withTableActions.tsx index 50e342e056..c73191e153 100644 --- a/src/components/Table/hoc/withTableActions/withTableActions.tsx +++ b/src/components/Table/hoc/withTableActions/withTableActions.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {Ellipsis} from '@gravity-ui/icons'; diff --git a/src/components/Table/hoc/withTableCopy/withTableCopy.tsx b/src/components/Table/hoc/withTableCopy/withTableCopy.tsx index b9efad1f2e..e13e788752 100644 --- a/src/components/Table/hoc/withTableCopy/withTableCopy.tsx +++ b/src/components/Table/hoc/withTableCopy/withTableCopy.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import _memoize from 'lodash/memoize'; diff --git a/src/components/Table/hoc/withTableSelection/withTableSelection.tsx b/src/components/Table/hoc/withTableSelection/withTableSelection.tsx index 497de15a6f..28ec55b982 100644 --- a/src/components/Table/hoc/withTableSelection/withTableSelection.tsx +++ b/src/components/Table/hoc/withTableSelection/withTableSelection.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import _difference from 'lodash/difference'; diff --git a/src/components/Table/hoc/withTableSettings/TableColumnSetup/TableColumnSetup.tsx b/src/components/Table/hoc/withTableSettings/TableColumnSetup/TableColumnSetup.tsx index 4f01d1ffa2..b037a7c9ba 100644 --- a/src/components/Table/hoc/withTableSettings/TableColumnSetup/TableColumnSetup.tsx +++ b/src/components/Table/hoc/withTableSettings/TableColumnSetup/TableColumnSetup.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {Gear, Grip, Lock} from '@gravity-ui/icons'; diff --git a/src/components/Table/hoc/withTableSettings/withTableSettings.tsx b/src/components/Table/hoc/withTableSettings/withTableSettings.tsx index 68871c0adf..1882e849ad 100644 --- a/src/components/Table/hoc/withTableSettings/withTableSettings.tsx +++ b/src/components/Table/hoc/withTableSettings/withTableSettings.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {Gear} from '@gravity-ui/icons'; diff --git a/src/components/Table/hoc/withTableSorting/withTableSorting.tsx b/src/components/Table/hoc/withTableSorting/withTableSorting.tsx index 86aa721713..a36e23c6d4 100644 --- a/src/components/Table/hoc/withTableSorting/withTableSorting.tsx +++ b/src/components/Table/hoc/withTableSorting/withTableSorting.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import _get from 'lodash/get'; diff --git a/src/components/TableColumnSetup/TableColumnSetup.tsx b/src/components/TableColumnSetup/TableColumnSetup.tsx index e7ba572eda..f233343651 100644 --- a/src/components/TableColumnSetup/TableColumnSetup.tsx +++ b/src/components/TableColumnSetup/TableColumnSetup.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {Gear} from '@gravity-ui/icons'; diff --git a/src/components/Tabs/Tabs.tsx b/src/components/Tabs/Tabs.tsx index d433ebc989..e0f1fd907a 100644 --- a/src/components/Tabs/Tabs.tsx +++ b/src/components/Tabs/Tabs.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import type {QAProps} from '../types'; diff --git a/src/components/Tabs/TabsItem.tsx b/src/components/Tabs/TabsItem.tsx index 2267196cca..909bebc5fe 100644 --- a/src/components/Tabs/TabsItem.tsx +++ b/src/components/Tabs/TabsItem.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {Label} from '../Label'; diff --git a/src/components/Toaster/Provider/ToasterProvider.tsx b/src/components/Toaster/Provider/ToasterProvider.tsx index 9cd3bb36f2..f6aaf3629e 100644 --- a/src/components/Toaster/Provider/ToasterProvider.tsx +++ b/src/components/Toaster/Provider/ToasterProvider.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import type {InternalToastProps, ToastProps, ToasterPublicMethods} from '../types'; diff --git a/src/components/Toaster/Toast/Toast.tsx b/src/components/Toaster/Toast/Toast.tsx index d678c70f9c..c385392653 100644 --- a/src/components/Toaster/Toast/Toast.tsx +++ b/src/components/Toaster/Toast/Toast.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {CircleCheck, CircleInfo, Thunderbolt, TriangleExclamation, Xmark} from '@gravity-ui/icons'; diff --git a/src/components/Toaster/ToastList/ToastList.tsx b/src/components/Toaster/ToastList/ToastList.tsx index daa00508d3..7d9ed4dd67 100644 --- a/src/components/Toaster/ToastList/ToastList.tsx +++ b/src/components/Toaster/ToastList/ToastList.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {CSSTransition, TransitionGroup} from 'react-transition-group'; diff --git a/src/components/Toaster/ToasterComponent/ToasterComponent.tsx b/src/components/Toaster/ToasterComponent/ToasterComponent.tsx index bcc62c2e36..ca3e801cb3 100644 --- a/src/components/Toaster/ToasterComponent/ToasterComponent.tsx +++ b/src/components/Toaster/ToasterComponent/ToasterComponent.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useMobile} from '../../mobile'; diff --git a/src/components/Toaster/ToasterComponent/ToasterPortal.tsx b/src/components/Toaster/ToasterComponent/ToasterPortal.tsx index d8bb8f14a8..ab4355abd5 100644 --- a/src/components/Toaster/ToasterComponent/ToasterPortal.tsx +++ b/src/components/Toaster/ToasterComponent/ToasterPortal.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {Portal} from '../../Portal'; diff --git a/src/components/Toaster/ToasterSingleton.tsx b/src/components/Toaster/ToasterSingleton.tsx index 06df2dc594..15096e70c5 100644 --- a/src/components/Toaster/ToasterSingleton.tsx +++ b/src/components/Toaster/ToasterSingleton.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import get from 'lodash/get'; diff --git a/src/components/Toc/TocItem/TocItem.tsx b/src/components/Toc/TocItem/TocItem.tsx index e026b38947..d67c0cf3df 100644 --- a/src/components/Toc/TocItem/TocItem.tsx +++ b/src/components/Toc/TocItem/TocItem.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useActionHandlers} from '../../../hooks'; diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index 4ca99e4cd8..45b9a9b487 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useForkRef} from '../../hooks'; diff --git a/src/components/TreeList/TreeList.tsx b/src/components/TreeList/TreeList.tsx index 98fae11779..f3a2248faa 100644 --- a/src/components/TreeList/TreeList.tsx +++ b/src/components/TreeList/TreeList.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useUniqId} from '../../hooks'; diff --git a/src/components/TreeSelect/TreeSelect.tsx b/src/components/TreeSelect/TreeSelect.tsx index 9a9f0d4cfe..aa1d2a59bd 100644 --- a/src/components/TreeSelect/TreeSelect.tsx +++ b/src/components/TreeSelect/TreeSelect.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useForkRef, useUniqId} from '../../hooks'; diff --git a/src/components/controls/TextArea/TextArea.tsx b/src/components/controls/TextArea/TextArea.tsx index cafcde68da..36151ab2d4 100644 --- a/src/components/controls/TextArea/TextArea.tsx +++ b/src/components/controls/TextArea/TextArea.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useControlledState, useForkRef, useUniqId} from '../../../hooks'; diff --git a/src/components/controls/TextArea/TextAreaControl.tsx b/src/components/controls/TextArea/TextAreaControl.tsx index b8ea429c8a..ad9bbfce6d 100644 --- a/src/components/controls/TextArea/TextAreaControl.tsx +++ b/src/components/controls/TextArea/TextAreaControl.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {useForkRef} from '../../../hooks'; diff --git a/src/components/controls/TextInput/TextInput.tsx b/src/components/controls/TextInput/TextInput.tsx index 6a44f74c36..4e77b6f48b 100644 --- a/src/components/controls/TextInput/TextInput.tsx +++ b/src/components/controls/TextInput/TextInput.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {TriangleExclamation} from '@gravity-ui/icons'; diff --git a/src/components/controls/common/ClearButton/ClearButton.tsx b/src/components/controls/common/ClearButton/ClearButton.tsx index e435114b1c..407ad70379 100644 --- a/src/components/controls/common/ClearButton/ClearButton.tsx +++ b/src/components/controls/common/ClearButton/ClearButton.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {Xmark} from '@gravity-ui/icons'; diff --git a/src/components/layout/Col/Col.tsx b/src/components/layout/Col/Col.tsx index 863f7ef9de..fbd38ec433 100644 --- a/src/components/layout/Col/Col.tsx +++ b/src/components/layout/Col/Col.tsx @@ -1,3 +1,5 @@ +'use client'; + /* eslint-disable valid-jsdoc */ import React from 'react'; diff --git a/src/components/layout/Container/Container.tsx b/src/components/layout/Container/Container.tsx index 957e0803f0..03eacffdba 100644 --- a/src/components/layout/Container/Container.tsx +++ b/src/components/layout/Container/Container.tsx @@ -1,3 +1,5 @@ +'use client'; + /* eslint-disable valid-jsdoc */ import React from 'react'; diff --git a/src/components/layout/Flex/Flex.tsx b/src/components/layout/Flex/Flex.tsx index 3a93a16493..98ac86419a 100644 --- a/src/components/layout/Flex/Flex.tsx +++ b/src/components/layout/Flex/Flex.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {block} from '../../utils/cn'; diff --git a/src/components/layout/LayoutProvider/LayoutProvider.tsx b/src/components/layout/LayoutProvider/LayoutProvider.tsx index 33addad11e..f23d666289 100644 --- a/src/components/layout/LayoutProvider/LayoutProvider.tsx +++ b/src/components/layout/LayoutProvider/LayoutProvider.tsx @@ -1,3 +1,5 @@ +'use client'; + /* eslint-disable valid-jsdoc */ import React from 'react'; diff --git a/src/components/layout/Row/Row.tsx b/src/components/layout/Row/Row.tsx index 4ebac152f6..d2f3316250 100644 --- a/src/components/layout/Row/Row.tsx +++ b/src/components/layout/Row/Row.tsx @@ -1,3 +1,5 @@ +'use client'; + /* eslint-disable valid-jsdoc */ import React from 'react'; diff --git a/src/components/mobile/MobileProvider.tsx b/src/components/mobile/MobileProvider.tsx index 42927415e3..22a04bf8c9 100644 --- a/src/components/mobile/MobileProvider.tsx +++ b/src/components/mobile/MobileProvider.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {MobileContext} from './MobileContext'; diff --git a/src/components/theme/ThemeProvider.tsx b/src/components/theme/ThemeProvider.tsx index c0690affe0..d466d50b9a 100644 --- a/src/components/theme/ThemeProvider.tsx +++ b/src/components/theme/ThemeProvider.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {PrivateLayoutProvider} from '../layout/LayoutProvider/LayoutProvider'; diff --git a/src/components/utils/FocusTrap.tsx b/src/components/utils/FocusTrap.tsx index e4b177825f..97f032a848 100644 --- a/src/components/utils/FocusTrap.tsx +++ b/src/components/utils/FocusTrap.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {createFocusTrap} from 'focus-trap'; diff --git a/src/components/utils/common.ts b/src/components/utils/common.ts index 0a394d0ddc..4e52a287c1 100644 --- a/src/components/utils/common.ts +++ b/src/components/utils/common.ts @@ -9,4 +9,5 @@ export function getUniqId() { return `${NAMESPACE}uniq-${nextUniqueId++}`; } +export const isSvg = isOfType('svg'); export const isIcon = isOfType(Icon); diff --git a/src/components/utils/event-broker/useEventBroker.ts b/src/components/utils/event-broker/useEventBroker.ts index b8efa8410b..5ba3a5f0de 100644 --- a/src/components/utils/event-broker/useEventBroker.ts +++ b/src/components/utils/event-broker/useEventBroker.ts @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {eventBroker} from './EventBroker'; diff --git a/src/components/utils/layer-manager/LayerManager.ts b/src/components/utils/layer-manager/LayerManager.ts index 09155a05dd..6aa483855f 100644 --- a/src/components/utils/layer-manager/LayerManager.ts +++ b/src/components/utils/layer-manager/LayerManager.ts @@ -1,3 +1,5 @@ +'use client'; + import type {VirtualElement} from '@popperjs/core'; import {KeyCode} from '../../../constants'; diff --git a/src/components/utils/withEventBrokerDomHandlers.tsx b/src/components/utils/withEventBrokerDomHandlers.tsx index 1b6b35c2bd..b14ee40b07 100644 --- a/src/components/utils/withEventBrokerDomHandlers.tsx +++ b/src/components/utils/withEventBrokerDomHandlers.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import type {EventBrokerData} from './event-broker'; diff --git a/src/hooks/usePortalContainer/PortalProvider.tsx b/src/hooks/usePortalContainer/PortalProvider.tsx index edf69b01bb..e10f853157 100644 --- a/src/hooks/usePortalContainer/PortalProvider.tsx +++ b/src/hooks/usePortalContainer/PortalProvider.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; export const PortalContext = React.createContext>({current: null}); From 5cdc6753d4461b8e531870bcd9f4724dc6782d80 Mon Sep 17 00:00:00 2001 From: Valerii Sidorenko Date: Thu, 13 Jun 2024 14:04:22 +0200 Subject: [PATCH 3/3] feat(Breadcrumbs): new component (#1497) --- .../lab/Breadcrumbs/BreadcrumbItem.tsx | 96 ++++ .../lab/Breadcrumbs/Breadcrumbs.scss | 110 ++++ .../lab/Breadcrumbs/Breadcrumbs.tsx | 272 ++++++++++ .../lab/Breadcrumbs/BreadcrumbsSeparator.tsx | 16 + src/components/lab/Breadcrumbs/README.md | 501 ++++++++++++++++++ .../__stories__/Breadcrumbs.stories.tsx | 138 +++++ .../lab/Breadcrumbs/__stories__/Docs.mdx | 39 ++ .../__tests__/Breadcrumbs.test.tsx | 284 ++++++++++ src/components/lab/Breadcrumbs/i18n/en.json | 4 + src/components/lab/Breadcrumbs/i18n/index.ts | 8 + src/components/lab/Breadcrumbs/i18n/ru.json | 4 + src/components/lab/Breadcrumbs/index.ts | 1 + src/components/lab/Breadcrumbs/utils.ts | 24 + src/components/types.ts | 29 + src/components/utils/filterDOMProps.ts | 46 ++ src/hooks/index.ts | 1 + src/hooks/useResizeObserver/README.md | 18 + src/hooks/useResizeObserver/index.ts | 1 + .../useResizeObserver/useResizeObserver.ts | 34 ++ src/unstable.ts | 9 + 20 files changed, 1635 insertions(+) create mode 100644 src/components/lab/Breadcrumbs/BreadcrumbItem.tsx create mode 100644 src/components/lab/Breadcrumbs/Breadcrumbs.scss create mode 100644 src/components/lab/Breadcrumbs/Breadcrumbs.tsx create mode 100644 src/components/lab/Breadcrumbs/BreadcrumbsSeparator.tsx create mode 100644 src/components/lab/Breadcrumbs/README.md create mode 100644 src/components/lab/Breadcrumbs/__stories__/Breadcrumbs.stories.tsx create mode 100644 src/components/lab/Breadcrumbs/__stories__/Docs.mdx create mode 100644 src/components/lab/Breadcrumbs/__tests__/Breadcrumbs.test.tsx create mode 100644 src/components/lab/Breadcrumbs/i18n/en.json create mode 100644 src/components/lab/Breadcrumbs/i18n/index.ts create mode 100644 src/components/lab/Breadcrumbs/i18n/ru.json create mode 100644 src/components/lab/Breadcrumbs/index.ts create mode 100644 src/components/lab/Breadcrumbs/utils.ts create mode 100644 src/components/utils/filterDOMProps.ts create mode 100644 src/hooks/useResizeObserver/README.md create mode 100644 src/hooks/useResizeObserver/index.ts create mode 100644 src/hooks/useResizeObserver/useResizeObserver.ts diff --git a/src/components/lab/Breadcrumbs/BreadcrumbItem.tsx b/src/components/lab/Breadcrumbs/BreadcrumbItem.tsx new file mode 100644 index 0000000000..e7c32c5f24 --- /dev/null +++ b/src/components/lab/Breadcrumbs/BreadcrumbItem.tsx @@ -0,0 +1,96 @@ +'use client'; + +import React from 'react'; + +import type {Href, RouterOptions} from '../../types'; +import {filterDOMProps} from '../../utils/filterDOMProps'; + +import type {BreadcrumbsItemProps} from './Breadcrumbs'; +import {b, shouldClientNavigate} from './utils'; + +interface BreadcrumbProps extends BreadcrumbsItemProps { + onAction?: () => void; + current?: boolean; + itemType?: 'link' | 'menu'; + disabled?: boolean; + navigate?: (href: Href, routerOptions: RouterOptions | undefined) => void; +} +export function BreadcrumbItem(props: BreadcrumbProps) { + const Element = props.href ? 'a' : 'span'; + const domProps = filterDOMProps(props, {labelable: true}); + + let title = props.title; + if (!title && typeof props.children === 'string') { + title = props.children; + } + + const handleAction = (event: React.MouseEvent | React.KeyboardEvent) => { + if (props.disabled || props.current) { + event.preventDefault(); + return; + } + + if (typeof props.onAction === 'function') { + props.onAction(); + } + + const target = event.currentTarget; + if (typeof props.navigate === 'function' && target instanceof HTMLAnchorElement) { + if (props.href && !event.isDefaultPrevented() && shouldClientNavigate(target, event)) { + event.preventDefault(); + props.navigate(props.href, props.routerOptions); + } + } + }; + + const isDisabled = props.disabled || props.current; + let linkProps: React.AnchorHTMLAttributes = { + title, + onClick: handleAction, + 'aria-disabled': isDisabled ? true : undefined, + }; + if (Element === 'a') { + linkProps.href = props.href; + linkProps.hrefLang = props.hrefLang; + linkProps.target = props.target; + linkProps.rel = props.target === '_blank' && !props.rel ? 'noopener noreferrer' : props.rel; + linkProps.download = props.download; + linkProps.ping = props.ping; + linkProps.referrerPolicy = props.referrerPolicy; + } else { + linkProps.role = 'link'; + linkProps.tabIndex = isDisabled ? undefined : 0; + linkProps.onKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + handleAction(event); + } + }; + } + + if (props.current) { + linkProps['aria-current'] = 'page'; + } + + if (props.itemType === 'menu') { + linkProps = {}; + } + + return ( + + {props.children} + + ); +} + +BreadcrumbItem.displayName = 'Breadcrumbs.Item'; diff --git a/src/components/lab/Breadcrumbs/Breadcrumbs.scss b/src/components/lab/Breadcrumbs/Breadcrumbs.scss new file mode 100644 index 0000000000..11b32f5ed0 --- /dev/null +++ b/src/components/lab/Breadcrumbs/Breadcrumbs.scss @@ -0,0 +1,110 @@ +@use '../../variables'; +@use '../../../../styles/mixins'; + +$block: '.#{variables.$ns}breadcrumbs2'; + +#{$block} { + display: flex; + flex-wrap: nowrap; + justify-content: flex-start; + + list-style-type: none; + margin: 0; + padding: 0; + + &__item { + position: relative; + display: flex; + align-items: center; + justify-content: flex-start; + + height: 24px; + white-space: nowrap; + color: var(--g-color-text-primary); + + &:last-child { + font-weight: var(--g-text-accent-font-weight); + overflow: hidden; + + #{$block}__link { + @include mixins.overflow-ellipsis(); + } + } + + &_calculating:last-child { + overflow: visible; + } + } + + &__link { + cursor: default; + position: relative; + text-decoration: none; + outline: none; + touch-action: manipulation; + -webkit-tap-highlight-color: transparent; + + height: 24px; + line-height: 24px; + vertical-align: middle; + border-radius: var(--g-focus-border-radius); + + color: inherit; + + &_is-disabled { + color: var(--g-color-text-hint); + } + + &:not([aria-disabled]) { + cursor: pointer; + + &:hover { + color: var(--g-color-text-link-hover); + } + } + + &:focus-visible { + outline: 2px solid var(--g-color-line-focus); + } + } + + &__divider { + display: flex; + align-items: center; + color: var(--g-color-text-secondary); + padding: 0 var(--g-spacing-2); + } + + &__more-button { + --g-button-border-radius: var(--g-focus-border-radius); + --g-button-focus-outline-offset: -2px; + } + + &__menu { + margin-inline: calc(-1 * var(--g-spacing-2)); + } + + &__item:first-child &__menu { + margin-inline-start: 0; + } + + &__popup_staircase { + $menu: '.#{variables.$ns}menu'; + $staircaseLength: 10; + #{$menu} { + #{$menu}__list-item { + #{$menu}__item[class] { + padding-inline-start: 8px * $staircaseLength; + } + } + + @for $i from 0 through $staircaseLength { + #{$menu}__list-item:nth-child(#{$i}) { + #{$menu}__item[class] { + padding-inline-start: 8px * $i; + } + } + } + } + } +} diff --git a/src/components/lab/Breadcrumbs/Breadcrumbs.tsx b/src/components/lab/Breadcrumbs/Breadcrumbs.tsx new file mode 100644 index 0000000000..cc062d957c --- /dev/null +++ b/src/components/lab/Breadcrumbs/Breadcrumbs.tsx @@ -0,0 +1,272 @@ +'use client'; + +import React from 'react'; + +import {useForkRef, useResizeObserver} from '../../../hooks'; +import {Button} from '../../Button'; +import {DropdownMenu} from '../../DropdownMenu'; +import type {PopupPlacement} from '../../Popup'; +import type {AriaLabelingProps, DOMProps, Href, Key, QAProps, RouterOptions} from '../../types'; +import {filterDOMProps} from '../../utils/filterDOMProps'; + +import {BreadcrumbItem} from './BreadcrumbItem'; +import {BreadcrumbsSeparator} from './BreadcrumbsSeparator'; +import i18n from './i18n'; +import {b, shouldClientNavigate} from './utils'; + +import './Breadcrumbs.scss'; + +export interface BreadcrumbsItemProps { + children: React.ReactNode; + title?: string; + href?: Href; + hrefLang?: string; + target?: React.HTMLAttributeAnchorTarget; + rel?: string; + download?: boolean | string; + ping?: string; + referrerPolicy?: React.HTMLAttributeReferrerPolicy; + 'aria-label'?: string; + routerOptions?: RouterOptions; +} + +function Item(_props: BreadcrumbsItemProps): React.ReactNode { + return null; +} + +export interface BreadcrumbsProps extends DOMProps, AriaLabelingProps, QAProps { + id?: string; + showRoot?: boolean; + separator?: React.ReactNode; + maxItems?: number; + popupStyle?: 'staircase'; + popupPlacement?: PopupPlacement; + children: React.ReactElement | React.ReactElement[]; + navigate?: (href: Href, routerOptions: RouterOptions | undefined) => void; + disabled?: boolean; + onAction?: (key: Key) => void; +} + +export const Breadcrumbs = React.forwardRef(function Breadcrumbs( + props: BreadcrumbsProps, + ref: React.Ref, +) { + const listRef = React.useRef(null); + const containerRef = useForkRef(ref, listRef); + + const items: React.ReactElement[] = []; + React.Children.forEach(props.children, (child, index) => { + if (React.isValidElement(child)) { + if (child.key === undefined || child.key === null) { + child = React.cloneElement(child, {key: index}); + } + items.push(child); + } + }); + + const [visibleItemsCount, setVisibleItemsCount] = React.useState(items.length); + const [calculated, setCalculated] = React.useState(false); + const recalculate = (visibleItems: number) => { + const list = listRef.current; + if (!list) { + return; + } + const listItems = Array.from(list.children) as HTMLElement[]; + if (listItems.length === 0) { + return; + } + const containerWidth = list.offsetWidth; + let newVisibleItemsCount = 0; + let calculatedWidth = 0; + let maxItems = props.maxItems || Infinity; + + let rootWidth = 0; + if (props.showRoot) { + const item = listItems.shift(); + if (item) { + rootWidth = item.scrollWidth; + calculatedWidth += rootWidth; + } + newVisibleItemsCount++; + } + + const hasMenu = items.length > visibleItems; + if (hasMenu) { + const item = listItems.shift(); + if (item) { + calculatedWidth += item.offsetWidth; + } + maxItems--; + } + + if (props.showRoot && calculatedWidth >= containerWidth) { + calculatedWidth -= rootWidth; + newVisibleItemsCount--; + } + + const lastItem = listItems.pop(); + if (lastItem) { + calculatedWidth += Math.min(lastItem.offsetWidth, 200); + if (calculatedWidth < containerWidth) { + newVisibleItemsCount++; + } + } + + for (let i = listItems.length - 1; i >= 0; i--) { + const item = listItems[i]; + calculatedWidth += item.offsetWidth; + if (calculatedWidth >= containerWidth) { + break; + } + newVisibleItemsCount++; + } + + newVisibleItemsCount = Math.max(Math.min(maxItems, newVisibleItemsCount), 1); + if (newVisibleItemsCount === visibleItemsCount) { + setCalculated(true); + } else { + setVisibleItemsCount(newVisibleItemsCount); + } + }; + + const handleResize = React.useCallback(() => { + setCalculated(false); + setVisibleItemsCount(items.length); + }, [items.length]); + useResizeObserver({ + ref: listRef, + onResize: handleResize, + }); + + const lastChildren = React.useRef(null); + React.useLayoutEffect(() => { + if (calculated && props.children !== lastChildren.current) { + lastChildren.current = props.children; + setCalculated(false); + setVisibleItemsCount(items.length); + } + }, [calculated, items.length, props.children]); + + React.useLayoutEffect(() => { + if (!calculated) { + recalculate(visibleItemsCount); + } + }); + + const {navigate} = props; + let contents = items; + if (items.length > visibleItemsCount) { + contents = []; + const breadcrumbs = [...items]; + let endItems = visibleItemsCount; + if (props.showRoot && visibleItemsCount > 1) { + const rootItem = breadcrumbs.shift(); + if (rootItem) { + contents.push(rootItem); + } + endItems--; + } + const hiddenItems = breadcrumbs.slice(0, -endItems); + const menuItem = ( + + { + return { + ...el.props, + text: el.props.children, + disabled: props.disabled, + items: [], + action: (event) => { + if (typeof props.onAction === 'function') { + props.onAction(el.key ?? index); + } + + // TODO: move this logic to DropdownMenu + const target = event.currentTarget; + if ( + typeof navigate === 'function' && + target instanceof HTMLAnchorElement + ) { + if (el.props.href && shouldClientNavigate(target, event)) { + event.preventDefault(); + navigate(el.props.href, el.props.routerOptions); + } + } + }, + }; + })} + popupProps={{ + className: b('popup', { + staircase: props.popupStyle === 'staircase', + }), + placement: props.popupPlacement, + }} + renderSwitcher={({onClick}) => ( + + )} + /> + + ); + + contents.push(menuItem); + contents.push(...breadcrumbs.slice(-endItems)); + } + + const lastIndex = contents.length - 1; + const breadcrumbItems = contents.map((child, index) => { + const isCurrent = index === lastIndex; + const key = child.key ?? index; + const handleAction = () => { + if (typeof props.onAction === 'function') { + props.onAction(key); + } + }; + + return ( +
  • + + {child.props.children} + + {isCurrent ? null : } +
  • + ); + }); + return ( +
      + {breadcrumbItems} +
    + ); +}) as unknown as BreadcrumbsComponent; + +type BreadcrumbsComponent = React.FunctionComponent< + BreadcrumbsProps & {ref?: React.Ref} +> & { + Item: typeof Item; +}; + +Breadcrumbs.Item = Item; +Breadcrumbs.displayName = 'Breadcrumbs'; + +export {Item as BreadcrumbsItem}; diff --git a/src/components/lab/Breadcrumbs/BreadcrumbsSeparator.tsx b/src/components/lab/Breadcrumbs/BreadcrumbsSeparator.tsx new file mode 100644 index 0000000000..299f19fd4e --- /dev/null +++ b/src/components/lab/Breadcrumbs/BreadcrumbsSeparator.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import type {BreadcrumbsProps} from './Breadcrumbs'; +import {b} from './utils'; + +type Props = Pick; + +export function BreadcrumbsSeparator({separator}: Props) { + return ( +
    + {separator ?? '/'} +
    + ); +} + +BreadcrumbsSeparator.displayName = 'Breadcrumbs.Separator'; diff --git a/src/components/lab/Breadcrumbs/README.md b/src/components/lab/Breadcrumbs/README.md new file mode 100644 index 0000000000..b3e3683234 --- /dev/null +++ b/src/components/lab/Breadcrumbs/README.md @@ -0,0 +1,501 @@ + + +# Breadcrumbs + + + +```tsx +import {unstable_Breadcrumbs as Breadcrumbs} from '@gravity-ui/uikit/unstable'; +``` + +`Breadcrumbs` is a navigation element that shows the current location of a page within a website’s hierarchy. It provides links that allow users to return to higher levels in the hierarchy, making it easier to navigate a site with multiple layers. Breadcrumbs are especially useful for large websites and applications with a hierarchical organization of pages. + +## Example + + + + + +```jsx + + Region + Country + City + District + Street + +``` + + + + + + + +### Events + +Use the `onAction` prop as a callback to handle click events on items. + + + + + +```jsx +const [currentId, setCurrentId] = React.useState(); +const items = [ + {id: 1, label: 'Region'}, + {id: 2, label: 'Country'}, + {id: 3, label: 'City'}, + {id: 4, label: 'District'}, + {id: 5, label: 'Street'}, +] +
    + + {items.map((i) => {i.label})} + +

    You clicked item ID: {currentId}

    +
    +``` + + + + + + + +### Links + +In Breadcrumbs, clicking an item normally triggers `onAction`. But you can also make them links to other pages or websites. To do that, add the href property to the `` component. + + + + + +```jsx + + Home + Components + Breadcrumbs + +``` + + + + + + + +### Root context + +To help users understand the overall structure, some applications always show the starting point (root item) of the Breadcrumbs, even when other items are hidden due to space limitations. + + + + + +```jsx + + + Home + Trendy + March 2020 Assets + Winter + Holiday + + +``` + + + + + + + +### Separator + + + + + +```jsx + + {breadcrumbs} + + + {breadcrumbs} + +}> + {breadcrumbs} + +``` + + + + + + + +### Breadcrumbs with icons + + + + + +```jsx + + + + uikit + + + + + components + + + + + + + Breadcrumbs + + + + +``` + + + + + + + +### Integration with routers + +`Breadcrumbs` component accepts navigate function received from your router for performing a client side navigation programmatically. +The following example shows the general pattern. + +```jsx +function Header() { + const navigate = useNavigateFromYourRouter(); + + return ( +
    + {/*...*/} +
    + ); +} +``` + +#### React Router v5 + +```jsx +import {useHistory} from 'react-router-dom'; +import {Breadcrumbs} from '@gravity-ui/uikit'; + +function Header() { + const history = useHistory(); + + return ( +
    + {/*...*/} +
    + ); +} +``` + +#### React Router v6 + +```jsx +import {useNavigate} from 'react-router-dom'; +import {Breadcrumbs} from '@gravity-ui/uikit'; + +function Header() { + const navigate = useNavigate(); + + return ( +
    + {/*...*/} +
    + ); +} +``` + +#### Next.js + +`App router` + +```jsx +'use client'; + +import {useRouter} from 'next/navigation'; +import {Breadcrumbs} from '@gravity-ui/uikit'; + +function Header() { + const router = useRouter(); + + return ( +
    + {/*...*/} +
    + ); +} +``` + +`Pages router` + +```jsx +import {useRouter} from 'next/router'; +import {Breadcrumbs} from '@gravity-ui/uikit'; + +function Header() { + const router = useRouter(); + + return ( +
    + {/*...*/} +
    + ); +} +``` + +### Landmarks + +When breadcrumbs are used as a main navigation element for a page, they can be placed in a [navigation landmark](https://www.w3.org/WAI/ARIA/apg/patterns/landmarks/examples/navigation.html). Landmarks help assistive technology users quickly find major sections of a page. Place breadcrumbs inside a `