Files
openchamber/packages/ui/src/lib/typography.ts
T
Bohdan Triapitsyn 47c943b487 feat(ui): redesign workspace shell with context panel, tabbed sidebars, and faster diff UX (#433)
* feat: tabbed right sidebar, context panel, floating diff comments

* fix: auto-close left sidebar when context panel opens

- Increase default context panel width from 520 to 600 pixels
- Increase sidebar minimum width from 200 to 300 pixels
- Replace collapsible component with custom button in diff view

* refactoring: rework sidebars, tabs, and file tree layout

- Rewrite AnimatedTabs as segment-style with sliding indicator
- Upgrade SidebarFilesTree to match FilesView features (context menus,
  git status, file icons, CRUD dialogs, fuzzy search ranking)
- Restructure FilesView header: tabs row + actions row, remove breadcrumbs
- Show relative path in context panel header, track active tab
- Allow left sidebar to stay open alongside context panel
- Hide diff/files tabs from header on desktop (mobile-only)
- Move chevron after group name in session sidebar
- Compact tab heights in right sidebar and git view
- Size PreviewToggleButton to match other action buttons
- Remove directory loading spinner from folder icons

* feat: add project icon and color customization

- Enable users to assign custom icons to projects
- Allow users to choose accent colors for projects
- Stabilize repo status UI during project switching

* feat: add scroll fade indicators to editor tabs

* style: reduce spacing and icon sizes in header

* style: adjust tab component padding from uniform to vertical-horizontal

* feat: Add session state indicators to project tabs

* feat: Enhance session status handling and improve UI responsiveness

* fix: preserve upstream tracking on branch rename

* fix: improve initial remote selection for pull requests

- Uses saved remote name from previous session when available
- Selects remote based on tracking branch when possible
- Falls back to origin or first available remote

* perf(diff): faster highlight, stable stacked scroll

- split/unified Pierre worker pools; prefer shiki-wasm
- align diff CSS line-height; disable scroll anchoring; drop WebKit compositing hacks
- harden stacked pin/align (cancel on user scroll/input); prevent overscroll
- make overlay scrollbar MutationObserver optional; disable for diff container

* feat: handle binary files in diff view

* fix: adjust project tabs layout and drag regions

* style: update drag overlay visual styling

* feat: enable number keys to switch projects in the sidebar

* fix: recognize octet-stream as text-based MIME type

* feat: add keyboard navigation to context panel

* feat: add session pinning to sidebar

- Pin important sessions to keep them at the top
- Pinned sessions persist across browser sessions

* refactor: move context usage display from chat input to header
2026-02-16 14:15:19 +02:00

358 lines
10 KiB
TypeScript

export const SEMANTIC_TYPOGRAPHY = {
markdown: '0.9375rem',
code: '0.8125rem',
uiHeader: '0.9375rem',
uiLabel: '0.8750rem',
meta: '0.875rem',
micro: '0.875rem',
} as const;
export const FONT_SIZE_SCALES = {
small: {
markdown: '0.875rem',
code: '0.8125rem',
uiHeader: '0.875rem',
uiLabel: '0.8125rem',
meta: '0.8125rem',
micro: '0.75rem',
},
medium: SEMANTIC_TYPOGRAPHY,
large: {
markdown: '1rem',
code: '0.9375rem',
uiHeader: '1rem',
uiLabel: '0.9375rem',
meta: '0.9375rem',
micro: '0.9375rem',
},
} as const;
export type FontSizeOption = keyof typeof FONT_SIZE_SCALES;
export const VSCODE_TYPOGRAPHY = {
// Keep VS Code webview typography slightly tighter; VS Code UI chrome already provides density.
markdown: '0.9063rem',
code: '0.8750rem',
uiHeader: '0.9063rem',
uiLabel: '0.8438rem',
meta: '0.8438rem',
micro: '0.7813rem',
} as const;
export const SEMANTIC_TYPOGRAPHY_CSS = {
'--text-markdown': SEMANTIC_TYPOGRAPHY.markdown,
'--text-code': SEMANTIC_TYPOGRAPHY.code,
'--text-ui-header': SEMANTIC_TYPOGRAPHY.uiHeader,
'--text-ui-label': SEMANTIC_TYPOGRAPHY.uiLabel,
'--text-meta': SEMANTIC_TYPOGRAPHY.meta,
'--text-micro': SEMANTIC_TYPOGRAPHY.micro,
} as const;
export const TYPOGRAPHY_CLASSES = {
markdown: 'typography-markdown',
code: 'typography-code',
uiHeader: 'typography-ui-header',
uiLabel: 'typography-ui-label',
meta: 'typography-meta',
micro: 'typography-micro',
} as const;
export type SemanticTypographyKey = keyof typeof SEMANTIC_TYPOGRAPHY;
export type TypographyClassKey = keyof typeof TYPOGRAPHY_CLASSES;
export function getTypographyVariable(key: SemanticTypographyKey): string {
return `--text-${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`;
}
export function getTypographyClass(key: TypographyClassKey): string {
return TYPOGRAPHY_CLASSES[key];
}
export const typography = {
semanticMarkdown: {
fontSize: 'var(--text-markdown)',
},
semanticCode: {
fontSize: 'var(--text-code)',
},
uiHeader: {
fontSize: 'var(--text-ui-header)',
},
uiLabel: {
fontSize: 'var(--text-ui-label)',
},
meta: {
fontSize: 'var(--text-meta)',
},
micro: {
fontSize: 'var(--text-micro)',
},
ui: {
button: {
fontSize: 'var(--text-ui-label)',
lineHeight: 'var(--ui-button-line-height)',
letterSpacing: 'var(--ui-button-letter-spacing)',
fontWeight: 'var(--ui-button-font-weight)',
},
buttonSmall: {
fontSize: 'var(--text-meta)',
lineHeight: 'var(--ui-button-small-line-height)',
letterSpacing: 'var(--ui-button-small-letter-spacing)',
fontWeight: 'var(--ui-button-small-font-weight)',
},
buttonLarge: {
fontSize: 'var(--text-ui-label)',
lineHeight: 'var(--ui-button-large-line-height)',
letterSpacing: 'var(--ui-button-large-letter-spacing)',
fontWeight: 'var(--ui-button-large-font-weight)',
},
label: {
fontSize: 'var(--text-meta)',
lineHeight: 'var(--ui-label-line-height)',
letterSpacing: 'var(--ui-label-letter-spacing)',
fontWeight: 'var(--ui-label-font-weight)',
},
caption: {
fontSize: 'var(--text-micro)',
lineHeight: 'var(--ui-caption-line-height)',
letterSpacing: 'var(--ui-caption-letter-spacing)',
fontWeight: 'var(--ui-caption-font-weight)',
},
badge: {
fontSize: 'var(--text-micro)',
lineHeight: 'var(--ui-badge-line-height)',
letterSpacing: 'var(--ui-badge-letter-spacing)',
fontWeight: 'var(--ui-badge-font-weight)',
},
tooltip: {
fontSize: 'var(--text-micro)',
lineHeight: 'var(--ui-tooltip-line-height)',
letterSpacing: 'var(--ui-tooltip-letter-spacing)',
fontWeight: 'var(--ui-tooltip-font-weight)',
},
input: {
fontSize: 'var(--text-ui-label)',
lineHeight: 'var(--ui-input-line-height)',
letterSpacing: 'var(--ui-input-letter-spacing)',
fontWeight: 'var(--ui-input-font-weight)',
},
helperText: {
fontSize: 'var(--text-meta)',
lineHeight: 'var(--ui-helper-text-line-height)',
letterSpacing: 'var(--ui-helper-text-letter-spacing)',
fontWeight: 'var(--ui-helper-text-font-weight)',
},
},
code: {
inline: {
fontSize: 'var(--text-code)',
lineHeight: 'var(--code-inline-line-height)',
letterSpacing: 'var(--code-inline-letter-spacing)',
fontWeight: 'var(--code-inline-font-weight)',
},
block: {
fontSize: 'var(--text-code)',
lineHeight: 'var(--code-block-line-height)',
letterSpacing: 'var(--code-block-letter-spacing)',
fontWeight: 'var(--code-block-font-weight)',
},
lineNumbers: {
fontSize: 'var(--text-micro)',
lineHeight: 'var(--code-line-numbers-line-height)',
letterSpacing: 'var(--code-line-numbers-letter-spacing)',
fontWeight: 'var(--code-line-numbers-font-weight)',
},
},
markdown: {
body: {
fontSize: 'var(--text-markdown)',
lineHeight: 'var(--markdown-body-line-height)',
letterSpacing: 'var(--markdown-body-letter-spacing)',
fontWeight: 'var(--markdown-body-font-weight)',
},
bodySmall: {
fontSize: 'var(--text-meta)',
lineHeight: 'var(--markdown-body-small-line-height)',
letterSpacing: 'var(--markdown-body-small-letter-spacing)',
fontWeight: 'var(--markdown-body-small-font-weight)',
},
bodyLarge: {
fontSize: 'var(--text-markdown)',
lineHeight: 'var(--markdown-body-large-line-height)',
letterSpacing: 'var(--markdown-body-large-letter-spacing)',
fontWeight: 'var(--markdown-body-large-font-weight)',
},
blockquote: {
fontSize: 'var(--text-markdown)',
lineHeight: 'var(--markdown-blockquote-line-height)',
letterSpacing: 'var(--markdown-blockquote-letter-spacing)',
fontWeight: 'var(--markdown-blockquote-font-weight)',
},
list: {
fontSize: 'var(--text-markdown)',
lineHeight: 'var(--markdown-list-line-height)',
letterSpacing: 'var(--markdown-list-letter-spacing)',
fontWeight: 'var(--markdown-list-font-weight)',
},
link: {
fontSize: 'var(--text-markdown)',
lineHeight: 'var(--markdown-link-line-height)',
letterSpacing: 'var(--markdown-link-letter-spacing)',
fontWeight: 'var(--markdown-link-font-weight)',
},
code: {
fontSize: 'var(--text-code)',
lineHeight: 'var(--markdown-code-line-height)',
letterSpacing: 'var(--markdown-code-letter-spacing)',
fontWeight: 'var(--markdown-code-font-weight)',
},
codeBlock: {
fontSize: 'var(--text-code)',
lineHeight: 'var(--markdown-code-block-line-height)',
letterSpacing: 'var(--markdown-code-block-letter-spacing)',
fontWeight: 'var(--markdown-code-block-font-weight)',
},
},
tool: {
collapsed: {
fontSize: 'var(--text-code)',
lineHeight: 'var(--code-block-line-height)',
letterSpacing: 'var(--code-block-letter-spacing)',
fontWeight: 'var(--code-block-font-weight)',
},
popup: {
fontSize: 'var(--text-code)',
lineHeight: 'var(--code-block-line-height)',
letterSpacing: 'var(--code-block-letter-spacing)',
fontWeight: 'var(--code-block-font-weight)',
},
inline: {
fontSize: 'var(--text-code)',
lineHeight: 'var(--code-inline-line-height)',
letterSpacing: 'var(--code-inline-letter-spacing)',
fontWeight: 'var(--code-inline-font-weight)',
},
},
};
export function getTypographyStyle(path: string, fallback?: React.CSSProperties): React.CSSProperties {
const parts = path.split('.');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let current: any = typography;
for (const part of parts) {
if (current && current[part]) {
current = current[part];
} else {
return fallback || {};
}
}
return current || fallback || {};
}
export const toolDisplayStyles = {
padding: {
collapsed: '0.375rem',
popup: '0.75rem',
popupContainer: '1rem',
},
backgroundOpacity: {
muted: '30',
mutedAlt: '50',
},
getCollapsedStyles: () => ({
...typography.tool.collapsed,
background: 'transparent',
backgroundColor: 'transparent',
margin: 0,
padding: toolDisplayStyles.padding.collapsed,
borderRadius: 0,
}),
getPopupStyles: () => ({
...typography.tool.popup,
background: 'transparent',
backgroundColor: 'transparent',
margin: 0,
padding: toolDisplayStyles.padding.popup,
borderRadius: '0.75rem',
}),
getPopupContainerStyles: () => ({
...typography.tool.popup,
background: 'transparent',
backgroundColor: 'transparent',
margin: 0,
padding: toolDisplayStyles.padding.popupContainer,
borderRadius: '0.5rem',
overflowX: 'auto' as const,
}),
getInlineStyles: () => ({
...typography.tool.inline,
}),
};
export const typographyClasses = {
'heading-1': 'typography-h1',
'heading-2': 'typography-h2',
'heading-3': 'typography-h3',
'heading-4': 'typography-h4',
'heading-5': 'typography-h5',
'heading-6': 'typography-h6',
'ui-button': 'typography-ui-button',
'ui-button-small': 'typography-ui-button-small',
'ui-button-large': 'typography-ui-button-large',
'ui-label': 'typography-ui-label',
'ui-caption': 'typography-ui-caption',
'ui-badge': 'typography-ui-badge',
'ui-tooltip': 'typography-ui-tooltip',
'ui-input': 'typography-ui-input',
'ui-helper': 'typography-ui-helper-text',
'code-inline': 'typography-code-inline',
'code-block': 'typography-code-block',
'code-line-numbers': 'typography-code-line-numbers',
'markdown-h1': 'typography-markdown-h1',
'markdown-h2': 'typography-markdown-h2',
'markdown-h3': 'typography-markdown-h3',
'markdown-h4': 'typography-markdown-h4',
'markdown-h5': 'typography-markdown-h5',
'markdown-h6': 'typography-markdown-h6',
'markdown-body': 'typography-markdown-body',
'markdown-body-small': 'typography-markdown-body-small',
'markdown-body-large': 'typography-markdown-body-large',
'markdown-blockquote': 'typography-markdown-blockquote',
'markdown-list': 'typography-markdown-list',
'markdown-link': 'typography-markdown-link',
'markdown-code': 'typography-markdown-code',
'markdown-code-block': 'typography-markdown-code-block',
'semantic-markdown': 'typography-markdown',
'semantic-code': 'typography-code',
'semantic-ui-header': 'typography-ui-header',
'semantic-ui-label': 'typography-ui-label',
'semantic-meta': 'typography-meta',
'semantic-micro': 'typography-micro',
};