Skip to content

comneed/lyra

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Lyra Design System

Lyra๋Š” OpenAI Apps SDK ๋””์ž์ธ ๊ฐ€์ด๋“œ๋ผ์ธ์„ ์ค€์ˆ˜ํ•˜๋ฉฐ, ์ ‘๊ทผ์„ฑ๊ณผ ์‚ฌ์šฉ์„ฑ์„ ์ตœ์šฐ์„ ์œผ๋กœ ํ•˜๋Š” ํ˜„๋Œ€์ ์ธ React ๊ธฐ๋ฐ˜ ๋””์ž์ธ ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค. Base UI Components๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌ์ถ•๋˜์—ˆ์œผ๋ฉฐ, ์ฒด๊ณ„์ ์ธ ๋””์ž์ธ ํ† ํฐ๊ณผ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

OpenAI Apps SDK ์ค€์ˆ˜: Lyra๋Š” OpenAI์˜ ๋””์ž์ธ ๊ฐ€์ด๋“œ๋ผ์ธ์„ ๋”ฐ๋ผ ์ผ๊ด€๋˜๊ณ  ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋‹จ์ƒ‰ ๋ฐฐ๊ฒฝ, gradient ๋ฏธ์‚ฌ์šฉ, ๋ช…ํ™•ํ•œ ๊ณ„์ธต ๊ตฌ์กฐ, ์ตœ์†Œํ•œ์˜ ์•ก์…˜ ์ œํ•œ ๋“ฑ OpenAI์˜ ๋””์ž์ธ ์ฒ ํ•™์„ ๋ฐ˜์˜ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

โœจ ์ฃผ์š” ํŠน์ง•

  • ๐Ÿค– OpenAI Apps SDK ์ค€์ˆ˜: OpenAI ๋””์ž์ธ ๊ฐ€์ด๋“œ๋ผ์ธ์„ ๋”ฐ๋ฅธ ์ผ๊ด€๋œ UX
  • ๐ŸŽจ ์ฒด๊ณ„์ ์ธ ๋””์ž์ธ ํ† ํฐ: Style Dictionary ๊ธฐ๋ฐ˜ ํ† ํฐ ์‹œ์Šคํ…œ์œผ๋กœ ์ผ๊ด€๋œ ๋””์ž์ธ ์–ธ์–ด ์ œ๊ณต
  • โ™ฟ๏ธ ์ ‘๊ทผ์„ฑ ์šฐ์„ : Base UI Components ๊ธฐ๋ฐ˜์˜ WCAG 2.1 AA ์ค€์ˆ˜ ์ปดํฌ๋„ŒํŠธ
  • ๐Ÿ“ฑ ๋ฐ˜์‘ํ˜• ๋””์ž์ธ: Polaris ๋ฐฉ์‹์˜ ๋ฏธ๋””์–ด ์ฟผ๋ฆฌ ์‹œ์Šคํ…œ์œผ๋กœ ๋ชจ๋“  ๋””๋ฐ”์ด์Šค ์ง€์›
  • ๐ŸŽญ CSS Modules: ์Šคํƒ€์ผ ์ถฉ๋Œ ์—†๋Š” ์•ˆ์ „ํ•œ ์Šค์ฝ”ํ”„ ์Šคํƒ€์ผ๋ง
  • ๐Ÿงช ์™„์ „ํ•œ ํ…Œ์ŠคํŠธ: Vitest ๊ธฐ๋ฐ˜ ์œ ๋‹› ํ…Œ์ŠคํŠธ ๋ฐ Storybook ์ธํ„ฐ๋ž™์…˜ ํ…Œ์ŠคํŠธ
  • ๐Ÿ“š ํ’๋ถ€ํ•œ ๋ฌธ์„œํ™”: Storybook์œผ๋กœ ์ž‘์„ฑ๋œ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์ปดํฌ๋„ŒํŠธ ๋ฌธ์„œ
  • ๐Ÿ”ง TypeScript: ์™„๋ฒฝํ•œ ํƒ€์ž… ์ •์˜ ์ œ๊ณต
  • ๐Ÿš€ ๋ชจ๋…ธ๋ ˆํฌ ๊ตฌ์กฐ: Turborepo ๊ธฐ๋ฐ˜ ๊ณ ์„ฑ๋Šฅ ๋นŒ๋“œ ์‹œ์Šคํ…œ

