@geeksquadstudio/gs-ui
v0.1.0
Published
GS UI — design system built on Ant Design
Downloads
274
Maintainers
Readme
GS UI
A themeable React design system built on top of Ant Design v5, with Storybook as the live playground and a Figma-driven token pipeline.
Table of contents
- Why GS UI?
- Quick start
- Installation in another project
- Usage
- Theming & tokens
- Component catalog
- GS extensions (not in vanilla antd)
- Project layout
- Scripts
- Customizing a component
- Contributing
- FAQ
- License
Why GS UI?
Ant Design is a great baseline, but shipping a design system on top of it means adding your own theme, your own component variants (e.g. Masonry, solid-variant Tag, sortable Table), and a cohesive story for every component. GS UI gives you:
- ✅ One import surface — every AntD component re-exported under a single package (
gs-ui). - ✅ Theme-first — tokens exported from Figma drive the whole library. Run
npm run build:tokensto re-generate. - ✅ Light + dark out of the box —
lightTheme/darkThemewith full token coverage. - ✅ Custom extensions — components AntD doesn't ship:
Masonry,Table.EditableCell,Table.Sortable,InputNumber.Spinner,ImageCrop,solidTag variant. - ✅ Storybook as docs — every component has a Docs page with description, interactive Playground, and demo stories.
- ✅ Tree-shakable — Vite +
vite-plugin-dtsbuild to pure ESM.
Quick start
git clone https://github.com/i4BSolutions/gs-ui.git
cd gs-ui
npm install
npm run storybookStorybook opens at http://localhost:6006 — browse the full component catalog, read per-component docs, and tweak props live in the Controls panel.
Build the library
npm run build # emits ./dist (ES + .d.ts)Build a static Storybook
npm run build-storybook # emits ./storybook-static for hostingInstallation in another project
This repo currently builds to ./dist as a private library. There are three common ways to consume it from another app.
1. Local link (monorepo or side-by-side folders)
# in gs-ui
npm run build
npm link
# in your app
npm link gs-ui2. Published to a private registry
Publish from ./dist to GitHub Packages, Verdaccio, or any private npm registry, then:
npm install @geeksquadstudio/gs-ui3. Git submodule / tarball
Point your app's package.json at a git ref or a packed tarball:
{
"dependencies": {
"gs-ui": "github:i4BSolutions/gs-ui#main"
}
}Peer dependencies
Your app must provide:
| Package | Version |
|---|---|
| react | >=18 |
| react-dom | >=18 |
GS UI ships antd, @ant-design/icons, dayjs, antd-img-crop, and @dnd-kit/* as dependencies, so you don't need to install them separately.
Usage
Wrap your app root with ConfigProvider (for theme) and App (for hook-based message / notification / modal). Then import any component from gs-ui.
// src/main.tsx
import { ConfigProvider, App, theme } from '@geeksquadstudio/gs-ui';
import Dashboard from './Dashboard';
export default function Root() {
return (
<ConfigProvider theme={theme}>
<App>
<Dashboard />
</App>
</ConfigProvider>
);
}// src/Dashboard.tsx
import { Button, Form, Input, Card, App } from '@geeksquadstudio/gs-ui';
export default function Dashboard() {
const { message } = App.useApp();
return (
<Card title="New account">
<Form layout="vertical" onFinish={() => message.success('Saved')}>
<Form.Item name="email" label="Email" rules={[{ required: true, type: 'email' }]}>
<Input />
</Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
</Form>
</Card>
);
}Tip: Use
App.useApp()(not the staticmessage.success(...)) so toasts inherit yourConfigProvidertheme, prefix, and locale.
Dark mode
import { ConfigProvider, App, darkTheme } from '@geeksquadstudio/gs-ui';
<ConfigProvider theme={darkTheme}>
<App>{/* … */}</App>
</ConfigProvider>Theming & tokens
Tokens are generated from Figma variables — they're the source of truth for color, typography, spacing, motion, and per-component overrides.
The pipeline
Figma variables (exported as JSON)
│
▼
scripts/build-tokens.mjs
│
▼
src/theme/tokens.ts ← auto-generated (seed / map / alias / static / palette / components)
│
▼
src/theme/index.ts ← builds ThemeConfig (lightTheme / darkTheme)
│
▼
ConfigProvider ← applies tokens to every antd + gs-ui componentRegenerating tokens
Export the seven Figma variable collections to
~/Downloads/:seed.json,map.json,alias.json,static.json,colors.json,components.json,responsive.json.Run:
npm run build:tokensThe script overwrites
src/theme/tokens.ts— commit and open a PR.
Available exports
import {
theme, // same as lightTheme — default export for convenience
lightTheme, // full ThemeConfig with default algorithm
darkTheme, // full ThemeConfig with dark algorithm
lightToken, // merged seed+map+alias+static+palette (light)
darkToken, // same (dark)
// Raw token sets (for direct consumption)
seedLight, seedDark,
mapLight, mapDark,
aliasLight, aliasDark,
staticLight, staticDark,
paletteLight, paletteDark,
componentsLight, componentsDark,
responsive,
} from '@geeksquadstudio/gs-ui';Overriding per-app
Nest a ConfigProvider to override tokens in a subtree:
<ConfigProvider theme={{ token: { colorPrimary: '#722ed1' } }}>
<MyBrandedArea />
</ConfigProvider>See the Foundations / Tokens story in Storybook for a browsable table of every token.
Component catalog
| Category | Components |
|---|---|
| General | Button, FloatButton, Icon, Typography |
| Layout | Divider, Flex, Grid (Row, Col), Layout, Masonry, Space, Splitter |
| Navigation | Anchor, Breadcrumb, Dropdown, Menu, Pagination, Steps, Tabs |
| Data Entry | AutoComplete, Cascader, Checkbox, ColorPicker, DatePicker, Form, ImageCrop, Input, InputNumber (+ .Spinner), Mentions, Radio, Rate, Select, Slider, Switch, TimePicker, Transfer, TreeSelect, Upload |
| Data Display | Avatar, Badge, Calendar, Card, Carousel, Collapse, Descriptions, Empty, Image, List, Popover, QRCode, Segmented, Statistic, Table (+ .EditableCell, .Sortable), Tag (+ variant="solid"), Timeline, Tooltip, Tour, Tree |
| Feedback | Alert, Drawer, Message, Modal, Notification, Popconfirm, Progress, Result, Skeleton, Spin, Watermark |
| Other | Affix, App, ConfigProvider |
Every component has a Docs page in Storybook with:
- 1–2 paragraph purpose statement
- Props reference
- An interactive Playground story
- Focused demo stories (variants, sizes, states, common patterns)
- A matrix / kitchen-sink view where applicable
GS extensions (not in vanilla antd)
These ship with GS UI but are not in upstream Ant Design:
| Component | Purpose |
|---|---|
| Masonry | Pinterest-style variable-height column layout, responsive via antd's breakpoints. |
| Tag with variant="solid" | Saturated-background variant on top of antd's outlined / filled. |
| InputNumber.Spinner | Horizontal stepper layout with − / + buttons on either side. |
| Table.EditableCell | Click-to-edit cell. Enter / blur commits, Escape reverts. |
| Table.Sortable | Drag-handle column + drop-reorder rows (powered by @dnd-kit). |
| ImageCrop | Opens an "Edit image" modal (zoom / rotate / aspect) before Upload transport. Wraps antd-img-crop. |
Usage examples:
import { Masonry, InputNumber, Table, Tag, ImageCrop, Upload } from '@geeksquadstudio/gs-ui';
// Pinterest-style grid
<Masonry columns={{ xs: 1, sm: 2, md: 3, lg: 4 }} gap={16}>
{items.map(i => <Card key={i.id}>{/* … */}</Card>)}
</Masonry>
// Solid tag
<Tag variant="solid" color="red">Urgent</Tag>
// Horizontal stepper
<InputNumber.Spinner min={0} max={10} defaultValue={3} />
// Editable + sortable table
<Table.Sortable<Row>
dataSource={rows}
onSort={setRows}
rowKey="id"
columns={[
{
title: 'Name',
dataIndex: 'name',
render: (v, r) => (
<Table.EditableCell value={v} onChange={(next) => update(r.id, next)} />
),
},
]}
/>
// Crop before upload
<ImageCrop aspect={1} cropShape="round">
<Upload listType="picture-circle" maxCount={1}>
<div>+ Avatar</div>
</Upload>
</ImageCrop>Project layout
.
├── .storybook/ # Storybook config
│ ├── main.ts # stories glob, framework, addons
│ └── preview.tsx # global ConfigProvider + light/dark toolbar
│
├── src/
│ ├── components/
│ │ └── <Name>/
│ │ ├── index.tsx # re-export or wrapped antd component
│ │ └── <Name>.stories.tsx
│ │
│ ├── foundations/
│ │ └── Tokens.stories.tsx # browsable seed/map/alias/palette/components
│ │
│ ├── theme/
│ │ ├── tokens.ts # AUTO-GENERATED from Figma
│ │ └── index.ts # builds ThemeConfig (lightTheme / darkTheme)
│ │
│ ├── Introduction.stories.tsx # landing page in Storybook
│ └── index.ts # public package entry
│
├── scripts/
│ ├── build-tokens.mjs # Figma → tokens.ts pipeline
│ └── apply-docs.mjs # injects component-level Docs text
│
├── package.json
├── tsconfig.json
├── tsconfig.stories.json # includes stories in typecheck
└── vite.config.ts # library build configScripts
| Command | What it does |
|---|---|
| npm run storybook | Start Storybook dev server at http://localhost:6006. |
| npm run build-storybook | Build a static Storybook in ./storybook-static/. |
| npm run build | Build the library to ./dist/ (ESM + .d.ts). |
| npm run dev | Run a bare Vite dev server (rarely needed; prefer Storybook). |
| npm run typecheck | TypeScript check on the library code. |
| npm run build:tokens | Regenerate src/theme/tokens.ts from Figma JSONs in ~/Downloads/. |
Customizing a component
Every component folder is a drop-in seam. To give Button a variant="brand" prop, replace the re-export in src/components/Button/index.tsx:
import { Button as AntButton, type ButtonProps as AntButtonProps } from 'antd';
import React from 'react';
export type ButtonProps = AntButtonProps & { variant?: 'brand' | 'muted' };
export const Button: React.FC<ButtonProps> = ({ variant, style, ...rest }) => {
const brand = variant === 'brand' ? { boxShadow: '0 0 0 3px rgba(22,119,255,0.15)' } : {};
return <AntButton {...rest} style={{ ...brand, ...style }} />;
};- The import path (
gs-ui) stays the same. - Existing consumers keep working — you're extending, not replacing.
- Static members (
Button.Group,Form.Item,Typography.Text, etc.) are preserved.
For bigger additions — a new component not in antd — put it in a new folder under src/components/<Name>/, export it from src/index.ts, and write a <Name>.stories.tsx next to it.
Contributing
Tooling
- Node ≥ 18
- npm ≥ 9
Typical workflow
# 1. Start Storybook
npm run storybook
# 2. Edit a component
$EDITOR src/components/Button/index.tsx
# 3. Confirm types pass
npm run typecheck
# 4. Confirm the library + Storybook build
npm run build
npm run build-storybookAdding or changing a component story
- Update
src/components/<Name>/<Name>.stories.tsx. - Add a
parameters.docs.description.storyto each story. - Keep an interactive Playground at the top of the file.
- Run
npm run typecheck.
Refreshing tokens
- Export the 7 Figma variable collections to
~/Downloads/. npm run build:tokens.- Commit the regenerated
src/theme/tokens.ts.
FAQ
Why not just use Ant Design directly?
You can — GS UI is a thin opinionated layer. What it adds:
- A token pipeline so Figma stays the source of truth.
- A handful of missing components (Masonry, drag-sort Table, inline-edit cells, ImageCrop, solid Tag, spinner InputNumber).
- A Storybook you can host internally and share with designers / PMs.
- One import surface for the team to agree on.
How do I get the full AntD v5 API for <Button>?
Every GS UI component passes props through unchanged. <Button type="primary" danger loading /> works identically.
Why does message.success(...) not respect my theme?
Use the hook version — the static one reads from a global fallback that doesn't see your ConfigProvider:
import { App } from '@geeksquadstudio/gs-ui';
const { message } = App.useApp();
message.success('Saved');Where are hover / active states documented?
Those render via native CSS pseudo-classes — hover or click the component in its Matrix / States story in Storybook to see them live.
Can I use this without Storybook?
Yes — Storybook is only a dev-time dep. Your app only needs the built ./dist/.
License
MIT © i4BSolutions
Built on top of Ant Design. Token pipeline powered by Figma Variables. Drag-and-drop by @dnd-kit. Image cropping by antd-img-crop.
