From ddfeefe568db0fa1b2ea8a0271fc98c684ec5dcb Mon Sep 17 00:00:00 2001 From: Alllex202 <56914444+Alllex202@users.noreply.github.com> Date: Fri, 3 May 2024 14:02:23 +0500 Subject: [PATCH] feat(ui): DOMA-8920 added IconButton (#4667) * feat(ui): DOMA-8920 added IconButton * feat(ui): DOMA-8920 added pressed style * refactor(ui): DOMA-8920 some refactor * fix(ui): DOMA-8920 try fix import * fix(ui): DOMA-8920 try fix import * fix(ui): DOMA-8920 try fix import * refactor(ui): DOMA-8920 moved IconButton to Button.Icon * fix(ui): DOMA-8920 removed unused files * feat(ui): DOMA-8920 remove prop focus * refactor(condo): DOMA-8920 refactored after review * refactor(ui): DOMA-8920 refactored after review. Removed "pressed" prop --- .../src/components/Button/icon/iconButton.tsx | 65 ++++++++++ .../ui/src/components/Button/icon/style.less | 111 ++++++++++++++++++ packages/ui/src/components/Button/index.ts | 14 ++- packages/ui/src/components/Button/style.less | 2 +- .../ui/src/components/_utils/analytics.ts | 1 + .../mixins.less => style/mixins/button.less} | 0 packages/ui/src/index.ts | 2 +- .../stories/{ => Button}/Button.stories.tsx | 0 .../src/stories/Button/IconButton.stories.tsx | 42 +++++++ 9 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 packages/ui/src/components/Button/icon/iconButton.tsx create mode 100644 packages/ui/src/components/Button/icon/style.less rename packages/ui/src/components/{Button/mixins.less => style/mixins/button.less} (100%) rename packages/ui/src/stories/{ => Button}/Button.stories.tsx (100%) create mode 100644 packages/ui/src/stories/Button/IconButton.stories.tsx diff --git a/packages/ui/src/components/Button/icon/iconButton.tsx b/packages/ui/src/components/Button/icon/iconButton.tsx new file mode 100644 index 00000000000..904c5e986c2 --- /dev/null +++ b/packages/ui/src/components/Button/icon/iconButton.tsx @@ -0,0 +1,65 @@ +import { + Button as DefaultButton, + ButtonProps as DefaultButtonProps, +} from 'antd' +import classNames from 'classnames' +import React, { useCallback } from 'react' + +import { sendAnalyticsClickEvent, extractChildrenContent } from '../../_utils/analytics' +import { BUTTON_CLASS_PREFIX } from '../button' + +const ICON_BUTTON_CLASS_PREFIX = 'condo-icon-btn' + +type CondoIconButtonProps = { + size?: 'small' | 'medium' +} + +export type IconButtonProps = Omit +& CondoIconButtonProps + +const IconButton: React.ForwardRefExoticComponent> = React.forwardRef((props, ref) => { + const { className, children, onClick, id, size, ...rest } = props + const mergedSize = size || 'medium' + const ariaLabel = rest['aria-label'] + const classes = classNames( + { + [BUTTON_CLASS_PREFIX]: true, + [`${ICON_BUTTON_CLASS_PREFIX}-${mergedSize}`]: mergedSize, + }, + className, + ) + + const wrappedContent = children + ? {children} + : null + + const handleClick = useCallback((event: React.MouseEvent & React.MouseEvent) => { + const stringContent = extractChildrenContent(children) || ariaLabel + if (stringContent || id) { + sendAnalyticsClickEvent('IconButton', { value: stringContent, id }) + } + + if (onClick) { + onClick(event) + } + }, [ariaLabel, children, id, onClick]) + + return ( + + ) +}) + +IconButton.displayName = 'IconButton' + +export { + IconButton, +} diff --git a/packages/ui/src/components/Button/icon/style.less b/packages/ui/src/components/Button/icon/style.less new file mode 100644 index 00000000000..f8c1bb433d5 --- /dev/null +++ b/packages/ui/src/components/Button/icon/style.less @@ -0,0 +1,111 @@ +@import (reference) "@open-condo/ui/src/tokens/variables.less"; +@import (reference) "@open-condo/ui/src/components/style/mixins/button"; +@import (reference) "@open-condo/ui/src/components/style/mixins/transition"; + +@condo-icon-button-focus-outline-width: @condo-global-border-width-default * 4; +@condo-icon-button-focus-visible-outline-width: @condo-global-border-width-default * 2; +@condo-icon-button-focus-outline-border-radius-small: @condo-global-border-radius-medium; +@condo-icon-button-focus-outline-border-radius-medium: @condo-global-border-radius-large; +@condo-icon-button-border-radius-small: @condo-global-border-radius-small; +@condo-icon-button-border-radius-medium: @condo-global-border-radius-medium; +@condo-icon-button-padding-small: 2px; +@condo-icon-button-padding-medium: 8px; + +.condo-icon-btn { + width: auto; + min-width: inherit; + height: auto; + padding: @condo-icon-button-padding-medium; + background-color: transparent; + border: none; + border-radius: @condo-icon-button-border-radius-medium; + .condo-transition(background); + + span { + position: relative; + z-index: 1; + } + + // After layer is used to show gradient outline on tab-focusing + &:focus-visible::after { + box-sizing: border-box; + background: @condo-global-color-brand-gradient-5 border-box; + border: @condo-icon-button-focus-visible-outline-width solid transparent; + opacity: 1; + mask: linear-gradient(#111 0 0) padding-box, linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; + } + + &::before { + .condo-btn-layer(0px); + + background: @condo-global-color-brand-gradient-1; + opacity: 0; + .condo-transition(opacity); + } + + .condo-icon-btn-content { + display: inline-flex; + color: @condo-global-color-black; + .condo-transition(color); + + svg { + color: @condo-global-color-black; + fill: currentcolor; + .condo-transition(color); + } + } + + &:focus::after { + .condo-btn-layer(-@condo-icon-button-focus-outline-width); + + border-radius: @condo-icon-button-focus-outline-border-radius-medium; + } + + &:disabled { + background-color: transparent; + + .condo-icon-btn-content { + opacity: @condo-global-opacity-disabled; + } + + &:hover { + background-color: transparent; + } + } + + &:not(:disabled):hover { + &::before { + opacity: 1; + } + + .condo-icon-btn-content { + color: @condo-global-color-green-5; + + svg { + color: @condo-global-color-green-5; + } + } + } + + &:not(:disabled):active, + &:not(:disabled):active:hover { + .condo-icon-btn-content { + color: @condo-global-color-green-7; + + svg { + color: @condo-global-color-green-7; + } + } + } + + &.condo-icon-btn-small { + padding: @condo-icon-button-padding-small; + border-radius: @condo-icon-button-border-radius-small; + + &:focus::after { + border-radius: @condo-icon-button-focus-outline-border-radius-small; + } + } +} diff --git a/packages/ui/src/components/Button/index.ts b/packages/ui/src/components/Button/index.ts index b39e2fdc224..1686f580b93 100644 --- a/packages/ui/src/components/Button/index.ts +++ b/packages/ui/src/components/Button/index.ts @@ -1,5 +1,17 @@ -import { Button } from './button' import './style.less' +import './icon/style.less' + +import { Button as InternalButton } from './button' +import { IconButton } from './icon/iconButton' export type { ButtonProps } from './button' +export type { IconButtonProps } from './icon/iconButton' + +type CombinedButtonType = typeof InternalButton & { + Icon: typeof IconButton +} + +const Button = InternalButton as CombinedButtonType +Button.Icon = IconButton + export { Button } diff --git a/packages/ui/src/components/Button/style.less b/packages/ui/src/components/Button/style.less index 7cd006d848a..6eb8a06601d 100644 --- a/packages/ui/src/components/Button/style.less +++ b/packages/ui/src/components/Button/style.less @@ -1,7 +1,7 @@ @import "antd/lib/button/style/index.less"; @import (reference) "@open-condo/ui/src/tokens/variables.less"; @import (reference) "@open-condo/ui/src/components/style/mixins/typography"; -@import "./mixins"; +@import (reference) "@open-condo/ui/src/components/style/mixins/button"; @condo-button-padding-vert: @condo-global-spacing-12; @condo-button-padding-hor: @condo-global-spacing-20; diff --git a/packages/ui/src/components/_utils/analytics.ts b/packages/ui/src/components/_utils/analytics.ts index 9ffcdb0ac8f..de19ba9c840 100644 --- a/packages/ui/src/components/_utils/analytics.ts +++ b/packages/ui/src/components/_utils/analytics.ts @@ -15,6 +15,7 @@ type CommonComponentProps = { type ComponentSpecificClickEventProps = { Banner: { title: string } Button: { value: string, type: string } + IconButton: { value?: string } 'Typography.Link': { value: string, href?: string } Dropdown: { optionValue: string, optionKey?: string, optionKeyPath?: Array, triggerValue?: string } Card: { title: string, accent?: boolean } diff --git a/packages/ui/src/components/Button/mixins.less b/packages/ui/src/components/style/mixins/button.less similarity index 100% rename from packages/ui/src/components/Button/mixins.less rename to packages/ui/src/components/style/mixins/button.less diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 43912f54c88..dce8fefb216 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -10,7 +10,7 @@ export { Banner } from './components/Banner' export type { BannerProps } from './components/Banner' export { Button } from './components/Button' -export type { ButtonProps } from './components/Button' +export type { ButtonProps, IconButtonProps } from './components/Button' export { Card } from './components/Card' export type { CardProps, CardCheckboxProps, CardButtonProps, CardHeaderProps, CardBodyProps } from './components/Card' diff --git a/packages/ui/src/stories/Button.stories.tsx b/packages/ui/src/stories/Button/Button.stories.tsx similarity index 100% rename from packages/ui/src/stories/Button.stories.tsx rename to packages/ui/src/stories/Button/Button.stories.tsx diff --git a/packages/ui/src/stories/Button/IconButton.stories.tsx b/packages/ui/src/stories/Button/IconButton.stories.tsx new file mode 100644 index 00000000000..f18137323c4 --- /dev/null +++ b/packages/ui/src/stories/Button/IconButton.stories.tsx @@ -0,0 +1,42 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react' +import React from 'react' + +import * as condoIcons from '@open-condo/icons' +import { Button as Component } from '@open-condo/ui/src' + +const { Trash } = condoIcons + +const icons = Object.assign({}, ...Object.entries(condoIcons).map(([key, Icon]) => ({ + [`${key}-small`]: , + [`${key}-medium`]: , + [`${key}-large`]: , +}))) + +export default { + title: 'Components/Button', + component: Component.Icon, + args: { + children: , + disabled: false, + size: 'medium', + }, + argTypes: { + size: { control: 'select', options: ['small', 'medium'] }, + children: { + options: Object.keys(icons), + mapping: icons, + control: { + type: 'select', + }, + }, + disabled: { control: 'boolean', default: false }, + onClick: { control: false }, + href: { control: false }, + target: { control: false }, + htmlType: { defaultValue: 'button' }, + }, +} as ComponentMeta + +const Template: ComponentStory = (props) => + +export const IconButton = Template.bind({})