Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .pnp.cjs

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

1 change: 1 addition & 0 deletions ui-parts/popover/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 2 additions & 1 deletion ui-parts/popover/src/arrow/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ 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 => (
<Condition match={!!arrow}>
<FloatingArrow
ref={ref}
context={context}
width={16}
fill='rgba(255, 255, 255, 1)'
fill={vars.colors.white}
{...(typeof arrow === 'boolean' ? {} : arrow)}
/>
</Condition>
Expand Down
58 changes: 45 additions & 13 deletions ui-parts/popover/src/component.tsx
Original file line number Diff line number Diff line change
@@ -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>): 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 />,
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 ?? <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),
}
: {}),
},
<Arrow ref={arrowRef} context={context} arrow={arrow} />
)

Expand Down
21 changes: 17 additions & 4 deletions ui-parts/popover/src/container/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 => (
<div className={containerStyles}>
<div className={containerTitleStyles}>{title}</div>
<div className={containerContentStyles}>{content}</div>
const getClassName = (...classNames: Array<string | undefined>): string =>
classNames.filter(Boolean).join(' ')

export const Container = ({
appearance,
children,
content,
title,
shape,
}: ContainerProps): ReactNode => (
<div className={getClassName(containerStyles, appearance?.root, shape?.root)}>
<div className={getClassName(containerTitleStyles, appearance?.title, shape?.title)}>
{title}
</div>
<div className={getClassName(containerContentStyles, appearance?.content, shape?.content)}>
{content}
</div>
{children}
</div>
)
5 changes: 5 additions & 0 deletions ui-parts/popover/src/container/interfaces.ts
Original file line number Diff line number Diff line change
@@ -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
}
11 changes: 0 additions & 11 deletions ui-parts/popover/src/container/styles.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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%',
})

Expand All @@ -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%',
})
1 change: 1 addition & 0 deletions ui-parts/popover/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './arrow/index.js'
export * from './component.js'
export * from './styles/index.js'
export type * from './interfaces.js'
12 changes: 12 additions & 0 deletions ui-parts/popover/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<MotionProps, 'children'> | boolean
arrow?: Omit<FloatingArrowProps, 'context'> | boolean
shape?: PopoverShape
}
21 changes: 21 additions & 0 deletions ui-parts/popover/src/styles/appearance.css.ts
Original file line number Diff line number Diff line change
@@ -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,
},
}
2 changes: 2 additions & 0 deletions ui-parts/popover/src/styles/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './appearance.css.js'
export * from './shape.css.js'
30 changes: 30 additions & 0 deletions ui-parts/popover/src/styles/shape.css.ts
Original file line number Diff line number Diff line change
@@ -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,
},
}
1 change: 1 addition & 0 deletions ui-parts/popover/stories/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { ReactNode } from 'react'
export interface StoryPopoverProps
extends Pick<PopoverProps, 'animated' | 'arrow' | 'offset' | 'placement' | 'trigger'> {
customContainer: boolean
styledContainer: boolean
}

export interface StoryPopoverContainerProps extends PropsWithChildren {
Expand Down
12 changes: 12 additions & 0 deletions ui-parts/popover/stories/popover.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ const meta: Meta<StoryPopoverProps> = {
description: 'Свой контейнер',
control: { type: 'boolean' },
},
styledContainer: {
description: 'Стили дефолтного контейнера',
control: { type: 'boolean' },
},
offset: {
description: 'Офсет',
control: { type: 'number' },
Expand Down Expand Up @@ -56,6 +60,7 @@ const meta: Meta<StoryPopoverProps> = {
animated: true,
arrow: true,
customContainer: false,
styledContainer: false,
offset: 10,
placement: 'top',
trigger: 'click',
Expand All @@ -67,3 +72,10 @@ export default meta
export const Base: StoryObj<StoryPopoverProps> = {
render: StoryPopover,
}

export const StyledContainer: StoryObj<StoryPopoverProps> = {
args: {
styledContainer: true,
},
render: StoryPopover,
}
37 changes: 31 additions & 6 deletions ui-parts/popover/stories/story-popover.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
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,
arrow,
customContainer,
offset,
placement,
styledContainer,
trigger,
}: StoryPopoverProps): ReactElement => {
const [open, setOpen] = useState<boolean>(false)
Expand All @@ -34,6 +41,24 @@ export const StoryPopover = ({
title='Popover title'
content='Popover content'
container={customContainer ? <StoryPopoverContainer onClose={handleClose} /> : undefined}
appearance={
styledContainer
? {
root: storyRootAppearanceStyles,
title: storyTitleAppearanceStyles,
content: storyContentAppearanceStyles,
}
: undefined
}
shape={
styledContainer
? {
root: storyRootShapeStyles,
title: storyTitleShapeStyles,
content: storyContentShapeStyles,
}
: undefined
}
onOpenChange={setOpen}
>
<div className={storyTriggerStyles}>Trigger</div>
Expand Down
Loading
Loading