Skip to content

Commit

Permalink
refactor: Balloon を Tailwind CSS 化 (#3936)
Browse files Browse the repository at this point in the history
* feat: 透過色を追加

* refactor: Balloon を Tailwind CSS 化

* chore: Story を整理

* fix: 任意の値を置き換え
  • Loading branch information
uknmr authored Nov 13, 2023
1 parent c8460ad commit 1f3e22c
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 158 deletions.
6 changes: 3 additions & 3 deletions src/components/Balloon/Balloon.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Story } from '@storybook/react'
import * as React from 'react'
import { StoryFn } from '@storybook/react'
import React from 'react'
import styled from 'styled-components'

import { Balloon } from './Balloon'
Expand All @@ -9,7 +9,7 @@ export default {
component: Balloon,
}

export const All: Story = () => (
export const All: StoryFn = () => (
<List>
<li>
<Balloon horizontal="left" vertical="top">
Expand Down
280 changes: 125 additions & 155 deletions src/components/Balloon/Balloon.tsx
Original file line number Diff line number Diff line change
@@ -1,164 +1,134 @@
import React, { HTMLAttributes, ReactNode, VFC } from 'react'
import styled, { css } from 'styled-components'
import React, { ComponentPropsWithoutRef, FC, PropsWithChildren, useMemo } from 'react'
import { VariantProps, tv } from 'tailwind-variants'

import { Theme, useTheme } from '../../hooks/useTheme'

import { useClassNames } from './useClassNames'

export type BalloonTheme = 'light' | 'dark'

export type Props = {
/** 吹き出しの垂直位置 */
horizontal: 'right' | 'center' | 'left'
/** 吹き出しの水平位置 */
vertical: 'top' | 'middle' | 'bottom'
/** コンポーネントに適用するクラス名 */
className?: string
/** バルーン内のコンテンツ */
children?: ReactNode
/** レンダリングするタグ */
as?: 'div' | 'span'
}
// HINT: trianble部分はRetinaディスプレイなどで途切れてしまう場合があるので
// 1pxほど大きめに描画してbody部分と被るようにしています。
const balloon = tv({
base: [
'smarthr-ui-Balloon',
'shr-relative',
'shr-inline-block',
'shr-text-sm',
'shr-rounded-s',
/* drop-shadow は spread-radius を受け付けないので shadow.LAYER2 に近い値をハードコーディングしている */
'shr-drop-shadow-[0_2px_2.5px_theme(colors.transparency.30)]',
'shr-whitespace-nowrap',
'shr-bg-white',
'shr-text-black',
'after:shr-block',
'after:shr-absolute',
'after:shr-content-[""]',
'after:shr-bg-white',
'contrast-more:shr-border',
'contrast-more:shr-border-solid',
'contrast-more:shr-border-highContrast',
'contrast-more:before:shr-block',
'contrast-more:before:shr-absolute',
'contrast-more:before:shr-content-[""]',
'contrast-more:before:shr-bg-black',
],
variants: {
horizontal: {
center: [
'before:shr-left-1/2',
'before:-shr-translate-x-[5px]',
'after:shr-left-1/2',
'after:-shr-translate-x-[5px]',
],
right: '',
left: '',
},
vertical: {
top: [
'before:-shr-top-[5px]',
'before:shr-w-[10px]',
'before:shr-h-[5px]',
'before:[clip-path:polygon(50%_0,100%_100%,0_100%)]',
'after:-shr-top-0.25',
'after:shr-w-[10px]',
'after:shr-h-[5px]',
'after:[clip-path:polygon(50%_0,100%_100%,0_100%)]',
],
bottom: [
'before:-shr-bottom-[5px]',
'before:shr-w-[10px]',
'before:shr-h-[5px]',
'before:[clip-path:polygon(0_0,100%_0,50%_100%)]',
'after:-shr-bottom-0.25',
'after:shr-w-[10px]',
'after:shr-h-[5px]',
'after:[clip-path:polygon(0_0,100%_0,50%_100%)]',
],
middle: [
'before:shr-top-1/2',
'before:-shr-translate-y-[5px]',
'after:shr-top-1/2',
'after:-shr-translate-y-[5px]',
],
},
},
compoundVariants: [
{
vertical: ['top', 'bottom'],
horizontal: 'left',
className: ['before:shr-left-1.5', 'after:shr-left-1.5'],
},
{
vertical: ['top', 'bottom'],
horizontal: 'right',
className: ['before:shr-right-1.5', 'after:shr-right-1.5'],
},
{
vertical: 'middle',
horizontal: 'left',
className: [
'before:-shr-left-[5px]',
'before:shr-w-[5px]',
'before:shr-h-[10px]',
'before:[clip-path:polygon(100%_0,100%_100%,0_50%)]',
'after:-shr-left-0.25',
'after:shr-w-[5px]',
'after:shr-h-[10px]',
'after:[clip-path:polygon(100%_0,100%_100%,0_50%)]',
],
},
{
vertical: 'middle',
horizontal: 'right',
className: [
'before:-shr-right-[5px]',
'before:shr-w-[5px]',
'before:shr-h-[10px]',
'before:[clip-path:polygon(0_0,100%_50%,0_100%)]',
'after:-shr-right-0.25',
'after:shr-w-[5px]',
'after:shr-h-[10px]',
'after:[clip-path:polygon(0_0,100%_50%,0_100%)]',
],
},
],
})

export type Props = PropsWithChildren<
VariantProps<typeof balloon> & {
/** レンダリングするタグ */
as?: 'div' | 'span'
}
>

type ElementProps = Omit<HTMLAttributes<HTMLDivElement>, keyof Props>
type ElementProps = Omit<ComponentPropsWithoutRef<'div'>, keyof Props>

export const Balloon: VFC<Props & ElementProps> = ({
export const Balloon: FC<Props & ElementProps> = ({
horizontal,
vertical,
className = '',
className,
as: Component = 'div',
...props
}) => {
if (horizontal === 'center' && vertical === 'middle') {
throw new Error('"vertical" can not be specified as "middle" when "horizontal" is "center".')
}

const themes = useTheme()
const { wrapper } = useClassNames()
const classNames = `${horizontal} ${vertical} ${className} ${wrapper}`
const styles = useMemo(
() => balloon({ horizontal, vertical, className }),
[className, horizontal, vertical],
)

return <Base {...props} className={classNames} themes={themes} />
return <Component {...props} className={styles} />
}

// HINT: trianble部分はRetinaディスプレイなどで途切れてしまう場合があるので
// 1pxほど大きめに描画してbody部分と被るようにしています。
const Base = styled.div<{ themes: Theme }>`
${({ themes }) => {
const { border, color, fontSize } = themes
return css`
position: relative;
display: inline-block;
font-size: ${fontSize.S};
border-radius: 4px;
filter: drop-shadow(
0 2px 2.5px rgba(0, 0, 0, 0.33)
); /* drop-shadow は spread-radius を受け付けないので shadow.LAYER2 に近い値をハードコーディングしている */
white-space: nowrap;
transform: translateZ(0); /* safari で filter を正しく描画するために必要 */
&::after {
display: block;
position: absolute;
content: '';
background-color: ${color.WHITE};
}
background-color: ${color.WHITE};
color: ${color.TEXT_BLACK};
@media (prefers-contrast: more) {
& {
border: ${border.highContrast};
}
&::before {
display: block;
position: absolute;
content: '';
background-color: ${color.TEXT_BLACK};
}
}
&.top {
&::before,
&::after {
top: -4px;
width: 10px;
height: 5px;
clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
&::before {
top: -5px;
}
}
&.bottom {
&::before,
&::after {
bottom: -4px;
width: 10px;
height: 5px;
clip-path: polygon(0 0, 100% 0, 50% 100%);
}
&::before {
bottom: -5px;
}
}
&.right {
&::before,
&::after {
right: 24px;
}
}
&.center {
&::before,
&::after {
left: 50%;
transform: translateX(-5px);
}
}
&.left {
&::before,
&::after {
left: 24px;
}
}
&.middle {
&::before,
&::after {
top: 50%;
transform: translateY(-5px);
}
&.left {
&::before,
&::after {
left: -4px;
width: 5px;
height: 10px;
clip-path: polygon(100% 0, 100% 100%, 0 50%);
}
&::before {
left: -5px;
}
}
&.right {
&::before,
&::after {
right: -4px;
width: 5px;
height: 10px;
clip-path: polygon(0 0, 100% 50%, 0 100%);
}
&::before {
right: -5px;
}
}
}
`
}}
`
5 changes: 5 additions & 0 deletions src/smarthr-ui-preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ export default {
65: defaultColor.GREY_65,
100: defaultColor.GREY_100,
},
transparency: {
15: defaultColor.TRANSPARENCY_15,
30: defaultColor.TRANSPARENCY_30,
50: defaultColor.TRANSPARENCY_50,
},
inherit: 'inherit',
transparent: 'transparent',
current: 'currentColor',
Expand Down

0 comments on commit 1f3e22c

Please sign in to comment.