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
2 changes: 1 addition & 1 deletion client/src/Providers/__tests__/themeCache.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ describe('theme cache storage', () => {
* - definitions with one or both mode blocks absent.
* A new role, or a new fallback, joins by construction.
*/
const PIN = { fingerprint: '1.5.dxwdxg', digest: '1n3ywnj' };
const PIN = { fingerprint: '1.5.1ln9dy9', digest: '1ohs8yp' };

const digestOf = (text: string): string => {
let hash = 5381;
Expand Down
23 changes: 18 additions & 5 deletions client/src/components/Chat/Input/__tests__/panelRadius.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,26 @@ const PACKAGE_COMPONENTS = path.resolve(
__dirname,
'../../../../../../packages/client/src/components',
);
const PACKAGE_PANELS = ['SendActions.tsx'];
const PACKAGE_PANELS = [
'SendActions.tsx',
'TimePicker.tsx',
'ControlCombobox.tsx',
'MultiSelect.tsx',
];
const COMPONENTS_DIR = path.resolve(CHAT_DIR, '..');
const CLIENT_PANELS = [
'ui/LocalizedDateRangePicker.tsx',
'Nav/AccountSettings.tsx',
'Prompts/editor/VariablesDropdown.tsx',
'Variables/Editor.tsx',
];
const SOURCE = /\.tsx$/;
const SKIPPED = /(__tests__|\.spec\.|\.test\.)/;
const PANEL_TAG =
/<(?:Ariakit\.(?:Menu|Popover|SelectPopover|ComboboxPopover|Hovercard)|Popover\.Content)\b(?:[^\n]*>$|[\s\S]*?\n\s*\/?>)/gm;
/<(?:(?:Ariakit\.)?(?:Popover|SelectPopover|ComboboxPopover|Hovercard)|(?:Ariakit|Menu)\.Menu|(?:Popover\.)?Content)(?![.\w])(?:[^\n]*>$|[\s\S]*?\n\s*\/?>)/gm;
const PANEL_CLASS = /className="popover\b[^"]*"/g;
const PANEL_CONSTANT = /const (?:menuClasses|panelClasses|popoverClasses)\b[^;]*;/g;
const PANEL_ROLE = /\brounded-theme-(?:menu-panel|popover)\b/;
const PANEL_ROLE = /\b(?:rounded-theme-(?:menu-panel|compact-panel|popover|surface)|popover-ui)\b/;
const DELEGATED = /className=\{[A-Za-z.]+\}/;

function sourcesUnder(dir: string): string[] {
Expand All @@ -30,10 +42,11 @@ function sourcesUnder(dir: string): string[] {
});
}

describe('composer menu and popover panels', () => {
describe('menu, popover and picker panels', () => {
const files = [
...SCAN_DIRS.flatMap((dir) => sourcesUnder(path.join(CHAT_DIR, dir))),
...PACKAGE_PANELS.map((file) => path.join(PACKAGE_COMPONENTS, file)),
...CLIENT_PANELS.map((file) => path.join(COMPONENTS_DIR, file)),
];

it('finds the panels the theme roles are meant to cover', () => {
Expand All @@ -45,7 +58,7 @@ describe('composer menu and popover panels', () => {
...(text.match(PANEL_CLASS) ?? []),
];
});
expect(panels.length).toBeGreaterThanOrEqual(10);
expect(panels.length).toBeGreaterThanOrEqual(15);
});

it.each(files.map((file) => [path.relative(CHAT_DIR, file), file]))(
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/Nav/AccountSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function HelpSubmenu({
<Menu.Menu
portal
gutter={12}
className="popover-ui popover-from-left z-[126] w-[min(15.25rem,90vw)] rounded-lg"
className="popover-ui popover-from-left z-[126] w-[min(15.25rem,90vw)]"
>
{hasHelpFaq && (
<Menu.MenuItem
Expand Down Expand Up @@ -133,7 +133,7 @@ function AccountSettings({ collapsed = false }: { collapsed?: boolean }) {
</Menu.MenuButton>
<Menu.Menu
portal
className="popover-ui z-[125] w-[min(19.0625rem,90vw)] rounded-lg md:w-[min(15.25rem,90vw)]"
className="popover-ui z-[125] w-[min(19.0625rem,90vw)] md:w-[min(15.25rem,90vw)]"
style={{
transformOrigin: collapsed ? 'left bottom' : 'bottom',
translate: collapsed ? '4px 0' : '0 -4px',
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/ui/LocalizedDateRangePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export default function LocalizedDateRangePicker({
<Popover.Content
align="end"
sideOffset={6}
className="border-border-light bg-surface-primary text-text-primary z-50 w-[20rem] max-w-[calc(100vw-2rem)] rounded-lg border p-4 shadow-lg"
className="border-border-light bg-surface-primary text-text-primary rounded-theme-compact-panel z-50 w-[20rem] max-w-[calc(100vw-2rem)] border p-4 shadow-lg"
>
<div className="flex flex-col gap-4">
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
Expand Down
102 changes: 102 additions & 0 deletions e2e/specs/mock/scenarios/panel-shape.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { expect, test } from '@playwright/test';
import type { Locator, Page } from '@playwright/test';
import { clickHouseTheme } from '../../../../packages/client/src/theme/themes/clickhouse';
import { NEW_CHAT_PATH } from '../helpers';
import { probeStyle } from './style.helpers';

/**
* The account menu and its help submenu are `.popover-ui` panels, whose rule draws the menu radius
* role, so the `rounded-lg` they also carried never painted. The default theme keeps the menu
* corner they have always shown, ClickHouse brings them onto its menu corner, and a reference theme
* that names only `menuRadius` reshapes them, with no rule written for the account menu itself.
* Every expected corner is resolved by the browser from a probe.
*/
test.describe.configure({ timeout: 120_000 });
test.use({ viewport: { width: 1280, height: 800 } });

const REFERENCE_RADIUS = '3px';

const referenceTheme = {
version: 1,
name: 'menu-radius-reference',
modes: {
light: { appearance: { menuRadius: REFERENCE_RADIUS } },
dark: { appearance: { menuRadius: REFERENCE_RADIUS } },
},
};

type ThemeChoice = 'default' | 'clickhouse' | 'reference';

const definitions: Record<Exclude<ThemeChoice, 'default'>, object> = {
clickhouse: clickHouseTheme,
reference: referenceTheme,
};

async function openNewChat(page: Page, theme: ThemeChoice) {
await page.addInitScript(
(definition) => {
localStorage.removeItem('theme-colors');
localStorage.removeItem('theme-name');
if (definition === null) {
localStorage.removeItem('theme-definition');
localStorage.removeItem('theme-source');
return;
}
localStorage.setItem('theme-definition', JSON.stringify(definition));
localStorage.setItem('theme-source', 'definition');
},
theme === 'default' ? null : definitions[theme],
);
await page.goto(NEW_CHAT_PATH, { timeout: 15000 });
await expect(page.getByTestId('text-input')).toBeVisible({ timeout: 30000 });
}

const cornerOf = (locator: Locator) =>
locator.evaluate((node) => getComputedStyle(node).borderTopLeftRadius);

/** Open the account menu, then its help submenu, and read both panels' corners. */
async function accountPanelCorners(page: Page) {
await page.getByTestId('nav-user').click();
const accountMenu = page.getByRole('menu').filter({ has: page.getByTestId('nav-settings') });
await expect(accountMenu).toBeVisible();
const account = await cornerOf(accountMenu);

await page.getByRole('menuitem', { name: 'Help' }).click();
const helpMenu = page.getByRole('menu').filter({ hasNot: page.getByTestId('nav-settings') });
await expect(helpMenu).toBeVisible();
const help = await cornerOf(helpMenu);
return { account, help };
}

test.describe('account menu panel shape', () => {
test('the default theme keeps the account menu corners @scenario:default-theme-account-panels-unchanged', async ({
page,
}) => {
await openNewChat(page, 'default');
const menuCorner = await probeStyle(page, 'popover-ui', 'border-top-left-radius');
const corners = await accountPanelCorners(page);

expect(menuCorner).toBe('11.2px');
expect(corners).toEqual({ account: menuCorner, help: menuCorner });
});

test('ClickHouse brings the account menus onto its menu corner @scenario:clickhouse-account-panels-follow-menu-corner', async ({
page,
}) => {
await openNewChat(page, 'clickhouse');
const menuCorner = await probeStyle(page, 'popover-ui', 'border-top-left-radius');
const corners = await accountPanelCorners(page);

expect(menuCorner).toBe('4px');
expect(corners).toEqual({ account: menuCorner, help: menuCorner });
});

test('a theme naming only the menu radius role reshapes the account menus @scenario:reference-theme-reshapes-account-panels', async ({
page,
}) => {
await openNewChat(page, 'reference');
const corners = await accountPanelCorners(page);

expect(corners).toEqual({ account: REFERENCE_RADIUS, help: REFERENCE_RADIUS });
});
});
2 changes: 1 addition & 1 deletion packages/client/src/components/ControlCombobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ function ControlCombobox({
portal={portal}
portalElement={portalElement}
className={cn(
'border-border-light bg-surface-secondary overflow-hidden rounded-xl border shadow-lg',
'border-border-light bg-surface-secondary rounded-theme-menu-panel overflow-hidden border shadow-lg',
popoverMaxHeight != null && 'flex flex-col',
popoverClassName ?? 'animate-popover',
)}
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/components/MultiSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export default function MultiSelect<T extends string>({
finalFocus={selectRef}
className={cn(
'animate-popover z-40 flex max-h-[18.75rem]',
'flex-col overflow-auto overscroll-contain rounded-xl',
'rounded-theme-menu-panel flex-col overflow-auto overscroll-contain',
'bg-surface-secondary text-text-primary px-1.5 py-1 shadow-lg',
'border-border-light border',
'outline-hidden',
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/components/TimePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ function PickerShell({
align="start"
sideOffset={6}
className={cn(
'border-border-light bg-surface-secondary z-[999] max-w-[min(calc(100vw-1rem),var(--radix-popover-content-available-width))] rounded-xl border p-2 shadow-lg outline-hidden',
'border-border-light bg-surface-secondary rounded-theme-menu-panel z-[999] max-w-[min(calc(100vw-1rem),var(--radix-popover-content-available-width))] border p-2 shadow-lg outline-hidden',
// Same enter/exit motion as the shared Radix primitives (Combobox,
// Select, DropdownMenu): fade + zoom from the trigger edge, with Radix's
// own transform origin so the zoom grows out of wherever it was placed.
Expand Down
5 changes: 3 additions & 2 deletions packages/client/src/theme/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ Three primitives keep corners of their own outside that scale: the menu panel (`
`tabRadius` (0.185rem, through `rounded-theme-tab`). The defaults are the corners they always drew.

The composer's popovers read `popoverRadius` (1rem, `rounded-theme-popover`), the model selector's
panel `menuPanelRadius` (0.75rem, `rounded-theme-menu-panel`) and the send and stop buttons
`composerActionRadius` (a full circle, `rounded-theme-composer-action`), so a theme can bring them
panel and the form-control pickers `menuPanelRadius` (0.75rem, `rounded-theme-menu-panel`), the
date range picker `compactPanelRadius` (0.5rem, `rounded-theme-compact-panel`) and the send and
stop buttons `composerActionRadius` (a full circle, `rounded-theme-composer-action`), so a theme can bring them
onto `menuRadius` or a square corner without moving the control or surface radii. An inline code
chip in Markdown takes its weight from `inlineCodeWeight` (600).

Expand Down
1 change: 1 addition & 0 deletions packages/client/src/theme/defaults.css
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ html {
--theme-menu-radius: 0.7rem;
--theme-popover-radius: var(--theme-radius-2xl, 1rem);
--theme-menu-panel-radius: var(--theme-radius-xl, 0.75rem);
--theme-compact-panel-radius: var(--theme-radius-lg, 0.5rem);
--theme-composer-action-radius: var(--theme-round-control-radius, 9999px);
--theme-inline-code-weight: 600;
--theme-tooltip-radius: 0.275rem;
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/theme/defaults.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const stockAliases: Partial<
menuShadow: 'shadowLg',
popoverRadius: 'radius2xl',
menuPanelRadius: 'radiusXl',
compactPanelRadius: 'radiusLg',
composerActionRadius: 'roundControlRadius',
authControlRadius: 'radius2xl',
authActionHeight: 'buttonHeight',
Expand Down
8 changes: 7 additions & 1 deletion packages/client/src/theme/registry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,12 @@ describe('theme registry', () => {
name: 'corner-reference',
modes: {
light: {
appearance: { radius2xl: '3px', radiusXl: '5px', roundControlRadius: '7px' },
appearance: {
radius2xl: '3px',
radiusXl: '5px',
radiusLg: '4px',
roundControlRadius: '7px',
},
},
},
},
Expand All @@ -298,6 +303,7 @@ describe('theme registry', () => {

expect(resolved.appearance.popoverRadius).toBe('3px');
expect(resolved.appearance.menuPanelRadius).toBe('5px');
expect(resolved.appearance.compactPanelRadius).toBe('4px');
expect(resolved.appearance.composerActionRadius).toBe('7px');
});

Expand Down
5 changes: 4 additions & 1 deletion packages/client/src/theme/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ export const themeAppearanceProperties: Readonly<
menuRadius: '--theme-menu-radius',
popoverRadius: '--theme-popover-radius',
menuPanelRadius: '--theme-menu-panel-radius',
compactPanelRadius: '--theme-compact-panel-radius',
composerActionRadius: '--theme-composer-action-radius',
inlineCodeWeight: '--theme-inline-code-weight',
tooltipRadius: '--theme-tooltip-radius',
Expand Down Expand Up @@ -339,6 +340,7 @@ export const defaultAppearance: IThemeAppearance = Object.freeze({
menuRadius: '0.7rem',
popoverRadius: '1rem',
menuPanelRadius: '0.75rem',
compactPanelRadius: '0.5rem',
composerActionRadius: '9999px',
inlineCodeWeight: '600',
tooltipRadius: '0.275rem',
Expand Down Expand Up @@ -624,7 +626,7 @@ function withFloorableLengths(appearance: IThemeAppearance): IThemeAppearance {
/**
* Roles split out of a broader one, each paired with the role it read before. Headings drew the UI
* family before the display role existed, theme-sized controls were padded by the shared spacing,
* and dialog titles were set in the `text-lg` step and the display family, the composer's popovers, the model selector and the send button drew `rounded-2xl`, `rounded-xl` and the round control corner, and the sign-in controls drew `rounded-2xl` and the other sign-in actions the default Button's height and corner, and the checkbox `rounded-sm`, so a theme that names
* and dialog titles were set in the `text-lg` step and the display family, the composer's popovers, the model selector, the date range picker and the send button drew `rounded-2xl`, `rounded-xl`, `rounded-lg` and the round control corner, and the sign-in controls drew `rounded-2xl` and the other sign-in actions the default Button's height and corner, and the checkbox `rounded-sm`, so a theme that names
* the broader role and not the split one keeps what it drew. Pairs resolve in order, so a role can
* follow one that is itself inherited; the other sign-in actions resolve before the sign-in corner
* is derived, so they follow only a corner the theme names and otherwise keep `radiusLg`.
Expand All @@ -638,6 +640,7 @@ const inheritedAppearance: ReadonlyArray<[keyof IThemeAppearance, keyof IThemeAp
['dialogTitleFontFamily', 'displayFontFamily'],
['popoverRadius', 'radius2xl'],
['menuPanelRadius', 'radiusXl'],
['compactPanelRadius', 'radiusLg'],
['composerActionRadius', 'roundControlRadius'],
['authActionRadius', 'authControlRadius'],
['authActionRadius', 'radiusLg'],
Expand Down
6 changes: 6 additions & 0 deletions packages/client/src/theme/tailwind.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@ describe('LibreChat Tailwind preset', () => {
['rounded-theme-popover', '--theme-popover-radius', defaultAppearance.popoverRadius],
['rounded-theme-checkbox', '--theme-checkbox-radius', defaultAppearance.checkboxRadius],
['rounded-theme-menu-panel', '--theme-menu-panel-radius', defaultAppearance.menuPanelRadius],
[
'rounded-theme-compact-panel',
'--theme-compact-panel-radius',
defaultAppearance.compactPanelRadius,
],
[
'rounded-theme-composer-action',
'--theme-composer-action-radius',
Expand Down Expand Up @@ -330,6 +335,7 @@ describe('LibreChat Tailwind preset', () => {
menuShadow: 'shadowLg',
popoverRadius: 'radius2xl',
menuPanelRadius: 'radiusXl',
compactPanelRadius: 'radiusLg',
composerActionRadius: 'roundControlRadius',
authControlRadius: 'radius2xl',
authActionHeight: 'buttonHeight',
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/theme/themes/clickhouse.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,7 @@ describe('clickhouse theme definition', () => {
composerActionRadius: '0.25rem',
popoverRadius: appearance.menuRadius,
menuPanelRadius: appearance.menuRadius,
compactPanelRadius: appearance.menuRadius,
inlineCodeWeight: '500',
});
});
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/theme/themes/clickhouse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ const clickHouseShape = {
menuRadius: '0.25rem', // genericMenu.panel.radii.all
popoverRadius: '0.25rem', // genericMenu.panel.radii.all, the panel the composer popovers are
menuPanelRadius: '0.25rem', // genericMenu.panel.radii.all
compactPanelRadius: '0.25rem', // genericMenu.panel.radii.all
composerActionRadius: '0.25rem', // button.radii.all, Click UI draws its icon buttons square
inlineCodeWeight: '500', // typography.font.weights.2
tooltipRadius: '0.25rem', // tooltip.radii.all
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/theme/themes/clickui.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,7 @@ const appearanceSources: Partial<Record<keyof IThemeAppearance, string>> = {
menuRadius: 'click.genericMenu.panel.radii.all',
popoverRadius: 'click.genericMenu.panel.radii.all',
menuPanelRadius: 'click.genericMenu.panel.radii.all',
compactPanelRadius: 'click.genericMenu.panel.radii.all',
composerActionRadius: 'click.button.radii.all',
authControlRadius: 'click.button.radii.all',
inlineCodeWeight: 'typography.font.weights.2',
Expand Down
2 changes: 2 additions & 0 deletions packages/client/src/theme/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,8 @@ export interface IThemeAppearance {
* control or surface radii. */
popoverRadius: string;
menuPanelRadius: string;
/** The corners of the date range picker panel, which drew `rounded-lg`. */
compactPanelRadius: string;
composerActionRadius: string;
/** The weight of an inline code chip in rendered Markdown. */
inlineCodeWeight: string;
Expand Down
1 change: 1 addition & 0 deletions packages/client/tailwind.preset.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ module.exports = {
'theme-surface': 'var(--theme-surface-radius, 1rem)',
'theme-popover': 'var(--theme-popover-radius, 1rem)',
'theme-menu-panel': 'var(--theme-menu-panel-radius, 0.75rem)',
'theme-compact-panel': 'var(--theme-compact-panel-radius, 0.5rem)',
'theme-composer-action': 'var(--theme-composer-action-radius, 9999px)',
'theme-auth-control': 'var(--theme-auth-control-radius, 1rem)',
'theme-auth-action': 'var(--theme-auth-action-radius, 0.5rem)',
Expand Down
1 change: 1 addition & 0 deletions packages/data-provider/src/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ const appearanceValidators = {
menuRadius: isLength,
popoverRadius: isLength,
menuPanelRadius: isLength,
compactPanelRadius: isLength,
composerActionRadius: isLength,
inlineCodeWeight: isFontWeight,
tooltipRadius: isLength,
Expand Down
Loading