๐Ÿ—๏ธ ๋ชจ๋…ธ๋ ˆํฌ ๊ตฌ์กฐ

lyra/
โ”œโ”€โ”€ apps/
โ”‚   โ””โ”€โ”€ web/           # ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ (Vite)
โ”œโ”€โ”€ packages/
โ”‚   โ”œโ”€โ”€ design-tokens/ # ๋””์ž์ธ ํ† ํฐ ์‹œ์Šคํ…œ
โ”‚   โ”œโ”€โ”€ ui/            # UI ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ (Storybook ํฌํ•จ)
โ”‚   โ”œโ”€โ”€ eslint-config/ # ESLint ๊ณต์œ  ์„ค์ •
โ”‚   โ””โ”€โ”€ typescript-config/ # TypeScript ๊ณต์œ  ์„ค์ •
โ””โ”€โ”€ docs/              # ํ”„๋กœ์ ํŠธ ๋ฌธ์„œ

๐Ÿš€ ๋น ๋ฅธ ์‹œ์ž‘

ํ•„์ˆ˜ ์š”๊ตฌ์‚ฌํ•ญ

  • Node.js 18.x ์ด์ƒ
  • pnpm 10.x ์ด์ƒ

์„ค์น˜

# ์ €์žฅ์†Œ ํด๋ก 
git clone https://github.com/YuJM/lyra.git
cd lyra

# ์˜์กด์„ฑ ์„ค์น˜
pnpm install

๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰

# ๋ชจ๋“  ํŒจํ‚ค์ง€๋ฅผ watch ๋ชจ๋“œ๋กœ ์‹คํ–‰
pnpm dev

# Storybook ๋ฌธ์„œ ์„œ๋ฒ„ ์‹คํ–‰ (localhost:6006)
pnpm dev --filter=@lyra/ui

๋นŒ๋“œ

# ๋ชจ๋“  ํŒจํ‚ค์ง€ ๋นŒ๋“œ
pnpm build

# ํŠน์ • ํŒจํ‚ค์ง€๋งŒ ๋นŒ๋“œ
pnpm build --filter=@lyra/ui

๐Ÿ“ฆ ํŒจํ‚ค์ง€ ์ƒ์„ธ

@lyra/design-tokens

๋””์ž์ธ ์‹œ์Šคํ…œ์˜ ํ•ต์‹ฌ ํ† ํฐ์„ ๊ด€๋ฆฌํ•˜๋Š” ํŒจํ‚ค์ง€์ž…๋‹ˆ๋‹ค.

์ œ๊ณตํ•˜๋Š” ํ† ํฐ:

  • ์ƒ‰์ƒ (Color primitives)
  • ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ (Font family, size, weight, line height)
  • ๊ฐ„๊ฒฉ (Spacing scale)
  • ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ (Responsive breakpoints)
  • ๊ทธ๋ฆผ์ž (Shadow tokens)
  • ํ…Œ๋‘๋ฆฌ (Border radius, width)
  • ์• ๋‹ˆ๋ฉ”์ด์…˜ (Duration, easing)
  • Z-index (Layering system)

๊ธฐ์ˆ  ์Šคํƒ:

  • Style Dictionary (ํ† ํฐ ๋ณ€ํ™˜)
  • DTCG ํฌ๋งท ์ง€์›
  • CSS, JavaScript, JSON ํ˜•์‹ ์ถœ๋ ฅ
  • Polaris ๋ฐฉ์‹ ๋ฏธ๋””์–ด ์ฟผ๋ฆฌ ์ž๋™ ์ƒ์„ฑ

์‚ฌ์šฉ๋ฒ•:

import '@lyra/design-tokens/css';

// CSS ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉ
.element {
  color: var(--color-blue-600);
  padding: var(--spacing-4);
  font-size: var(--font-size-base);
}

@lyra/ui

Base UI Components ๊ธฐ๋ฐ˜์˜ ์ ‘๊ทผ์„ฑ ์šฐ์„  React ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.

์ œ๊ณต ์ปดํฌ๋„ŒํŠธ:

