From b79acf6f9914e7bbdadb34f5b7e104ed07a37963 Mon Sep 17 00:00:00 2001 From: Arina Gazhina Date: Wed, 17 Jun 2026 21:25:20 +0300 Subject: [PATCH 1/3] refactor(popover): appearance/shape contract --- ui-parts/popover/src/arrow/component.tsx | 3 +- ui-parts/popover/src/component.tsx | 58 ++++++++++++++----- ui-parts/popover/src/container/component.tsx | 21 +++++-- ui-parts/popover/src/container/interfaces.ts | 5 ++ ui-parts/popover/src/index.ts | 1 + ui-parts/popover/src/interfaces.ts | 12 ++++ ui-parts/popover/src/styles/appearance.css.ts | 21 +++++++ ui-parts/popover/src/styles/index.ts | 2 + ui-parts/popover/src/styles/shape.css.ts | 30 ++++++++++ 9 files changed, 135 insertions(+), 18 deletions(-) create mode 100644 ui-parts/popover/src/styles/appearance.css.ts create mode 100644 ui-parts/popover/src/styles/index.ts create mode 100644 ui-parts/popover/src/styles/shape.css.ts diff --git a/ui-parts/popover/src/arrow/component.tsx b/ui-parts/popover/src/arrow/component.tsx index 6113e14b..1a29f0dc 100644 --- a/ui-parts/popover/src/arrow/component.tsx +++ b/ui-parts/popover/src/arrow/component.tsx @@ -5,6 +5,7 @@ import type { ArrowProps } from './interfaces.js' import { FloatingArrow } from '@floating-ui/react' import { Condition } from '@atls-ui-parts/condition' +import { vars } from '@atls-ui-parts/theme' export const Arrow = ({ ref, context, arrow = true }: ArrowProps): ReactNode => ( @@ -12,7 +13,7 @@ export const Arrow = ({ ref, context, arrow = true }: ArrowProps): ReactNode => ref={ref} context={context} width={16} - fill='rgba(255, 255, 255, 1)' + fill={vars.colors.white} {...(typeof arrow === 'boolean' ? {} : arrow)} /> diff --git a/ui-parts/popover/src/component.tsx b/ui-parts/popover/src/component.tsx index b600fbb4..71a78c46 100644 --- a/ui-parts/popover/src/component.tsx +++ b/ui-parts/popover/src/component.tsx @@ -1,36 +1,68 @@ -import type { ReactNode } from 'react' +import type { ReactNode } from 'react' -import type { PopoverProps } from './interfaces.js' +import type { PopoverProps } from './interfaces.js' +import type { PopoverStyleSlots } from './interfaces.js' -import { FloatingPortal } from '@floating-ui/react' -import { AnimatePresence } from 'framer-motion' -import { motion } from 'framer-motion' -import { cloneElement } from 'react' +import { FloatingPortal } from '@floating-ui/react' +import { AnimatePresence } from 'framer-motion' +import { motion } from 'framer-motion' +import { cloneElement } from 'react' -import { useFloat } from '@atls-utils/use-float' +import { useFloat } from '@atls-utils/use-float' -import { Arrow } from './arrow/index.js' -import { Container } from './container/index.js' -import { animateProps } from './constants.js' +import { Arrow } from './arrow/index.js' +import { Container } from './container/index.js' +import { animateProps } from './constants.js' +import { popoverAppearances } from './styles/index.js' +import { popoverShapes } from './styles/index.js' + +const mergeClassName = (...classNames: Array): string | undefined => { + const className = classNames.filter(Boolean).join(' ') + + return className || undefined +} + +const mergeStyleSlots = ( + defaultSlots: PopoverStyleSlots, + customSlots?: PopoverStyleSlots +): PopoverStyleSlots => ({ + root: mergeClassName(defaultSlots.root, customSlots?.root), + title: mergeClassName(defaultSlots.title, customSlots?.title), + content: mergeClassName(defaultSlots.content, customSlots?.content), +}) export const Popover = ({ + appearance, children, title, content, open, - container = , + container, animated = true, arrow = true, + shape, ...props }: PopoverProps): ReactNode => { const { arrowRef, refs, isOpen, context, floatingStyles, getFloatingProps, getReferenceProps } = useFloat({ open, role: 'tooltip', ...props }) const TriggerElement = cloneElement(children, { ref: refs.setReference, ...getReferenceProps() }) + const defaultContainer = container === undefined + const containerElement = container ?? const ContainerElement = cloneElement( - container, - { open: isOpen, title, content }, + containerElement, + { + open: isOpen, + title, + content, + ...(defaultContainer + ? { + appearance: mergeStyleSlots(popoverAppearances.default, appearance), + shape: mergeStyleSlots(popoverShapes.default, shape), + } + : {}), + }, ) diff --git a/ui-parts/popover/src/container/component.tsx b/ui-parts/popover/src/container/component.tsx index 7b973a0a..884c6d44 100644 --- a/ui-parts/popover/src/container/component.tsx +++ b/ui-parts/popover/src/container/component.tsx @@ -6,10 +6,23 @@ import { containerStyles } from './styles.css.js' import { containerContentStyles } from './styles.css.js' import { containerTitleStyles } from './styles.css.js' -export const Container = ({ children, content, title }: ContainerProps): ReactNode => ( -
-
{title}
-
{content}
+const getClassName = (...classNames: Array): string => + classNames.filter(Boolean).join(' ') + +export const Container = ({ + appearance, + children, + content, + title, + shape, +}: ContainerProps): ReactNode => ( +
+
+ {title} +
+
+ {content} +
{children}
) diff --git a/ui-parts/popover/src/container/interfaces.ts b/ui-parts/popover/src/container/interfaces.ts index df04a95b..2e7bfdbd 100644 --- a/ui-parts/popover/src/container/interfaces.ts +++ b/ui-parts/popover/src/container/interfaces.ts @@ -1,7 +1,12 @@ import type { PropsWithChildren } from 'react' import type { ReactNode } from 'react' +import type { PopoverAppearance } from '../interfaces.js' +import type { PopoverShape } from '../interfaces.js' + export interface ContainerProps extends PropsWithChildren { + appearance?: PopoverAppearance title?: string content?: ReactNode + shape?: PopoverShape } diff --git a/ui-parts/popover/src/index.ts b/ui-parts/popover/src/index.ts index f564fb7d..5bf8dfad 100644 --- a/ui-parts/popover/src/index.ts +++ b/ui-parts/popover/src/index.ts @@ -1,3 +1,4 @@ export * from './arrow/index.js' export * from './component.js' +export * from './styles/index.js' export type * from './interfaces.js' diff --git a/ui-parts/popover/src/interfaces.ts b/ui-parts/popover/src/interfaces.ts index e3841028..38096718 100644 --- a/ui-parts/popover/src/interfaces.ts +++ b/ui-parts/popover/src/interfaces.ts @@ -4,11 +4,23 @@ import type { MotionProps } from 'framer-motion' import type { JSX } from 'react' import type { ReactNode } from 'react' +export interface PopoverStyleSlots { + root?: string + title?: string + content?: string +} + +export type PopoverAppearance = PopoverStyleSlots + +export type PopoverShape = PopoverStyleSlots + export interface PopoverProps extends UseFloatProps { + appearance?: PopoverAppearance children: JSX.Element title?: string content?: ReactNode container?: JSX.Element animated?: Omit | boolean arrow?: Omit | boolean + shape?: PopoverShape } diff --git a/ui-parts/popover/src/styles/appearance.css.ts b/ui-parts/popover/src/styles/appearance.css.ts new file mode 100644 index 00000000..40571284 --- /dev/null +++ b/ui-parts/popover/src/styles/appearance.css.ts @@ -0,0 +1,21 @@ +import type { PopoverAppearance } from '../interfaces.js' + +import { style } from '@vanilla-extract/css' + +import { vars } from '@atls-ui-parts/theme' + +const rootDefaultAppearanceStyles = style({ + backgroundColor: vars.colors.white, + boxShadow: vars.shadows.gordonsgreen, +}) + +const titleDefaultAppearanceStyles = style({ + borderBottom: vars.borders.thinLightGray, +}) + +export const popoverAppearances: Record<'default', PopoverAppearance> = { + default: { + root: rootDefaultAppearanceStyles, + title: titleDefaultAppearanceStyles, + }, +} diff --git a/ui-parts/popover/src/styles/index.ts b/ui-parts/popover/src/styles/index.ts new file mode 100644 index 00000000..93e0eec5 --- /dev/null +++ b/ui-parts/popover/src/styles/index.ts @@ -0,0 +1,2 @@ +export * from './appearance.css.js' +export * from './shape.css.js' diff --git a/ui-parts/popover/src/styles/shape.css.ts b/ui-parts/popover/src/styles/shape.css.ts new file mode 100644 index 00000000..88eba7a2 --- /dev/null +++ b/ui-parts/popover/src/styles/shape.css.ts @@ -0,0 +1,30 @@ +import type { PopoverShape } from '../interfaces.js' + +import { style } from '@vanilla-extract/css' + +import { vars } from '@atls-ui-parts/theme' + +const rootDefaultShapeStyles = style({ + minWidth: 180, + minHeight: 32, + padding: vars.space.zero, + borderRadius: vars.radii.f4, + zIndex: 1000, +}) + +const titleDefaultShapeStyles = style({ + minHeight: 32, + padding: `${vars.space.g4} ${vars.space.g8}`, +}) + +const contentDefaultShapeStyles = style({ + padding: `${vars.space.g12} ${vars.space.g16}`, +}) + +export const popoverShapes: Record<'default', PopoverShape> = { + default: { + root: rootDefaultShapeStyles, + title: titleDefaultShapeStyles, + content: contentDefaultShapeStyles, + }, +} From eff0edc2a0f3178f8bebb82adf835a339e6ef950 Mon Sep 17 00:00:00 2001 From: Arina Gazhina Date: Wed, 17 Jun 2026 21:25:56 +0300 Subject: [PATCH 2/3] feat(popover): update stories --- ui-parts/popover/stories/interfaces.ts | 1 + ui-parts/popover/stories/popover.stories.tsx | 12 +++++++ ui-parts/popover/stories/story-popover.tsx | 37 ++++++++++++++++---- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/ui-parts/popover/stories/interfaces.ts b/ui-parts/popover/stories/interfaces.ts index 5001f23d..4fe6d966 100644 --- a/ui-parts/popover/stories/interfaces.ts +++ b/ui-parts/popover/stories/interfaces.ts @@ -5,6 +5,7 @@ import type { ReactNode } from 'react' export interface StoryPopoverProps extends Pick { customContainer: boolean + styledContainer: boolean } export interface StoryPopoverContainerProps extends PropsWithChildren { diff --git a/ui-parts/popover/stories/popover.stories.tsx b/ui-parts/popover/stories/popover.stories.tsx index 26bef63f..191630d2 100644 --- a/ui-parts/popover/stories/popover.stories.tsx +++ b/ui-parts/popover/stories/popover.stories.tsx @@ -24,6 +24,10 @@ const meta: Meta = { description: 'Свой контейнер', control: { type: 'boolean' }, }, + styledContainer: { + description: 'Стили дефолтного контейнера', + control: { type: 'boolean' }, + }, offset: { description: 'Офсет', control: { type: 'number' }, @@ -56,6 +60,7 @@ const meta: Meta = { animated: true, arrow: true, customContainer: false, + styledContainer: false, offset: 10, placement: 'top', trigger: 'click', @@ -67,3 +72,10 @@ export default meta export const Base: StoryObj = { render: StoryPopover, } + +export const StyledContainer: StoryObj = { + args: { + styledContainer: true, + }, + render: StoryPopover, +} diff --git a/ui-parts/popover/stories/story-popover.tsx b/ui-parts/popover/stories/story-popover.tsx index 1cf8d9ab..89124b95 100644 --- a/ui-parts/popover/stories/story-popover.tsx +++ b/ui-parts/popover/stories/story-popover.tsx @@ -1,13 +1,19 @@ -import type { ReactElement } from 'react' +import type { ReactElement } from 'react' -import type { StoryPopoverProps } from './interfaces.js' +import type { StoryPopoverProps } from './interfaces.js' -import { useState } from 'react' +import { useState } from 'react' -import { Popover } from '@atls-ui-parts/popover' +import { Popover } from '@atls-ui-parts/popover' -import { StoryPopoverContainer } from './story-popover-container.js' -import { storyTriggerStyles } from './styles.css.js' +import { StoryPopoverContainer } from './story-popover-container.js' +import { storyContentAppearanceStyles } from './styles.css.js' +import { storyContentShapeStyles } from './styles.css.js' +import { storyRootAppearanceStyles } from './styles.css.js' +import { storyRootShapeStyles } from './styles.css.js' +import { storyTitleAppearanceStyles } from './styles.css.js' +import { storyTitleShapeStyles } from './styles.css.js' +import { storyTriggerStyles } from './styles.css.js' export const StoryPopover = ({ animated, @@ -15,6 +21,7 @@ export const StoryPopover = ({ customContainer, offset, placement, + styledContainer, trigger, }: StoryPopoverProps): ReactElement => { const [open, setOpen] = useState(false) @@ -34,6 +41,24 @@ export const StoryPopover = ({ title='Popover title' content='Popover content' container={customContainer ? : undefined} + appearance={ + styledContainer + ? { + root: storyRootAppearanceStyles, + title: storyTitleAppearanceStyles, + content: storyContentAppearanceStyles, + } + : undefined + } + shape={ + styledContainer + ? { + root: storyRootShapeStyles, + title: storyTitleShapeStyles, + content: storyContentShapeStyles, + } + : undefined + } onOpenChange={setOpen} >
Trigger
From 95cbd086981f22d1ca7f8188850333e6f0e522c6 Mon Sep 17 00:00:00 2001 From: Arina Gazhina Date: Wed, 17 Jun 2026 21:26:24 +0300 Subject: [PATCH 3/3] refactor(popover): use theme tokens --- .pnp.cjs | 1 + ui-parts/popover/package.json | 1 + ui-parts/popover/src/container/styles.css.ts | 11 ----- ui-parts/popover/stories/styles.css.ts | 49 ++++++++++++++++---- yarn.lock | 1 + 5 files changed, 42 insertions(+), 21 deletions(-) diff --git a/.pnp.cjs b/.pnp.cjs index a6267e62..b1e9324f 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -1681,6 +1681,7 @@ const RAW_RUNTIME_STATE = ["@atls-ui-parts/condition", "virtual:da655f76fb0a7abb1a892608b634316179378c6672ef2e4237cfc638bc59c4632d4d90add99f0f36fc3476fd7ba3bfb9b8da134f89edd82cecf6748f1bd98e54#workspace:ui-parts/condition"],\ ["@atls-ui-parts/layout", "virtual:76ebc481ac02aa95961a7136f7e551f7070fbf8eb171c0ca48399e504fe0f0f0edaf1d4f5953b763ae0b309e0e91ebf7d54e047cc0a0270d8aee71a756c50a70#workspace:ui-parts/layout"],\ ["@atls-ui-parts/popover", "workspace:ui-parts/popover"],\ + ["@atls-ui-parts/theme", "virtual:6e3d13d4bb141546c851eafb803659d9bef8a8a4b826ab761e48399dcbf8cda80227a38097da69531ddae63c1380c43a5fad649d9ff8e485d2bc4c96720b9bd0#workspace:ui-parts/theme"],\ ["@atls-utils/use-float", "virtual:e8b379d238bee0c446fdeaddb9725553fcee91eeb8db9f1f4b47e4e36a5c6c26c07ae1861b7d0a5dd574863bc008f048d25fc793392ceeaade0c9801b921d418#workspace:utils/use-float"],\ ["@floating-ui/react", "virtual:bdec61daa5ce18b211e41086a87aa58babcd016eed745eb318a2389812d34b0215bd01c4e05f4de11cfc30bd0259efe6cace4826920dba58cacb6c2dd7978d3e#npm:0.27.8"],\ ["@storybook/react", "virtual:76ebc481ac02aa95961a7136f7e551f7070fbf8eb171c0ca48399e504fe0f0f0edaf1d4f5953b763ae0b309e0e91ebf7d54e047cc0a0270d8aee71a756c50a70#npm:8.6.12"],\ diff --git a/ui-parts/popover/package.json b/ui-parts/popover/package.json index fa43c46e..3c4f4783 100644 --- a/ui-parts/popover/package.json +++ b/ui-parts/popover/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "@atls-ui-parts/condition": "workspace:*", + "@atls-ui-parts/theme": "workspace:*", "@atls-utils/use-float": "workspace:*", "@floating-ui/react": "0.27.8", "framer-motion": "12.23.22" diff --git a/ui-parts/popover/src/container/styles.css.ts b/ui-parts/popover/src/container/styles.css.ts index 1097d230..a204d81c 100644 --- a/ui-parts/popover/src/container/styles.css.ts +++ b/ui-parts/popover/src/container/styles.css.ts @@ -3,19 +3,11 @@ import { style } from '@vanilla-extract/css' export const containerStyles = style({ display: 'flex', flexDirection: 'column', - minWidth: 180, - minHeight: 32, - padding: 0, margin: 0, - backgroundColor: 'rgba(255, 255, 255, 1)', - borderRadius: '4px', - boxShadow: '0px 2px 24px rgba(0, 0, 0, 0.15)', - zIndex: 1000, }) export const containerContentStyles = style({ display: 'flex', - padding: '12px 16px', width: '100%', }) @@ -24,8 +16,5 @@ export const containerTitleStyles = style({ display: 'flex', alignItems: 'center', justifyContent: 'center', - padding: '4px 8px', - borderBottom: '1px solid rgba(228, 228, 228, 1)', - minHeight: 32, width: '100%', }) diff --git a/ui-parts/popover/stories/styles.css.ts b/ui-parts/popover/stories/styles.css.ts index e1d1be5e..f5af5a7f 100644 --- a/ui-parts/popover/stories/styles.css.ts +++ b/ui-parts/popover/stories/styles.css.ts @@ -1,15 +1,17 @@ import { style } from '@vanilla-extract/css' +import { vars } from '@atls-ui-parts/theme' + export const storyTriggerStyles = style({ boxSizing: 'border-box', display: 'flex', alignItems: 'center', justifyContent: 'center', height: 32, - padding: '0px 16px', - color: 'rgba(0, 0, 0, 0.65)', - backgroundColor: '#fff', - border: '1px solid #d9d9d9', + padding: `${vars.space.zero} ${vars.space.g16}`, + color: vars.colors.blackThreeQuarters, + backgroundColor: vars.colors.white, + border: vars.borders.normalMediumGray, cursor: 'pointer', }) @@ -18,15 +20,42 @@ export const storyContainerStyles = style({ flexDirection: 'column', minWidth: 100, minHeight: 64, - padding: 10, - margin: 0, - backgroundColor: 'rgba(255, 255, 255, 1)', - boxShadow: '0px 2px 24px rgba(0, 0, 0, 0.15)', - borderRadius: '8px', + padding: vars.space.g10, + margin: vars.space.zero, + backgroundColor: vars.colors.white, + boxShadow: vars.shadows.gordonsgreen, + borderRadius: vars.radii.f8, zIndex: 1000, }) export const storyContainerCloseStyles = style({ cursor: 'pointer', - color: '#1890ff', + color: vars.colors.blueProtective, +}) + +export const storyRootAppearanceStyles = style({ + backgroundColor: vars.colors.gray, + boxShadow: vars.shadows.jaguar, +}) + +export const storyRootShapeStyles = style({ + minWidth: 220, + borderRadius: vars.radii.f12, +}) + +export const storyTitleAppearanceStyles = style({ + color: vars.colors.green, + borderBottom: vars.borders.normalGreen, +}) + +export const storyTitleShapeStyles = style({ + justifyContent: 'flex-start', +}) + +export const storyContentAppearanceStyles = style({ + color: vars.colors['text.green'], +}) + +export const storyContentShapeStyles = style({ + padding: `${vars.space.g16} 20px`, }) diff --git a/yarn.lock b/yarn.lock index e646ca98..ecef53c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -813,6 +813,7 @@ __metadata: dependencies: "@atls-ui-parts/condition": "workspace:*" "@atls-ui-parts/layout": "workspace:*" + "@atls-ui-parts/theme": "workspace:*" "@atls-utils/use-float": "workspace:*" "@floating-ui/react": "npm:0.27.8" "@storybook/react": "npm:8.6.12"