From 01b6fcf5ea03c614eaf71cf21a7984ad115f2266 Mon Sep 17 00:00:00 2001 From: "noah.choi" Date: Thu, 27 Aug 2026 16:03:39 +0900 Subject: [PATCH 01/12] remove unnecessary use-client --- packages/composites/src/utils/create-slots.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/composites/src/utils/create-slots.ts b/packages/composites/src/utils/create-slots.ts index 245418521..34148abaf 100644 --- a/packages/composites/src/utils/create-slots.ts +++ b/packages/composites/src/utils/create-slots.ts @@ -1,5 +1,3 @@ -'use client'; - import type { ComponentProps, ElementType, ReactElement, ReactNode } from 'react'; import { Fragment, createElement, isValidElement } from 'react'; From 76efafb991e71b3b734b33acad685719792356f2 Mon Sep 17 00:00:00 2001 From: "noah.choi" Date: Wed, 26 Aug 2026 11:59:29 +0900 Subject: [PATCH 02/12] add test-bed table utilities --- .../src/components/dialog/dialog.stories.tsx | 102 +++++++++ packages/composites/src/utils/regressions.tsx | 207 ++++++++++++++++++ 2 files changed, 309 insertions(+) create mode 100644 packages/composites/src/utils/regressions.tsx diff --git a/packages/composites/src/components/dialog/dialog.stories.tsx b/packages/composites/src/components/dialog/dialog.stories.tsx index 6de2d5c78..9ccd3ac2d 100644 --- a/packages/composites/src/components/dialog/dialog.stories.tsx +++ b/packages/composites/src/components/dialog/dialog.stories.tsx @@ -3,6 +3,8 @@ import { useRef, useState } from 'react'; import type { Meta, StoryObj } from '@storybook/react-vite'; import { Button, HStack, Text, TextInput, VStack } from '@vapor-ui/core'; +import { Regression, cartesianRows } from '~/utils/regressions'; + import { Dialog } from '.'; export default { @@ -181,3 +183,103 @@ export const AsyncAction: Story = { ); }, }; + +/* ----------------------------------------------------------------------------------------------- + * Test Bed + * ---------------------------------------------------------------------------------------------- */ + +const SIZES = ['md', 'lg', 'xl'] as const; +const DESCRIPTION_PRESENCE = [ + '유사도 검사 결과를 다운받으시겠어요? 결과는 현재 표에 적용된 필터 기준으로 다운로드 됩니다.', + null, +] as const; +const SCROLL = [6, 2] as const; + +const props = { + ariaLabels: { close: '닫기' }, + title: '제목은 여러 줄에 걸쳐 표시될 수가 있습니다. 제목은 여러 줄에 걸쳐 표시될 수가 있습니다. 제목은 여러 줄에 걸쳐 표시될 수가 있습니다.', + action: 확인, + assistive: 보조, +} as const; + +const testBedRows = cartesianRows({ + size: SIZES, + hasDescription: DESCRIPTION_PRESENCE, + hasScroll: SCROLL, +}); + +const dialogOverlayStripStyles = ` +.regression-cell [data-base-ui-portal] [role="dialog"] { + top: unset; + left: unset; + transform: unset; +} +`; + +const TestBedRender = () => { + return ( + <> + + + + + + + + + + + size + description + scroll + render + + + + {testBedRows.map((row, idx) => ( + + size = {row.size} + + description = {row.hasDescription ? 'O' : 'X'} + + + scroll = {row.hasScroll ? 'O' : 'X'} + + + {(container) => ( + + + + {Array.from( + { length: row.hasScroll }, + (_, index) => ( + + 내용물입니다. + + ), + )} + + + + )} + + + ))} + + + + ); +}; + +export const TestBed: Story = { + render: () => , +}; diff --git a/packages/composites/src/utils/regressions.tsx b/packages/composites/src/utils/regressions.tsx new file mode 100644 index 000000000..fa722fae5 --- /dev/null +++ b/packages/composites/src/utils/regressions.tsx @@ -0,0 +1,207 @@ +import type { ReactNode } from 'react'; +import { useState } from 'react'; + +import { Box, Table } from '@vapor-ui/core'; + +/* ----------------------------------------------------------------------------------------------- + * Regression.Root + * -----------------------------------------------------------------------------------------------*/ + +const RegressionRoot = ({ children }: { children: ReactNode }) => { + return ( + <> + + + + {children} + + + + ); +}; + +const portalResetStyles = ` +.regression-cell { + position: relative; + display: flex; + justify-content: flex-start; + width: fit-content; + min-width: 100%; + min-height: 320px; + padding: 16px; + box-sizing: border-box; + transform: translateZ(0); +} +.regression-cell [data-base-ui-portal] { + position: static !important; + display: contents !important; +} +.regression-cell [data-base-ui-portal] > [data-open][role="presentation"] { + position: static !important; + inset: auto !important; + top: auto !important; + left: auto !important; + right: auto !important; + bottom: auto !important; + transform: none !important; + max-height: 100% !important; +} +`; + +/* ----------------------------------------------------------------------------------------------- + * Regression.ColumnGroup + * -----------------------------------------------------------------------------------------------*/ + +const RegressionColumnGroup = ({ children }: { children: ReactNode }) => { + return {children}; +}; + +/* ----------------------------------------------------------------------------------------------- + * Regression.ConditionColumn + * -----------------------------------------------------------------------------------------------*/ + +const RegressionConditionColumn = () => { + return ; +}; + +/* ----------------------------------------------------------------------------------------------- + * Regression.RenderColumn + * -----------------------------------------------------------------------------------------------*/ + +const RegressionRenderColumn = () => { + return ; +}; + +/* ----------------------------------------------------------------------------------------------- + * Regression.Header + * -----------------------------------------------------------------------------------------------*/ + +const RegressionHeader = ({ children }: { children: ReactNode }) => { + return {children}; +}; + +/* ----------------------------------------------------------------------------------------------- + * Regression.Body + * -----------------------------------------------------------------------------------------------*/ + +const RegressionBody = ({ children }: { children: ReactNode }) => { + return {children}; +}; + +/* ----------------------------------------------------------------------------------------------- + * Regression.Row + * -----------------------------------------------------------------------------------------------*/ + +const RegressionRow = ({ children }: { children: ReactNode }) => { + return {children}; +}; + +/* ----------------------------------------------------------------------------------------------- + * Regression.Heading + * -----------------------------------------------------------------------------------------------*/ + +const RegressionHeading = ({ children }: { children: ReactNode }) => { + return ( + + {children} + + ); +}; + +/* ----------------------------------------------------------------------------------------------- + * Regression.Condition + * -----------------------------------------------------------------------------------------------*/ + +const RegressionCondition = ({ children }: { children: ReactNode }) => { + return ( + + {children} + + ); +}; + +/* ----------------------------------------------------------------------------------------------- + * Regression.Render + * -----------------------------------------------------------------------------------------------*/ + +const RegressionRender = ({ + children: childrenProp, +}: { + children: ((container: HTMLElement | null) => ReactNode) | ReactNode; +}) => { + const [container, setContainer] = useState(null); + const children = typeof childrenProp === 'function' ? childrenProp(container) : childrenProp; + + return ( + +
+ {children} +
+
+ ); +}; + +/* ----------------------------------------------------------------------------------------------- + * cartesianRows + * -----------------------------------------------------------------------------------------------*/ + +export type CartesianRows> = Array<{ + [K in keyof T]: T[K][number]; +}>; + +export const cartesianRows = >( + conditions: T, +): CartesianRows => { + const keys = Object.keys(conditions) as (keyof T)[]; + if (keys.length === 0) return [{}] as CartesianRows; + + return keys.reduce>>( + (acc, key) => + acc.flatMap((row) => conditions[key].map((value) => ({ ...row, [key]: value }))), + [{}], + ) as CartesianRows; +}; + +/* -----------------------------------------------------------------------------------------------*/ + +export const Regression = { + Root: RegressionRoot, + ColumnGroup: RegressionColumnGroup, + ConditionColumn: RegressionConditionColumn, + RenderColumn: RegressionRenderColumn, + Header: RegressionHeader, + Body: RegressionBody, + Row: RegressionRow, + Heading: RegressionHeading, + Condition: RegressionCondition, + Render: RegressionRender, +}; From f6b98c7d0e925506a01de9f857438ccefd77fe13 Mon Sep 17 00:00:00 2001 From: "noah.choi" Date: Wed, 26 Aug 2026 14:33:11 +0900 Subject: [PATCH 03/12] integrate regression tables --- packages/composites/src/utils/regressions.tsx | 87 ++++++++++++++++++- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/packages/composites/src/utils/regressions.tsx b/packages/composites/src/utils/regressions.tsx index fa722fae5..4afed2d56 100644 --- a/packages/composites/src/utils/regressions.tsx +++ b/packages/composites/src/utils/regressions.tsx @@ -27,12 +27,13 @@ const portalResetStyles = ` position: relative; display: flex; justify-content: flex-start; - width: fit-content; - min-width: 100%; + width: 100%; + min-width: 0; min-height: 320px; padding: 16px; box-sizing: border-box; transform: translateZ(0); + overflow: hidden; } .regression-cell [data-base-ui-portal] { position: static !important; @@ -46,7 +47,9 @@ const portalResetStyles = ` right: auto !important; bottom: auto !important; transform: none !important; + max-width: 100% !important; max-height: 100% !important; + box-sizing: border-box !important; } `; @@ -191,6 +194,85 @@ export const cartesianRows = >( ) as CartesianRows; }; +/* ----------------------------------------------------------------------------------------------- + * Regression.Table + * -----------------------------------------------------------------------------------------------*/ + +type RegressionCondition = { + key: Key; + label: ReactNode; + values: readonly Value[]; + format: (value: Value) => ReactNode; +}; + +type AnyRegressionCondition = RegressionCondition; + +type RegressionRow = { + [K in C[number]['key']]: Extract['values'][number]; +}; + +type RegressionTableProps = { + conditions: C; + render: (row: RegressionRow, container: HTMLElement | null) => ReactNode; +}; + +const RegressionTable = ({ + conditions, + render, +}: RegressionTableProps) => { + const conditionMap = Object.fromEntries( + conditions.map((condition) => [condition.key, condition.values]), + ); + + const rows = cartesianRows(conditionMap) as RegressionRow[]; + + return ( + + + {conditions.map((_, colIdx) => ( + + ))} + + + + + {conditions.map((condition, colIdx) => ( + {condition.label} + ))} + render + + + + {rows.map((row, rowIdx) => ( + + {conditions.map((condition, colIdx) => ( + + {condition.format(row[condition.key as keyof RegressionRow])} + + ))} + {(container) => render(row, container)} + + ))} + + + + + 총 {rows.length}개 케이스 + + + + + ); +}; + /* -----------------------------------------------------------------------------------------------*/ export const Regression = { @@ -204,4 +286,5 @@ export const Regression = { Heading: RegressionHeading, Condition: RegressionCondition, Render: RegressionRender, + Table: RegressionTable, }; From 21102e33fbd917f4bc7cf8f9af5b2a36dc00e368 Mon Sep 17 00:00:00 2001 From: "noah.choi" Date: Wed, 26 Aug 2026 14:51:54 +0900 Subject: [PATCH 04/12] add dialog parts dataset --- .../src/components/dialog/dialog.tsx | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/composites/src/components/dialog/dialog.tsx b/packages/composites/src/components/dialog/dialog.tsx index 03131f0eb..fbfbc335d 100644 --- a/packages/composites/src/components/dialog/dialog.tsx +++ b/packages/composites/src/components/dialog/dialog.tsx @@ -75,18 +75,23 @@ export const Dialog = ({ return ( - + - - + + - +
{children}