Form Components

  • Button: ๋‹ค์–‘ํ•œ variant๋ฅผ ์ง€์›ํ•˜๋Š” ๋ฒ„ํŠผ
  • Checkbox: ๋‹จ์ผ/๊ทธ๋ฃน ์ฒดํฌ๋ฐ•์Šค
  • Radio: ๋ผ๋””์˜ค ๋ฒ„ํŠผ ๋ฐ ๊ทธ๋ฃน
  • Switch: ํ† ๊ธ€ ์Šค์œ„์น˜
  • Field: ํผ ํ•„๋“œ ๊ตฌ์„ฑ ์š”์†Œ (Label, Control, Description, Error)
  • Select: ๋“œ๋กญ๋‹ค์šด ์„ ํƒ ์ปดํฌ๋„ŒํŠธ

Overlay Components

  • Dialog: ๋ชจ๋‹ฌ ๋‹ค์ด์–ผ๋กœ๊ทธ
  • Tooltip: ํˆดํŒ

๊ธฐ์ˆ  ์Šคํƒ:

  • React 19
  • Base UI Components
  • CSS Modules + PostCSS
  • Rollup (๋นŒ๋“œ ์‹œ์Šคํ…œ)
  • Vitest (ํ…Œ์ŠคํŒ…)
  • Storybook (๋ฌธ์„œํ™”)

์‚ฌ์šฉ๋ฒ•:

import { Button, Field, Select } from '@lyra/ui';
import '@lyra/ui/styles';

function App() {
  return (
    <>
      <Button variant="primary">์ œ์ถœ</Button>

      <Field.Root>
        <Field.Label>์ด๋ฉ”์ผ</Field.Label>
        <Field.Control type="email" />
        <Field.Description>๋กœ๊ทธ์ธ์— ์‚ฌ์šฉํ•  ์ด๋ฉ”์ผ์ž…๋‹ˆ๋‹ค</Field.Description>
      </Field.Root>

      <Select.Root>
        <Select.Trigger>
          <Select.Value placeholder="์„ ํƒํ•˜์„ธ์š”" />
        </Select.Trigger>
        <Select.Portal>
          <Select.Popup>
            <Select.Item value="1">์˜ต์…˜ 1</Select.Item>
            <Select.Item value="2">์˜ต์…˜ 2</Select.Item>
          </Select.Popup>
        </Select.Portal>
      </Select.Root>
    </>
  );
}

๐Ÿ› ๏ธ ๊ฐœ๋ฐœ ๊ฐ€์ด๋“œ

๋ช…๋ น์–ด

๊ฐœ๋ฐœ

pnpm dev                    # ๋ชจ๋“  ํŒจํ‚ค์ง€๋ฅผ watch ๋ชจ๋“œ๋กœ ์‹คํ–‰
pnpm dev --filter=@lyra/ui  # ํŠน์ • ํŒจํ‚ค์ง€๋งŒ ์‹คํ–‰

๋นŒ๋“œ

pnpm build                     # ๋ชจ๋“  ํŒจํ‚ค์ง€ ๋นŒ๋“œ
pnpm build --filter=@lyra/ui   # UI ํŒจํ‚ค์ง€ ๋ฐ Storybook ๋นŒ๋“œ

ํ…Œ์ŠคํŠธ

pnpm test                   # ๋ชจ๋“  ํ…Œ์ŠคํŠธ ์‹คํ–‰
pnpm test --filter=@lyra/ui # UI ํŒจํ‚ค์ง€ ํ…Œ์ŠคํŠธ๋งŒ ์‹คํ–‰
pnpm test:watch             # Watch ๋ชจ๋“œ๋กœ ํ…Œ์ŠคํŠธ

๋ฆฐํŒ…

pnpm lint                   # ๋ชจ๋“  ํŒจํ‚ค์ง€ ๋ฆฐํŒ…
pnpm lint:fix               # ๋ฆฐํŠธ ์—๋Ÿฌ ์ž๋™ ์ˆ˜์ •

ํด๋ฆฐ์—…

pnpm clean                  # node_modules ๋ฐ ๋นŒ๋“œ ๊ฒฐ๊ณผ๋ฌผ ์‚ญ์ œ

์ƒˆ ์ปดํฌ๋„ŒํŠธ ์ถ”๊ฐ€

  1. ์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ ์ƒ์„ฑ
