Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
100 changes: 48 additions & 52 deletions .claude/skills/click-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ alwaysApply: false

## Overview

Click UI is the official design system and component library for ClickHouse, built with React, TypeScript, and styled-components. This skill provides guidance for effectively using Click UI in development workflows.
Click UI is the official design system and component library for ClickHouse, built with React, TypeScript, and CSS Modules. This skill provides guidance for effectively using Click UI in development workflows.

**Official Documentation**: https://clickhouse.design/click-ui
**GitHub Repository**: https://github.com/ClickHouse/click-ui
Expand Down Expand Up @@ -64,7 +64,7 @@ function App() {
}
```

**Note**: Click UI uses styled-components for styling, so no CSS import is required. The styles are injected automatically via JavaScript.
**Note**: Since 0.10, Click UI ships CSS Modules that each component imports itself, so no CSS import is required. All of its styles sit in one `clickui` cascade layer: unlayered app CSS always beats them, and Tailwind's layers lose to them.

## Core Architecture Concepts

Expand Down Expand Up @@ -509,26 +509,23 @@ const [open, setOpen] = useState(false)

## Critical: Avoid Custom CSS

**NEVER write custom CSS or styled-components unless absolutely necessary.** Click UI provides components for almost every use case. When you must customize:
**NEVER write custom CSS unless absolutely necessary.** Click UI provides components for almost every use case. When you must customize:

### Hierarchy of Approaches (Best to Worst)

1. **Use existing Click UI components** - Check if a component already exists
2. **Use component props** - Most components have extensive prop options
3. **Extend with styled-components** - Only for adding theme-aware styles
3. **Extend with Tailwind classes on theme tokens** - Only for adding theme-aware styles
4. **Inline styles** - Only for one-off positioning tweaks
5. **Custom CSS** - Last resort, avoid if possible

### ❌ Common Mistake: Rebuilding Existing Components

```typescript
// WRONG - Don't rebuild what Click UI provides
const CustomDropdown = styled.div`
background: white;
border: 1px solid #ccc;
border-radius: 4px;
/* ... 50 more lines of CSS ... */
`;
const CustomDropdown = () => (
<div className="rounded border border-gray-300 bg-white">{/* ... 50 more lines ... */}</div>
);

// CORRECT - Use the Select component
<Select value={value} onSelect={setValue}>
Expand All @@ -539,33 +536,37 @@ const CustomDropdown = styled.div`

### When Extending is Necessary

If you must extend, use styled-components to add theme-aware styles:
If you must extend, add Tailwind classes that read the app's `--cui-*` theme tokens (`src/styles.css`):

```typescript
import { Container } from '@clickhouse/click-ui'
import styled from 'styled-components'
```tsx
import { Container } from '@clickhouse/click-ui';

// CORRECT - Extend with theme tokens
const SidebarContainer = styled(Container)`
background: ${({ theme }) => theme.global.color.background.default};
border-right: 1px solid ${({ theme }) => theme.global.color.stroke.default};
`

// If flex properties aren't working, you may need to add them explicitly
const FlexContainer = styled(Container)`
display: flex;
flex-direction: column;
`
function Sidebar({ children }: { children: ReactNode }) {
return (
<Container
orientation="vertical"
className="border-r border-(--cui-color-stroke-default) bg-(--cui-color-background-panel)"
>
{children}
</Container>
);
}
```

Tailwind classes can only add properties Click UI leaves unset. Its `clickui` layer comes after Tailwind's `utilities` layer, so a utility never overrides a property Click UI sets, however specific the selector: `grid` on `Tabs.TriggersList` still renders `display: flex`. To change such a property, write an unlayered rule in `src/styles.css`.

### Theme Token Reference

When extending components, use these theme tokens:
When extending components, use the app's `--cui-*` tokens from `src/styles.css`. They switch with the light and dark theme:

- `--cui-color-background-default` - Main background
- `--cui-color-background-muted` - Muted/secondary background
- `--cui-color-background-panel` - Panels and cards
- `--cui-color-stroke-default` - Borders
- `--cui-color-text-default` and `--cui-color-text-muted` - Text

- `theme.global.color.background.default` - Main background
- `theme.global.color.background.muted` - Muted/secondary background
- `theme.global.color.stroke.default` - Borders
- `theme.click.sidebar.main.color.background.default` - Sidebar background
Click UI's own tokens are CSS variables named after its theme paths, such as `--click-global-color-background-default` and `--click-sidebar-main-color-background-default`.

## Component Selection Guide

Expand Down Expand Up @@ -714,28 +715,23 @@ Tabs require specific sub-components:
</Tabs>
```

For styled tabs (like in sidebars), extend the components:
For styled tabs (like in sidebars), add classes to the parts. `Tabs.TriggersList` is already `display: flex` in Click UI's layer, so split it evenly with `flex-1` on each trigger rather than a Tailwind grid:

```typescript
const TabsStyled = styled(Tabs)`
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
`

const TabsTriggersList = styled(Tabs.TriggersList)`
display: grid;
grid-template-columns: 1fr 1fr;
min-height: 50px;
`

