style(storybook): refine manager sidebar, LNB inset and shared story frame
- Manager sidebar labels and sort order for the grouped pages; LNB 1-depth text start 12px (token, baseline and LNB doc updated) - Shared story frame: drop the divider under section titles, side margin max 60px -> 30px; Foundation color/typography spacing - Pseudo-state mirroring also pins :focus-visible via data-sb-focus while keeping the previous clones for compound rules (Slider Focus + Drag)
This commit is contained in:
1 parent
7ff852952a
commit
fe9c115cf9
22 files changed
+100
-83
No files matched your search
+35
-39
@@ -16,7 +16,8 @@
|
||||
--overlay-black-30: rgba(0, 0, 0, .3);
|
||||
}
|
||||
/* Storybook 10.6 manager only. Source: docs/patterns/dashboard-composition.md §6.
|
||||
The 18px join / inset geometry follows the current LNB specimen, not a new token. */
|
||||
2026-11-13 compact 180px LNB: 14px side gutters, 40px / 36px rows, 6px selected radius and a 12px join
|
||||
(was 18px, the LNB specimen value) so the curves stay in proportion to the narrower column. Not a new token. */
|
||||
@font-face {
|
||||
font-family: 'Hanmac Manager Noto Sans KR';
|
||||
src: url('/fonts/NotoSansKR-variable.ttf') format('truetype');
|
||||
@@ -36,7 +37,7 @@
|
||||
}
|
||||
|
||||
#storybook-sidebar-region {
|
||||
--hm-manager-join: 18px;
|
||||
--hm-manager-join: 12px;
|
||||
color: var(--color-fg-on-green-primary);
|
||||
background: linear-gradient(90deg,
|
||||
var(--hm-green-default) 0%, var(--hm-green-light-1) 30%,
|
||||
@@ -46,7 +47,7 @@
|
||||
font-family: var(--hm-manager-font);
|
||||
}
|
||||
#storybook-sidebar-region [data-radix-scroll-area-content] > div {
|
||||
padding: 28px 0 28px 20px;
|
||||
padding: 20px 0 20px 14px;
|
||||
}
|
||||
/* Keep the native draggable scrollbar in the unused left gutter. The selected
|
||||
item must reach the content edge without a scroll thumb crossing its curves.
|
||||
@@ -61,13 +62,17 @@
|
||||
background: var(--hm-fg-on-green-secondary);
|
||||
}
|
||||
#storybook-sidebar-region .sidebar-header {
|
||||
margin: 0 20px 0px 0;
|
||||
margin: 0 14px 0px 0;
|
||||
padding: 0;
|
||||
color: var(--color-fg-on-green-primary);
|
||||
}
|
||||
/* The Settings button is hidden, so the title uses the full header width. */
|
||||
#storybook-sidebar-region .sidebar-header > div { margin-right: 0; }
|
||||
#storybook-sidebar-region .sidebar-header > div a {
|
||||
/* 2026-11-13: centred on the 180px LNB (the header box is already symmetric, 14px each side);
|
||||
Storybook's -4px link inline margin is dropped so the link spans exactly that box. */
|
||||
justify-content: center;
|
||||
margin-inline: 0;
|
||||
color: var(--color-fg-on-green-primary);
|
||||
font: 700 16px/30px var(--hm-manager-font);
|
||||
white-space: normal;
|
||||
@@ -113,27 +118,18 @@
|
||||
#storybook-explorer-tree .sidebar-subheading {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 0 20px 0 0;
|
||||
min-height: 48px;
|
||||
padding: 0 14px 0 0;
|
||||
min-height: 40px;
|
||||
color: var(--hm-fg-on-green-secondary);
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-subheading[data-hm-section]:not([data-hm-section='0']) {
|
||||
margin-top: 24px;
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-subheading[data-hm-section]:not([data-hm-section='0'])::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: 0;
|
||||
right: 20px;
|
||||
height: 2px;
|
||||
background: linear-gradient(var(--overlay-black-30) 0 50%, rgba(255,255,255,.4) 50% 100%);
|
||||
mask-image: linear-gradient(90deg, transparent, #000 20%, #000 80%, transparent);
|
||||
margin-top: 0;
|
||||
}
|
||||
/* 2026-11-13: the divider above each group was removed. */
|
||||
#storybook-explorer-tree .sidebar-subheading > button {
|
||||
min-height: 48px;
|
||||
min-height: 40px;
|
||||
color: inherit;
|
||||
font: 700 14px/28px var(--hm-manager-font);
|
||||
font: 700 14px/24px var(--hm-manager-font);
|
||||
letter-spacing: 0;
|
||||
text-transform: none;
|
||||
}
|
||||
@@ -141,7 +137,7 @@
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 48px;
|
||||
min-height: 40px;
|
||||
overflow: visible;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
@@ -155,51 +151,51 @@
|
||||
box-shadow: inset 1px 0 var(--hm-green-light-2);
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-item[data-hm-group-start='true'] {
|
||||
border-radius: 8px 0 0 0;
|
||||
border-radius: 6px 0 0 0;
|
||||
box-shadow: inset 1px 1px var(--hm-green-light-2);
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-item[data-hm-group-end='true'] {
|
||||
border-radius: 0 0 0 8px;
|
||||
padding-bottom: 8px;
|
||||
border-radius: 0 0 0 6px;
|
||||
padding-bottom: 6px;
|
||||
box-shadow: inset 1px -1px var(--hm-green-light-2);
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-item > :is(a[id], button[id]:not([data-testid])) {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-height: 48px;
|
||||
gap: 8px;
|
||||
min-height: 40px;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 10px 32px 10px 18px;
|
||||
padding: 8px 14px 8px 10px;
|
||||
border: 0;
|
||||
border-radius: 4px 0 0 4px;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: 700 14px/28px var(--hm-manager-font);
|
||||
font: 700 14px/24px var(--hm-manager-font);
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
white-space: normal;
|
||||
overflow: visible;
|
||||
text-overflow: clip;
|
||||
}
|
||||
/* Sub rows (depth 2+): 42px (7px + 28px line + 7px), shorter than the 48px top-level rows. */
|
||||
/* Sub rows (depth 2+): 36px (6px + 24px line + 6px), shorter than the 40px top-level rows (2026-11-13 compact 180px LNB). */
|
||||
#storybook-explorer-tree .sidebar-item:is([data-hm-depth='2'], [data-hm-depth='3'], [data-hm-depth='4']) {
|
||||
min-height: 42px;
|
||||
min-height: 36px;
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-item:is([data-hm-depth='2'], [data-hm-depth='3'], [data-hm-depth='4']) > :is(a[id], button[id]:not([data-testid])) {
|
||||
min-height: 42px;
|
||||
padding-block: 7px;
|
||||
min-height: 36px;
|
||||
padding-block: 6px;
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-item[data-hm-depth='2'] > :is(a[id], button[id]:not([data-testid])) {
|
||||
margin-left: 32px;
|
||||
width: calc(100% - 32px);
|
||||
margin-left: 26px;
|
||||
width: calc(100% - 26px);
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-item[data-hm-depth='3'] > :is(a[id], button[id]:not([data-testid])),
|
||||
#storybook-explorer-tree .sidebar-item[data-hm-depth='4'] > :is(a[id], button[id]:not([data-testid])) {
|
||||
margin-left: 44px;
|
||||
width: calc(100% - 44px);
|
||||
margin-left: 36px;
|
||||
width: calc(100% - 36px);
|
||||
}
|
||||
/* Menu item labels and section heading labels (.sidebar-subheading) are both 14px / 700. */
|
||||
#storybook-explorer-tree .sidebar-item .hm-manager-label {
|
||||
@@ -246,9 +242,9 @@
|
||||
display: none;
|
||||
}
|
||||
/* First-depth leaves still sit inside their root label after losing the icon
|
||||
slot. Deeper rows retain their existing 32px / 44px depth margins. */
|
||||
slot. Deeper rows retain their 26px / 36px depth margins. */
|
||||
#storybook-explorer-tree .sidebar-item[data-hm-depth='1'] > :is(a[id], button[id]:not([data-testid])):has(> div:first-child > svg[type]):not(:has(> div:first-child > div > svg)) {
|
||||
padding-left: 32px;
|
||||
padding-left: 26px;
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-item[data-hm-current-parent='true'],
|
||||
#storybook-explorer-tree .sidebar-subheading[data-hm-current-parent='true'] {
|
||||
@@ -261,7 +257,7 @@
|
||||
z-index: 1;
|
||||
background: var(--color-bg-content);
|
||||
color: var(--color-action-primary);
|
||||
border-radius: 8px 0 0 8px;
|
||||
border-radius: 6px 0 0 6px;
|
||||
}
|
||||
/* Keep the keyboard skip link without reserving a 1px flex column.
|
||||
That column exposed the green branch background at the selected item's end. */
|
||||
@@ -300,7 +296,7 @@
|
||||
background: radial-gradient(circle at bottom left, transparent calc(var(--hm-manager-join) - .5px), var(--color-bg-content) var(--hm-manager-join));
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-item[data-selected='true'][data-hm-group-end='true']::after {
|
||||
top: calc(100% - 8px);
|
||||
top: calc(100% - 6px);
|
||||
}
|
||||
/* Auxiliary status/create actions are separate from selected row geometry.
|
||||
Hide only these controls; the Story link and native group toggles stay intact. */
|
||||
|
||||
+10
-6
@@ -29,6 +29,9 @@ toolbarStyle.textContent = `
|
||||
document.head.appendChild(toolbarStyle)
|
||||
|
||||
// Sidebar display names only. Story titles, Story IDs and storySort stay English; items not listed keep item.name.
|
||||
// 2026-11-13 grouped pages: 버튼 (Button + Toggle), 선택 컨트롤 (Checkbox / Radio Group / Switch), 시트·드로어 (Sheet + Drawer),
|
||||
// 툴팁·호버 카드 (Tooltip + Hover Card), 접기·펼치기 (Accordion + Collapsible), 날짜 선택 (Date Picker + Calendar). The absorbed
|
||||
// components keep their own labels for the hidden (verification) Stories shown by the sidebar tag filter.
|
||||
const koreanLabels = {
|
||||
foundation: '기본 요소',
|
||||
components: '컴포넌트',
|
||||
@@ -43,6 +46,7 @@ const koreanLabels = {
|
||||
'components-form-input': '입력창',
|
||||
'components-form-textarea': '텍스트 영역',
|
||||
'components-form-select': '선택 목록',
|
||||
'components-form-selection-controls': '선택 컨트롤',
|
||||
'components-form-checkbox': '체크박스',
|
||||
'components-form-radio-group': '라디오 버튼',
|
||||
'components-form-switch': '스위치',
|
||||
@@ -55,12 +59,12 @@ const koreanLabels = {
|
||||
'components-navigation-menubar': '메뉴바',
|
||||
'components-overlay-dialog': '대화상자',
|
||||
'components-overlay-alert-dialog': '확인·경고 대화상자',
|
||||
'components-overlay-sheet': '시트 패널',
|
||||
'components-overlay-sheet': '시트·드로어',
|
||||
'components-overlay-drawer': '드로어',
|
||||
'components-overlay-dropdown-menu': '드롭다운 메뉴',
|
||||
'components-overlay-dropdown-menu': '팝업 메뉴',
|
||||
'components-overlay-context-menu': '컨텍스트 메뉴',
|
||||
'components-overlay-popover': '팝오버',
|
||||
'components-overlay-tooltip': '툴팁',
|
||||
'components-overlay-tooltip': '툴팁·호버 카드',
|
||||
'components-overlay-hover-card': '호버 카드',
|
||||
'components-data-display-card': '카드',
|
||||
'components-data-display-table': '테이블',
|
||||
@@ -72,7 +76,7 @@ const koreanLabels = {
|
||||
'components-data-display-aspect-ratio': '화면 비율',
|
||||
'components-data-display-carousel': '캐러셀',
|
||||
'components-data-display-scroll-area': '스크롤 영역',
|
||||
'components-disclosure-accordion': '아코디언',
|
||||
'components-disclosure-accordion': '접기·펼치기',
|
||||
'components-disclosure-collapsible': '접기·펼치기',
|
||||
'components-disclosure-resizable': '크기 조절',
|
||||
'components-search-date-command': '명령어 검색',
|
||||
@@ -86,9 +90,9 @@ const koreanLabels = {
|
||||
'patterns-data-data-table': '데이터 테이블',
|
||||
'patterns-data-filter-bar': '필터 바',
|
||||
'patterns-layout': '화면 구성',
|
||||
'patterns-layout-gnb': '전체 메뉴 (GNB)',
|
||||
'patterns-layout-gnb': '상단 메뉴',
|
||||
'patterns-layout-dashboard': '대시보드',
|
||||
'patterns-layout-lnb': '좌측 메뉴 (LNB)',
|
||||
'patterns-layout-lnb': '좌측 메뉴',
|
||||
'patterns-layout-dashboard-layout': '대시보드',
|
||||
'patterns-layout-page-header': '페이지 헤더',
|
||||
chart: '차트',
|
||||
|
||||
@@ -29,7 +29,8 @@ const preview: Preview = {
|
||||
"Components",
|
||||
[
|
||||
"Form",
|
||||
["Button", "Input", "Textarea", "Select", "Checkbox", "Radio Group", "Switch", "Slider", "Toggle", "Toggle Group", "Label"],
|
||||
// "Selection Controls" (2026-11-13) takes the place of Checkbox / Radio Group / Switch, now hidden from the sidebar.
|
||||
["Button", "Input", "Textarea", "Select", "Selection Controls", "Checkbox", "Radio Group", "Switch", "Slider", "Toggle", "Toggle Group", "Label"],
|
||||
"Navigation",
|
||||
["Tabs", "Breadcrumb", "Pagination", "Navigation Menu", "Menubar"],
|
||||
"Overlay",
|
||||
@@ -48,9 +49,9 @@ const preview: Preview = {
|
||||
"Chart",
|
||||
["Bar Chart", "Line Chart", "Donut Chart", "Pie Chart", "Radar Chart", "Area Chart", "Radial Chart"],
|
||||
"SW",
|
||||
["Header", ["SW Header", "*"], "Viewer", "Step Header", "Popup", "하단 명령어바", ["SW 하단 명령어바", "*"], "Theme"],
|
||||
["Header", ["SW Header", "*"], "Viewer", "Step Header", "Popup", "하단 명령어바", ["SW 명령어바", "*"], "Theme"],
|
||||
"조직도",
|
||||
["개요", "부서별 색상", "전체 구성", "확장 조직도"],
|
||||
["가족사 현황", "부서별 색상", "전체 구성", "확장 조직도"],
|
||||
"노선도",
|
||||
"*",
|
||||
// With includeNames, keeps the Loader group after the Runtime Contract Stories (its place without names).
|
||||
|
||||
@@ -1,19 +1,28 @@
|
||||
// Pins :hover and :active visuals for state Stories (Storybook only, never loaded by the app).
|
||||
// Every rule that uses :hover or :active is cloned right after itself with the pseudo-class swapped for a
|
||||
// Pins :hover, :active and :focus-visible visuals for state Stories (Storybook only, never loaded by the app).
|
||||
// Every rule that uses :hover, :active or :focus-visible is cloned right after itself with the pseudo-class swapped for a
|
||||
// data attribute, so cascade order and specificity stay the same. No declaration or value is authored here.
|
||||
// 2026-11-13: :focus-visible → data-sb-focus, so a Focus specimen can show its ring without holding the page focus (only one
|
||||
// element can be focused; e.g. Selection Controls focuses the Checkbox and pins the Radio Group / Switch rings).
|
||||
const HOVER = "data-sb-hover"
|
||||
const ACTIVE = "data-sb-active"
|
||||
const FOCUS = "data-sb-focus"
|
||||
const PSEUDO = /:hover\b|:active\b|:focus-visible\b/
|
||||
// A negated :focus-visible (:not(:focus-visible)) would match every element without the attribute, so such a rule keeps its
|
||||
// :focus-visible and only its :hover / :active (if any) are swapped.
|
||||
const NEGATED_FOCUS = /:not\([^)]*:focus-visible/
|
||||
const mirrored = new WeakSet<CSSStyleSheet>()
|
||||
|
||||
function mirror(list: CSSStyleSheet | CSSGroupingRule) {
|
||||
for (let i = list.cssRules.length - 1; i >= 0; i--) {
|
||||
const rule = list.cssRules[i]
|
||||
if (rule instanceof CSSStyleRule) {
|
||||
if (/:hover\b|:active\b/.test(rule.selectorText)) {
|
||||
list.insertRule(
|
||||
rule.cssText.replace(/:hover\b/g, `[${HOVER}]`).replace(/:active\b/g, `[${ACTIVE}]`),
|
||||
i + 1,
|
||||
)
|
||||
if (PSEUDO.test(rule.selectorText)) {
|
||||
// `base` keeps :focus-visible (the pre-2026-11-13 clone), so real focus + data-sb-hover / data-sb-active still matches
|
||||
// a compound rule such as :focus-visible:active (Slider Focus + Drag). `full` also swaps :focus-visible.
|
||||
const base = rule.cssText.replace(/:hover\b/g, `[${HOVER}]`).replace(/:active\b/g, `[${ACTIVE}]`)
|
||||
const full = NEGATED_FOCUS.test(rule.selectorText) ? base : base.replace(/:focus-visible\b/g, `[${FOCUS}]`)
|
||||
if (full !== base) list.insertRule(full, i + 1)
|
||||
if (base !== rule.cssText) list.insertRule(base, i + 1)
|
||||
}
|
||||
} else if ("cssRules" in rule) {
|
||||
mirror(rule as CSSGroupingRule)
|
||||
|
||||
Reference in new issue
Block a user