// packages/ui/src/components/my-component/my-component.tsx
import * as BaseUI from '@base-ui-components/react/MyComponent';
import styles from './my-component.module.css';

export function MyComponent({ children, ...props }) {
  return (
    <BaseUI.Root {...props} className={styles.root}>
      {children}
    </BaseUI.Root>
  );
}
  1. ์Šคํƒ€์ผ ์ž‘์„ฑ
/* packages/ui/src/components/my-component/my-component.module.css */
.root {
  padding: var(--spacing-4);
  background: var(--color-bg-surface-default);
}
  1. ํ…Œ์ŠคํŠธ ์ž‘์„ฑ
// packages/ui/src/components/my-component/my-component.test.tsx
import { render, screen } from '@testing-library/react';
import { MyComponent } from './my-component';

describe('MyComponent', () => {
  it('renders children', () => {
    render(<MyComponent>Test</MyComponent>);
    expect(screen.getByText('Test')).toBeInTheDocument();
  });
});
  1. Storybook ์Šคํ† ๋ฆฌ ์ถ”๊ฐ€
// packages/ui/src/stories/components/my-component/my-component.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { MyComponent } from '../../../components/my-component/my-component';

const meta = {
  title: "MyComponent",
  component: MyComponent,
  tags: ["autodocs"],
} satisfies Meta<typeof MyComponent>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
  args: {
    children: "Hello World",
  },
};
  1. export ์ถ”๊ฐ€
// packages/ui/src/index.tsx
export { MyComponent } from './components/my-component/my-component';

๐ŸŽจ ๋””์ž์ธ ํ† ํฐ ์‚ฌ์šฉ ๊ฐ€์ด๋“œ

CSS์—์„œ ์‚ฌ์šฉ

.button {
  /* ์ƒ‰์ƒ ํ† ํฐ */
  color: var(--color-text-primary);
  background: var(--color-bg-primary-default);
  border-color: var(--color-border-default);

  /* ๊ฐ„๊ฒฉ ํ† ํฐ */
  padding: var(--spacing-2) var(--spacing-4);
  margin: var(--spacing-4);

  /* ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ ํ† ํฐ */
  font-family: var(--font-family-sans);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-medium);
  line-height: var(--line-height-normal);

  /* ํ…Œ๋‘๋ฆฌ ํ† ํฐ */
  border-radius: var(--border-radius-md);

  /* ์• ๋‹ˆ๋ฉ”์ด์…˜ ํ† ํฐ */
  transition-duration: var(--duration-fast);
  transition-timing-function: var(--easing-ease-in-out);
}

๋ฐ˜์‘ํ˜• ๋ฏธ๋””์–ด ์ฟผ๋ฆฌ

.container {
  width: 100%;
}

/* 640px ์ดํ•˜ (๋ชจ๋ฐ”์ผ) */
@media (--sm-down) {
  .container {
    padding: var(--spacing-2);
  }
}

/* 640px ์ด์ƒ (ํƒœ๋ธ”๋ฆฟ+) */
@media (--sm-up) {
  .container {
    padding: var(--spacing-4);
  }
}

/* 640px ~ 768px (ํƒœ๋ธ”๋ฆฟ๋งŒ) */
@media (--sm-only) {
  .container {
    padding: var(--spacing-3);
  }
}

๐Ÿงช ํ…Œ์ŠคํŒ…

์œ ๋‹› ํ…Œ์ŠคํŠธ (Vitest)

# ๋ชจ๋“  ํ…Œ์ŠคํŠธ ์‹คํ–‰
pnpm test

# Watch ๋ชจ๋“œ
pnpm test:watch

# ์ปค๋ฒ„๋ฆฌ์ง€ ๋ฆฌํฌํŠธ
pnpm test:coverage

Storybook ์ธํ„ฐ๋ž™์…˜ ํ…Œ์ŠคํŠธ

import { expect, userEvent, within } from '@storybook/test';

export const InteractionTest: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const button = canvas.getByRole('button');

    await userEvent.click(button);
    await expect(button).toHaveAttribute('aria-pressed', 'true');
  },
};

๐Ÿ“– ๋ฌธ์„œํ™”