const TabContent = styled(Tabs.Content)`
display: flex;
flex-direction: column;
&[data-state='active'] {
flex: 1;
overflow: hidden auto;
}
`
```tsx
<Tabs className="flex flex-1 flex-col overflow-hidden">
<Tabs.TriggersList className="min-h-12.5">
<Tabs.Trigger value="tables" className="flex-1">
Tables
</Tabs.Trigger>
<Tabs.Trigger value="queries" className="flex-1">
Queries
</Tabs.Trigger>
</Tabs.TriggersList>
<Tabs.Content
value="tables"
className="flex flex-col data-[state=active]:flex-1 data-[state=active]:overflow-y-auto"
>
{/* Tables content */}
</Tabs.Content>
</Tabs>
```
42 changes: 37 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ SESSION_SECRET=
# The endpoint returns 401 if this is unset or the token does not match.
# ADMIN_PANEL_METRICS_SECRET=

# How often, in milliseconds, the server samples event-loop delay for the
# nodejs_eventloop_lag_{min,max,mean,stddev,p50,p90,p99}_seconds metrics.
# prom-client's own default of 10 wakes an idle server about 100 times a second;
# lower values give finer percentiles at that cost. A whole number from 1 to
# 2147483647; anything else falls back to the default of 1000.
# ADMIN_PANEL_METRICS_EVENT_LOOP_PRECISION_MS=1000

# URL base path for serving the admin panel under a subpath (e.g., /adminpanel).
# Must match at build time and runtime. Leave unset for root (/).
# VITE_BASE_PATH=/adminpanel
Expand All @@ -21,13 +28,15 @@ SESSION_SECRET=
# Useful when the server reaches LibreChat on a different URL than the browser.
# API_SERVER_URL=http://localhost:3080

# Force SSO-only login (hides the email/password form)
# Force SSO-only login (hides the email/password form). With exactly one SSO
# provider (OpenID, Google or SAML) available in LibreChat, sign-in goes
# straight to it; with several, the admin picks one.
# ADMIN_SSO_ONLY=false

# Hide the SSO login button while keeping email/password login enabled.
# Set to false to suppress the admin-panel SSO button (and SSO auto-redirect)
# even when LibreChat has OpenID configured. Takes precedence over ADMIN_SSO_ONLY.
# Defaults to enabled.
# Hide the SSO login buttons while keeping email/password login enabled.
# Set to false to suppress the admin-panel SSO buttons (and SSO auto-redirect)
# even when LibreChat has OpenID, Google or SAML configured. Takes precedence
# over ADMIN_SSO_ONLY. Defaults to enabled.
# ADMIN_SSO_ENABLED=true

# Session idle timeout in milliseconds (default: 30 minutes)
Expand All @@ -38,6 +47,29 @@ SESSION_SECRET=
# Set to false only for HTTP-only deployments where browsers drop Secure cookies.
# SESSION_COOKIE_SECURE=false

# Extra browser-facing origins allowed to call the panel's server functions,
# comma-separated. Server functions answer 403 to requests the browser did not
# make from the panel's own origin. Over HTTPS (or on localhost) browsers state
# that directly; over plain HTTP the panel compares Origin/Referer with the
# Host it received, so list the public origin here when a reverse proxy
# rewrites Host or drops a non-default port (or terminates TLS for browsers
# without Sec-Fetch-Site support).
# ADMIN_PANEL_TRUSTED_ORIGINS=http://admin.example.com:8080,http://10.0.0.5:3000

# Request log. Each request is logged as "[req] GET /path" when it arrives and
# "[req] GET /path 200 12ms" once it is answered (paths only, no query strings,
# at most 200 requests per 10s). An arrival with no completion line names a
# request the process died holding. A request the client abandons before it is
# answered completes as 499; one it leaves mid-response ends in "client-closed".
# Set to false to turn the request log off.
# ADMIN_PANEL_REQUEST_LOG=true

# Memory watermarks. Every 10s the panel compares its resident memory (RSS) with
# these thresholds in MiB and logs "[mem] rss=391Mi heapUsed=213Mi (crossed 384Mi)"
# each time it climbs past one. A threshold is logged again only after RSS
# drops back below it.
# ADMIN_PANEL_MEMORY_LOG_THRESHOLDS_MB=256,384,448

# ── Cache Control ────────────────────────────────────────────
# These mirror LibreChat's cache env vars. ADMIN_PANEL_* variants
# take precedence, falling back to the shared LibreChat equivalents.
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- uses: oven-sh/setup-bun@v2

Expand All @@ -30,7 +30,7 @@ jobs:
name: Type Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- uses: oven-sh/setup-bun@v2

Expand All @@ -44,7 +44,7 @@ jobs:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- uses: oven-sh/setup-bun@v2

Expand Down
44 changes: 44 additions & 0 deletions .github/workflows/dependency-graph.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Dependency Graph

on:
workflow_dispatch:
push:
branches:
- main
paths:
- 'bun.lock'
- 'package.json'
- 'scripts/dependencies.ts'
- '.github/workflows/dependency-graph.yml'

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: write

jobs:
submit:
name: Submit bun.lock
runs-on: ubuntu-latest
timeout-minutes: 5

steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false

- name: Set up Bun
uses: oven-sh/setup-bun@v2

- name: Build the snapshot
run: bun scripts/dependencies.ts > "$RUNNER_TEMP/snapshot.json"

- name: Submit the snapshot
env:
GH_TOKEN: ${{ github.token }}
run: |
gh api --method POST "repos/$GITHUB_REPOSITORY/dependency-graph/snapshots" \
--input "$RUNNER_TEMP/snapshot.json" --jq '"\(.result) \(.message) (snapshot \(.id))"'
Loading