Storybook ์‹คํ–‰

# ๊ฐœ๋ฐœ ๋ชจ๋“œ
pnpm dev --filter=@lyra/ui

# ๋นŒ๋“œ
pnpm build --filter=@lyra/ui

# Storybook๋งŒ ์‹คํ–‰
cd packages/ui && pnpm storybook

Storybook์€ http://localhost:6006 ์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

๐Ÿ”ง ๊ธฐ์ˆ  ์Šคํƒ

์ฝ”์–ด

  • React 19: UI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • TypeScript: ํƒ€์ž… ์•ˆ์ •์„ฑ
  • Base UI Components: ์ ‘๊ทผ์„ฑ ์šฐ์„  ํ—ค๋“œ๋ฆฌ์Šค ์ปดํฌ๋„ŒํŠธ

๋นŒ๋“œ ๋„๊ตฌ

  • Turborepo: ๋ชจ๋…ธ๋ ˆํฌ ๋นŒ๋“œ ์‹œ์Šคํ…œ
  • pnpm: ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ €
  • Rollup: UI ํŒจํ‚ค์ง€ ๋ฒˆ๋“ค๋Ÿฌ
  • Vite: ๊ฐœ๋ฐœ ์„œ๋ฒ„ ๋ฐ ๋นŒ๋“œ ๋„๊ตฌ

์Šคํƒ€์ผ๋ง

  • CSS Modules: ์Šค์ฝ”ํ”„ ์Šคํƒ€์ผ๋ง
  • PostCSS: CSS ๋ณ€ํ™˜
    • postcss-nesting
    • postcss-custom-media
    • postcss-mixins
    • postcss-global-data
  • Style Dictionary: ๋””์ž์ธ ํ† ํฐ ๋ณ€ํ™˜

ํ…Œ์ŠคํŒ… & ๋ฌธ์„œํ™”

  • Vitest: ์œ ๋‹› ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ
  • Testing Library: React ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŒ…
  • Storybook: ์ปดํฌ๋„ŒํŠธ ๋ฌธ์„œํ™” ๋ฐ ์ธํ„ฐ๋ž™์…˜ ํ…Œ์ŠคํŠธ
  • Chromatic: ์‹œ๊ฐ์  ํšŒ๊ท€ ํ…Œ์ŠคํŠธ

์ฝ”๋“œ ํ’ˆ์งˆ

  • ESLint: ์ฝ”๋“œ ๋ฆฐํŒ…
  • TypeScript: ์ •์  ํƒ€์ž… ๊ฒ€์‚ฌ
  • Changesets: ๋ฒ„์ „ ๊ด€๋ฆฌ ๋ฐ ์ฒด์ธ์ง€๋กœ๊ทธ

๐Ÿ“ ๋ฒ„์ „ ๊ด€๋ฆฌ

์ด ํ”„๋กœ์ ํŠธ๋Š” Changesets๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฒ„์ „์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์ฒด์ธ์ง€์…‹ ์ƒ์„ฑ

pnpm changeset
  1. ๋ณ€๊ฒฝ๋œ ํŒจํ‚ค์ง€ ์„ ํƒ
  2. ๋ฒ„์ „ ๋ฒ”ํ”„ ํƒ€์ž… ์„ ํƒ (major/minor/patch)
  3. ๋ณ€๊ฒฝ ์‚ฌํ•ญ ์š”์•ฝ ์ž‘์„ฑ

๋ฒ„์ „ ์—…๋ฐ์ดํŠธ

pnpm changeset version

ํผ๋ธ”๋ฆฌ์‹œ

pnpm release

๐Ÿค ๊ธฐ์—ฌํ•˜๊ธฐ

์ด์Šˆ์™€ ํ’€ ๋ฆฌํ€˜์ŠคํŠธ๋Š” ์–ธ์ œ๋‚˜ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค!

  1. Fork the repository
  2. Create your feature branch (git checkout -b feat/amazing-feature)
  3. Commit your changes (git commit -m 'feat: add amazing feature')
  4. Push to the branch (git push origin feat/amazing-feature)
  5. Open a Pull Request

๐Ÿ“„ ๋ผ์ด์„ ์Šค

MIT

๐Ÿ”— ๋งํฌ

About

design system for openai apps

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •