Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

85 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿค– RobotPrinter

English | ไธญๆ–‡

ไธ€ไธชๆœ‰่ถฃ็š„ React ็ป„ไปถ โ€”โ€” ๆœบๅ™จไบบๅ็บธๆก๏ผ

ๅฏ็”จไบŽ AI ๅฏน่ฏใ€ๅฟซๆทๆŒ‡ไปคใ€ๆ•ฐๆฎๅค„็†ไธญ้—ดไปถ็ญ‰ๅœบๆ™ฏ๏ผŒ่‡ชๅธฆ็‹ฌ็‰น็š„ๅŠจ็”ปๆ•ˆๆžœๅ’Œไบคไบ’ไฝ“้ชŒใ€‚

ๅฑ•็คบ้กต๏ผšai-island.boat2moon.com

firefox_rtt8vASilo.mp4

ไธ€ไธชๅœจTiatapๆ™บ่ƒฝๆ–‡ๆกฃ้กน็›ฎไธญ็š„ๅบ”็”จ๏ผšrumuai.top

firefox_dkJvNHpn7s.mp4

โœจ ็‰นๆ€ง

  • ๐ŸŽจ ็‹ฌ็‰น่ง†่ง‰้ฃŽๆ ผ - 3D ๆœบๅ™จไบบๅคด้ƒจ + ๅฏๅฑ•ๅผ€็š„ๅคๅค็บธๆก
  • ๐Ÿ–ฑ๏ธ ๅฏๆ‹–ๆ‹ฝ - ๆ”ฏๆŒๅœจ้กต้ขไปปๆ„ไฝ็ฝฎๆ‹–ๅŠจ
  • ๐Ÿ“ ๆ™บ่ƒฝไบคไบ’ - ่พ“ๅ…ฅๆก†ๆ”ฏๆŒ Enter ๆไบค๏ผŒๅณไพง้™„ๅธฆๅ›ž่ฝฆๅ›พๆ ‡๏ผŒๅŠ ่ฝฝๆ—ถ่‡ชๅŠจ้”ๅฎš
  • ๐Ÿ›‘ ็›ด่ง‚็Šถๆ€ - ็œผ็›ๆ˜พ็คบๅŠ ่ฝฝ่„‰ๅ†ฒ/ๅ€’่ฎกๆ—ถ๏ผŒๅคฉ็บฟๅ˜่บซ็ปฟ่‰ฒไธญๆญขๆŒ‰้’ฎ
  • ๐Ÿ˜ด ๆ™บ่ƒฝ็ก็œ  - ๆทฑ่‰ฒๆจกๅผๆˆ–้•ฟๆ—ถ้—ดๆ— ไบคไบ’่‡ชๅŠจ้—ญ็œผไผ‘ๆฏ
  • ๐Ÿ”— ๅค–้ƒจๆŽงๅˆถ - ๆ”ฏๆŒ้€š่ฟ‡ expanded prop ๅค–้ƒจๆŽงๅˆถๅฑ•ๅผ€/ๆ”ถ่ตท
  • ๐ŸŽฏ ๅŠŸ่ƒฝๆŒ‰้’ฎ - ๅฏ้…็ฝฎ็š„ๆ“ไฝœๆŒ‰้’ฎ๏ผŒๆ”ฏๆŒๆ‚ฌๅœๅฑ•ๅผ€ๅญ่œๅ•
  • ๐Ÿ“‹ ็ป“ๆžœ้ขๆฟ - ๆ˜พ็คบๅค„็†็ป“ๆžœ๏ผŒๆ”ฏๆŒๆ›ฟๆข/ๆ’ๅ…ฅ็ญ‰ๆ“ไฝœ
  • ๐Ÿ’ฌ ๅบ•้ƒจๆ็คบ - ๅฏ่‡ชๅฎšไน‰็š„ๆ็คบไฟกๆฏๅŒบๅŸŸ
  • ๐Ÿ”ง ้ซ˜ๅบฆๅฏ้…็ฝฎ - ๆ‰€ๆœ‰ๅŠŸ่ƒฝ้€š่ฟ‡ props ๆณจๅ…ฅ๏ผŒๆ— ไธšๅŠก้€ป่พ‘่€ฆๅˆ
  • ๐Ÿ’Ž Glass Mode - ๆไพ›ๆฏ›็Žป็’ƒ (Glassmorphism) ้ฃŽๆ ผ้€‰้กน๏ผŒ็Žฐไปฃๆ„Ÿๅ่ถณ

    โš ๏ธ ๅทฒ็Ÿฅ้—ฎ้ข˜: Glass Mode ๅœจ Chrome/Edge ไธ‹ๅฝ•ๅฑๆ—ถๅฏ่ƒฝๅ‡บ็Žฐ้—ช็ƒ๏ผˆ่‚‰็œผไธๅฏ่ง๏ผŒไฝ†ๅฝ•ๅฑไผšๆ˜พ็คบ๏ผ‰ใ€‚่ฟ™ๆ˜ฏ Chromium ๆธฒๆŸ“ๅผ•ๆ“Ž็š„ ๅทฒ็Ÿฅ Bug๏ผŒๅœจ Firefox ๅ’Œ Safari ไธŠๅฎŒๅ…จๆญฃๅธธใ€‚่ฏฆ่งๆŠ€ๆœฏๅˆ†ๆžใ€‚

  • ๐Ÿ“ ่‡ช้€‚ๅบ”ๅธƒๅฑ€ - ็ป“ๆžœ้ขๆฟ่‡ชๅŠจ้ฟ่ฎฉ้ฎๆŒก๏ผŒไฝ็ฝฎๅ‚ๆ•ฐๅฎŒๅ…จๅฏ้…

๐Ÿ”ง ่ฟ›้˜ถ้…็ฝฎ

ๅœจ src/components/RobotPrinter/ResultPanel.tsx ๆ–‡ไปถ้กถ้ƒจ๏ผŒไฝ ๅฏไปฅๆ นๆฎ้œ€่ฆ่ฐƒๆ•ดๅธƒๅฑ€ๅธธ้‡๏ผš

// --- Glass ๆจกๅผๅ‚ๆ•ฐ ---
const GLASS_GAP_TOP = 35;
const GLASS_GAP_BOTTOM = 45;
const GLASS_OFFSET_LEFT = 30;
const GLASS_OFFSET_RIGHT = 30;

// --- Default ๆจกๅผๅ‚ๆ•ฐ ---
const DEFAULT_GAP_TOP = 65;
const DEFAULT_GAP_BOTTOM = 65;
// ...

๐Ÿ› ๏ธ ๆŠ€ๆœฏๆ ˆ

  • React 19+ (Hooks)
  • TypeScript 5.9+ (ไธฅๆ ผๆจกๅผ)
  • Vite 7.x (ๅผ€ๅ‘/ๆž„ๅปบ)
  • Vitest (ๅ•ๅ…ƒๆต‹่ฏ•๏ผŒ114+ ๆต‹่ฏ•็”จไพ‹)
  • CSS Variables (ไธป้ข˜ๅŒ–)
  • ESLint + Prettier (ไปฃ็ ่ง„่Œƒ)
  • Husky + Commitlint (Git Hooks + ๆไบค่ง„่Œƒ)
  • GitHub Actions (CI/CD)
  • sonner (Toast ้€š็Ÿฅ๏ผŒDemo ไฝฟ็”จ)

๐Ÿ“ฆ ๅฎ‰่ฃ…

# ๅฐ†็ป„ไปถๆ–‡ไปถๅคนๅคๅˆถๅˆฐไฝ ็š„้กน็›ฎไธญ
cp -r src/components/RobotPrinter your-project/src/components/

ๅผ€ๅ‘ Demo

# ๅ…‹้š†ไป“ๅบ“
git clone https://github.com/boat2moon/RobotPrinter.git
cd RobotPrinter

# ๅฎ‰่ฃ…ไพ่ต–
npm install

# ๅฏๅŠจๅผ€ๅ‘ๆœๅŠกๅ™จ
npm run dev

# ่ฟ่กŒๆต‹่ฏ•
npm run test

# ๆต‹่ฏ•่ฆ†็›–็އ
npm run test:coverage

# ็ฑปๅž‹ๆฃ€ๆŸฅ
npm run type-check

# ๆž„ๅปบ็”Ÿไบง็‰ˆๆœฌ
npm run build

# ๆž„ๅปบ็ป„ไปถๅบ“ (็”จไบŽ npm ๅ‘ๅธƒ)
npm run build:lib

# ๆž„ๅปบๅนถ็”Ÿๆˆ Bundle ๅˆ†ๆžๆŠฅๅ‘Š
npm run build:lib:analyze

๐Ÿš€ ๅฟซ้€Ÿๅผ€ๅง‹

import { RobotPrinter } from './components/RobotPrinter';

function App() {
  const handleSubmit = (value: string) => {
    console.log('็”จๆˆท่พ“ๅ…ฅ:', value);
    // ่ฐƒ็”จไฝ ็š„ AI API...
  };

  return <RobotPrinter placeholder="่พ“ๅ…ฅๆŒ‡ไปค..." onSubmit={handleSubmit} />;
}

ไฝฟ็”จ้ข„่ฎพ้…็ฝฎ

ๅ†…็ฝฎๅคš็งๅœบๆ™ฏ้ข„่ฎพ๏ผŒๅผ€็ฎฑๅณ็”จ๏ผš

import { RobotPrinter, chatPreset, darkPreset, compactPreset } from './components/RobotPrinter';

// AI ่Šๅคฉๅœบๆ™ฏ
<RobotPrinter {...chatPreset} onSubmit={handleSubmit} />

// ๆทฑ่‰ฒไธป้ข˜
<RobotPrinter {...darkPreset} onSubmit={handleSubmit} />

// ็ดงๅ‡‘ๆจกๅผ
<RobotPrinter {...compactPreset} onSubmit={handleSubmit} />

ๅฏ็”จ้ข„่ฎพ๏ผšcompactPreset | widePreset | chatPreset | darkPreset | demoPreset | minimalPreset | floatingPreset | loadingPreset

้”™่ฏฏ่พน็•ŒๅŒ…่ฃ…

ๆŽจ่ไฝฟ็”จ้”™่ฏฏ่พน็•ŒๅŒ…่ฃ…็ป„ไปถ๏ผŒ้˜ฒๆญขๆธฒๆŸ“้”™่ฏฏๅฏผ่‡ด้กต้ขๅดฉๆบƒ๏ผš

import { RobotPrinterErrorBoundary, RobotPrinter } from './components/RobotPrinter';

<RobotPrinterErrorBoundary
  fallback={(error, reset) => (
    <div>
      <p>็ป„ไปถๅ‡บ้”™: {error.message}</p>
      <button onClick={reset}>้‡่ฏ•</button>
    </div>
  )}
>
  <RobotPrinter />
</RobotPrinterErrorBoundary>;

## ๐Ÿ“– API ๆ–‡ๆกฃ

### ๅŸบ็ก€้…็ฝฎ

| ๅฑžๆ€ง             | ็ฑปๅž‹      | ้ป˜่ฎคๅ€ผ          | ่ฏดๆ˜Ž                   |
| ---------------- | --------- | --------------- | ---------------------- |
| `placeholder`    | `string`  | `'่พ“ๅ…ฅ่ฎฐๅฝ•...'` | ่พ“ๅ…ฅๆก†ๅ ไฝ็ฌฆๆ–‡ๆœฌ       |
| `defaultValue`   | `string`  | `''`            | ่พ“ๅ…ฅๆก†ๅˆๅง‹ๅ€ผ           |
| `paperWidth`     | `number`  | `500`           | ็บธๆกๅฎฝๅบฆ (px)          |
| `showHint`       | `boolean` | `true`          | ๆ˜ฏๅฆๆ˜พ็คบๅบ•้ƒจๆ“ไฝœๆ็คบ   |
| `rotateDuration` | `number`  | `400`           | ๆœบๅ™จไบบๆ—‹่ฝฌๅŠจ็”ปๆ—ถ้—ด(ms) |
| `paperDuration`  | `number`  | `600`           | ็บธๆกๅฑ•ๅผ€ๅŠจ็”ปๆ—ถ้—ด(ms)   |

### ๅค–่ง‚้…็ฝฎ

| ๅฑžๆ€ง               | ็ฑปๅž‹                   | ้ป˜่ฎคๅ€ผ                              | ่ฏดๆ˜Ž                               |
| ------------------ | ---------------------- | ----------------------------------- | ---------------------------------- |
| `antennaBallColor` | `string \| string[]`   | `['#ff6b6b', '#e74c3c', '#c0392b']` | ๅคฉ็บฟๅฐ็ƒ้ขœ่‰ฒ๏ผˆๅŠ ่ฝฝๆ—ถ่‡ชๅŠจๅ˜ไธบ็ปฟ่‰ฒ๏ผ‰ |
| `tiltStrength`     | `number`               | `1`                                 | ๅ€พๆ–œๅผบๅบฆ (0-2)                     |
| `shadowStrength`   | `number`               | `1`                                 | ้˜ดๅฝฑๅผบๅบฆ (0-2)                     |
| `styleMode`        | `'default' \| 'glass'` | `'default'`                         | ่ง†่ง‰้ฃŽๆ ผ (ๆ–ฐ!)                     |
| `eyeMode`          | `EyeMode`              | `{ mode: 'normal' }`                | ็œผ็›ๆจกๅผ้…็ฝฎ๏ผŒ่งไธ‹ๆ–น็ฑปๅž‹ๅฎšไน‰       |

### ็Šถๆ€ๆŽงๅˆถ

| ๅฑžๆ€ง               | ็ฑปๅž‹                          | ้ป˜่ฎคๅ€ผ  | ่ฏดๆ˜Ž                                     |
| ------------------ | ----------------------------- | ------- | ---------------------------------------- |
| `loading`          | `boolean`                     | `false` | ๅŠ ่ฝฝ็Šถๆ€๏ผŒ็ฆ็”จ่พ“ๅ…ฅๅนถๆ˜พ็คบ่„‰ๅ†ฒๅŠจ็”ป         |
| `delay`            | `number`                      | `0`     | ้ข‘็އ้™ๅˆถๅ€’่ฎกๆ—ถ (็ง’)๏ผŒ>0 ๆ—ถๆ˜พ็คบ           |
| `expanded`         | `boolean`                     | -       | ๅค–้ƒจๆŽงๅˆถๅฑ•ๅผ€็Šถๆ€๏ผˆๅฏ้€‰๏ผŒไธไผ ๅˆ™ๅ†…้ƒจๆŽงๅˆถ๏ผ‰ |
| `onExpandedChange` | `(expanded: boolean) => void` | -       | ๅฑ•ๅผ€็Šถๆ€ๅ˜ๅŒ–ๅ›ž่ฐƒ                         |
| `isDark`           | `boolean`                     | `false` | ๆ˜ฏๅฆๆทฑ่‰ฒๆจกๅผ (ๅฝฑๅ“็œผ็›็ฉบ้—ฒ็ก็œ ็Šถๆ€)      |
| `highlightTrigger` | `number`                      | `0`     | ๅ€ผๅ˜ๅŒ–ๆ—ถ่งฆๅ‘ๆŠ–ๅŠจ้ซ˜ไบฎๅŠจ็”ป                 |
| `position`         | `{ x: number; y: number }`    | -       | ๅค–้ƒจๆŽงๅˆถไฝ็ฝฎ๏ผˆๅฏ้€‰๏ผŒๅ—ๆŽงๆจกๅผ๏ผ‰           |
| `onPositionChange` | `(pos: Position) => void`     | -       | ไฝ็ฝฎๅ˜ๅŒ–ๅ›ž่ฐƒ                             |

### ้ซ˜็บง้…็ฝฎ

| ๅฑžๆ€ง          | ็ฑปๅž‹                | ่ฏดๆ˜Ž                                |
| ------------- | ------------------- | ----------------------------------- |
| `actions`     | `ActionConfig[]`    | ็บธๆกไธŠๆ–น็š„ๆ“ไฝœ่œๅ•้…็ฝฎ              |
| `resultPanel` | `ResultPanelConfig` | ็ป“ๆžœๆ˜พ็คบ้ขๆฟ็š„้…็ฝฎไธŽ็Šถๆ€            |
| `infoContent` | `ReactNode`         | ๅบ•้ƒจไฟกๆฏๆ ็š„่‡ชๅฎšไน‰ๅ†…ๅฎน (ไพ‹ๅฆ‚ token) |

#### ็ฑปๅž‹ๅฎšไน‰

```typescript
// ็œผ็›ๆจกๅผ้…็ฝฎ
type EyeMode =
  | { mode: 'normal'; blinkInterval?: [number, number] }
  | { mode: 'loading' }
  | { mode: 'countdown' }
  | { mode: 'sleeping' };

// ่œๅ•ๅŠจไฝœ้…็ฝฎ
interface ActionConfig {
  label: string;
  onClick?: (value: string) => void;
  disabled?: boolean;
  subActions?: ActionConfig[]; // ้€’ๅฝ’ๅญ่œๅ•
}

// ็ป“ๆžœ้ขๆฟ้…็ฝฎ
interface ResultPanelConfig {
  visible: boolean;
  content: string;
  loading: boolean;
  onClose: () => void;
  actions?: ActionConfig[];
  onPlacementChange?: (placement: 'top' | 'bottom') => void;
}

็”จๆˆทๅ›ž่ฐƒ

ๅฑžๆ€ง ็ฑปๅž‹ ่ฏดๆ˜Ž
onValueChange (value: string) => void ่พ“ๅ…ฅๅ†…ๅฎนๅ˜ๅŒ–ๆ—ถ่งฆๅ‘
onSubmit (value: string) => void ็”จๆˆทๆไบคๅ†…ๅฎนๆ—ถ่งฆๅ‘ (Enter ้”ฎ)
onAbort () => void ็”จๆˆท็‚นๅ‡ป็ปฟ่‰ฒๅคฉ็บฟไธญๆญขๆŒ‰้’ฎๆ—ถ่งฆๅ‘

ๆ‹–ๆ‹ฝ้…็ฝฎ

ๅฑžๆ€ง ็ฑปๅž‹ ้ป˜่ฎคๅ€ผ ่ฏดๆ˜Ž
draggable boolean false ๆ˜ฏๅฆๅฏๆ‹–ๆ‹ฝ
defaultPosition { x: number; y: number } ๅณไธ‹่ง’ ๅˆๅง‹ไฝ็ฝฎ (viewport ๅๆ ‡)

๐ŸŽฏ ๅค–้ƒจๆŽงๅˆถ็คบไพ‹

const [expanded, setExpanded] = useState(false);

// ๅค–้ƒจๆŒ‰้’ฎๆŽงๅˆถๅฑ•ๅผ€/ๆ”ถ่ตท
<button onClick={() => setExpanded(true)}>่š็„ฆๅฑ•ๅผ€</button>
<button onClick={() => setExpanded(false)}>ๅคฑ็„ฆๆ”ถ่ตท</button>

<RobotPrinter
  expanded={expanded}
  onExpandedChange={setExpanded}
/>

๐Ÿ“ ็›ฎๅฝ•็ป“ๆž„

RobotPrinter/
โ”œโ”€โ”€ index.ts                # ไธปๅ…ฅๅฃ๏ผŒๅฏผๅ‡บๆ‰€ๆœ‰ๅ…ฌๅ…ฑ API
โ”œโ”€โ”€ RobotPrinter.tsx        # ไธป็ป„ไปถ
โ”œโ”€โ”€ RobotPrinterRoot.tsx    # ๅคๅˆ็ป„ไปถๆ นๅฎนๅ™จ
โ”œโ”€โ”€ types.ts                # ็ปŸไธ€็ฑปๅž‹ๅฎšไน‰
โ”œโ”€โ”€ constants.ts            # ๅธธ้‡้…็ฝฎ
โ”œโ”€โ”€ defaults.ts             # Props ้ป˜่ฎคๅ€ผ็ฎก็†
โ”œโ”€โ”€ presets.ts              # ้…็ฝฎ้ข„่ฎพ (chat, dark, compact...)
โ”œโ”€โ”€ ErrorBoundary.tsx       # ้”™่ฏฏ่พน็•Œ็ป„ไปถ
โ”œโ”€โ”€ Paper.tsx               # ็บธๆก่พ“ๅ…ฅๆก†็ป„ไปถ
โ”œโ”€โ”€ ResultPanel.tsx         # ็ป“ๆžœ้ขๆฟ็ป„ไปถ
โ”œโ”€โ”€ InfoBar.tsx             # ๅบ•้ƒจๆ็คบๆ ็ป„ไปถ
โ”œโ”€โ”€ context/                # Context ๆจกๅ—
โ”‚   โ”œโ”€โ”€ RobotPrinterContext.tsx  # Provider
โ”‚   โ””โ”€โ”€ useRobotPrinter.ts       # Context Hooks
โ”œโ”€โ”€ hooks/                  # ่‡ชๅฎšไน‰ Hooks
โ”‚   โ”œโ”€โ”€ index.ts
โ”‚   โ”œโ”€โ”€ useDrag.ts          # ๆ‹–ๆ‹ฝ้€ป่พ‘
โ”‚   โ”œโ”€โ”€ useAnimationPhase.ts # ๅŠจ็”ป้˜ถๆฎต็ฎก็†
โ”‚   โ”œโ”€โ”€ useAnimationMachine.ts # ็Šถๆ€ๆœบๅŠจ็”ป (useReducer)
โ”‚   โ”œโ”€โ”€ useIdleDetection.ts  # ็ฉบ้—ฒๆฃ€ๆต‹
โ”‚   โ””โ”€โ”€ useTilt.ts          # ๅ€พๆ–œ/้˜ดๅฝฑ่ฎก็ฎ—
โ”œโ”€โ”€ utils/                  # ๅทฅๅ…ทๅ‡ฝๆ•ฐ
โ”‚   โ”œโ”€โ”€ index.ts
โ”‚   โ”œโ”€โ”€ geometry.ts         # ๅ‡ ไฝ•่ฎก็ฎ—
โ”‚   โ””โ”€โ”€ devWarnings.ts      # ๅผ€ๅ‘็Žฏๅขƒ่ญฆๅ‘Š
โ”œโ”€โ”€ styles/                 # ๆจกๅ—ๅŒ– CSS
โ”‚   โ”œโ”€โ”€ index.css           # ๆ ทๅผๅ…ฅๅฃ
โ”‚   โ”œโ”€โ”€ tokens.css          # ่ฎพ่ฎกๆ ‡่ฎฐ/CSS ๅ˜้‡
โ”‚   โ”œโ”€โ”€ animations.css      # ๅŠจ็”ปๅ…ณ้”ฎๅธง
โ”‚   โ”œโ”€โ”€ base.css            # ๅŸบ็ก€ๅฎนๅ™จๆ ทๅผ
โ”‚   โ”œโ”€โ”€ robot-head.css      # ๆœบๅ™จไบบๅคด้ƒจ
โ”‚   โ”œโ”€โ”€ paper.css           # ็บธๆกๆ ทๅผ
โ”‚   โ”œโ”€โ”€ result-panel.css    # ็ป“ๆžœ้ขๆฟ
โ”‚   โ”œโ”€โ”€ action-menu.css     # ๆ“ไฝœ่œๅ•
โ”‚   โ””โ”€โ”€ glass-backdrop.css  # ๆฏ›็Žป็’ƒๆ•ˆๆžœ
โ”œโ”€โ”€ menus/                  # ่œๅ•ๆจกๅ—
โ”‚   โ”œโ”€โ”€ index.ts
โ”‚   โ”œโ”€โ”€ types.ts
โ”‚   โ”œโ”€โ”€ ActionMenu.tsx
โ”‚   โ””โ”€โ”€ ActionButton.tsx
โ”œโ”€โ”€ robot/                  # ๆœบๅ™จไบบๅคด้ƒจๆจกๅ—
โ”‚   โ”œโ”€โ”€ index.ts
โ”‚   โ”œโ”€โ”€ RobotHead.tsx
โ”‚   โ”œโ”€โ”€ Eyes.tsx
โ”‚   โ””โ”€โ”€ Antenna.tsx
โ””โ”€โ”€ __tests__/              # ๅ•ๅ…ƒๆต‹่ฏ• (114+ tests)
    โ”œโ”€โ”€ geometry.test.ts
    โ”œโ”€โ”€ useIdleDetection.test.ts
    โ”œโ”€โ”€ useTilt.test.ts
    โ”œโ”€โ”€ useAnimationMachine.test.ts
    โ”œโ”€โ”€ devWarnings.test.ts
    โ”œโ”€โ”€ defaults.test.ts
    โ”œโ”€โ”€ RobotPrinterContext.test.tsx
    โ””โ”€โ”€ RobotPrinterRoot.test.tsx

๐Ÿค ่ดก็Œฎ

ๆฌข่ฟŽๆไบค Issue ๅ’Œ PR๏ผ

๐Ÿ“„ ่ฎธๅฏ่ฏ

MIT License


๐Ÿค– RobotPrinter

English | ไธญๆ–‡

A fun React component โ€” Robot Spitting Notes!

Perfect for AI conversations, quick commands, data processing middleware, and more. Features unique animations and interactive experiences.

Demo: www.boat2moon.com/robot-printer

๐Ÿ“น Demo Videos

https://github.com/boat2moon/RobotPrinter/releases/download/demo-videos/DEMO1.mp4

https://github.com/boat2moon/RobotPrinter/releases/download/demo-videos/DEMO2.mp4

โœจ Features

  • ๐ŸŽจ Unique Visual Style - 3D robot head + expandable vintage paper slip
  • ๐Ÿ–ฑ๏ธ Draggable - Drag anywhere on the page
  • ๐Ÿ“ Smart Interaction - Enter to submit, visual Enter icon, auto-locks during loading
  • ๐Ÿ›‘ Intuitive States - Eyes show loading pulse/countdown, antenna turns green as abort button
  • ๐Ÿ˜ด Smart Sleep - Automatically sleeps in dark mode or when idle
  • ๐Ÿ”— External Control - Control expand/collapse via expanded prop
  • ๐ŸŽฏ Action Buttons - Configurable buttons with hover submenus
  • ๐Ÿ“‹ Result Panel - Display AI results with replace/insert actions
  • ๐Ÿ’ฌ Info Bar - Customizable hint area at bottom
  • ๐Ÿ”ง Highly Configurable - All features via props, no business logic coupling
  • ๐Ÿ’Ž Glass Mode - Provides Glassmorphism style option, modern look

    โš ๏ธ Known Issue: Glass Mode may flicker when screen recording in Chrome/Edge (invisible to naked eye, but visible in recordings). This is a known Chromium bug, works perfectly in Firefox and Safari. See technical analysis for details.

  • ๐Ÿ“ Adaptive Layout - Result panel automatically avoids obstructions, position is fully configurable

๐Ÿ”ง Advanced Configuration

You can adjust layout constants at the top of src/components/RobotPrinter/ResultPanel.tsx as needed:

// --- Glass Mode Parameters ---
const GLASS_GAP_TOP = 35;
const GLASS_GAP_BOTTOM = 45;
const GLASS_OFFSET_LEFT = 30;
const GLASS_OFFSET_RIGHT = 30;

// --- Default Mode Parameters ---
const DEFAULT_GAP_TOP = 65;
const DEFAULT_GAP_BOTTOM = 65;
// ...

๐Ÿ› ๏ธ Tech Stack

  • React 19+ (Hooks)
  • TypeScript 5.9+ (Strict mode)
  • Vite 7.x (Development/Build)
  • Vitest (Unit testing, 114+ test cases)
  • CSS Variables (Theming)
  • ESLint + Prettier (Code style)
  • Husky + Commitlint (Git Hooks + Commit conventions)
  • GitHub Actions (CI/CD)
  • sonner (Toast notifications, used in Demo)

๐Ÿ“ฆ Installation

# Copy component folder to your project
cp -r src/components/RobotPrinter your-project/src/components/

Development Demo

# Clone repository
git clone https://github.com/boat2moon/RobotPrinter.git
cd RobotPrinter

# Install dependencies
npm install

# Start dev server
npm run dev

# Run tests
npm run test

# Test coverage
npm run test:coverage

# Type check
npm run type-check

# Build for production
npm run build

# Build component library (for npm publish)
npm run build:lib

# Build with bundle analysis report
npm run build:lib:analyze

๐Ÿš€ Quick Start

import { RobotPrinter } from './components/RobotPrinter';

function App() {
  const handleSubmit = (value: string) => {
    console.log('User input:', value);
    // Call your AI API...
  };

  return <RobotPrinter placeholder="Enter command..." onSubmit={handleSubmit} />;
}

Using Presets

Built-in presets for common scenarios:

import { RobotPrinter, chatPreset, darkPreset, compactPreset } from './components/RobotPrinter';

// AI Chat scenario
<RobotPrinter {...chatPreset} onSubmit={handleSubmit} />

// Dark theme
<RobotPrinter {...darkPreset} onSubmit={handleSubmit} />

// Compact mode
<RobotPrinter {...compactPreset} onSubmit={handleSubmit} />

Available presets: compactPreset | widePreset | chatPreset | darkPreset | demoPreset | minimalPreset | floatingPreset | loadingPreset

Error Boundary Wrapper

Recommended to wrap with error boundary to prevent render errors from crashing the page:

import { RobotPrinterErrorBoundary, RobotPrinter } from './components/RobotPrinter';

<RobotPrinterErrorBoundary
  fallback={(error, reset) => (
    <div>
      <p>Component error: {error.message}</p>
      <button onClick={reset}>Retry</button>
    </div>
  )}
>
  <RobotPrinter />
</RobotPrinterErrorBoundary>;

## ๐Ÿ“– API Reference

### Basic Props

| Prop             | Type      | Default         | Description                   |
| ---------------- | --------- | --------------- | ----------------------------- |
| `placeholder`    | `string`  | `'่พ“ๅ…ฅ่ฎฐๅฝ•...'` | Input placeholder text        |
| `defaultValue`   | `string`  | `''`            | Initial input value           |
| `paperWidth`     | `number`  | `500`           | Paper width (px)              |
| `showHint`       | `boolean` | `true`          | Show bottom hint              |
| `rotateDuration` | `number`  | `400`           | Robot rotation animation (ms) |
| `paperDuration`  | `number`  | `600`           | Paper expand animation (ms)   |

### Appearance

| Prop               | Type                   | Default                             | Description                                   |
| ------------------ | ---------------------- | ----------------------------------- | --------------------------------------------- |
| `antennaBallColor` | `string \| string[]`   | `['#ff6b6b', '#e74c3c', '#c0392b']` | Antenna ball color (turns green when loading) |
| `tiltStrength`     | `number`               | `1`                                 | Tilt intensity (0-2)                          |
| `shadowStrength`   | `number`               | `1`                                 | Shadow intensity (0-2)                        |
| `styleMode`        | `'default' \| 'glass'` | `'default'`                         | Visual style (New!)                           |
| `eyeMode`          | `EyeMode`              | `{ mode: 'normal' }`                | Eye mode config, see type defs below          |

### State Control

| Prop               | Type                          | Default | Description                                  |
| ------------------ | ----------------------------- | ------- | -------------------------------------------- |
| `loading`          | `boolean`                     | `false` | Loading state, disables input                |
| `delay`            | `number`                      | `0`     | Rate limit countdown (seconds)               |
| `expanded`         | `boolean`                     | -       | External control for expand state (optional) |
| `onExpandedChange` | `(expanded: boolean) => void` | -       | Callback when expand state changes           |
| `isDark`           | `boolean`                     | `false` | Dark mode flag (affects idle sleep state)    |
| `highlightTrigger` | `number`                      | `0`     | Changing this value triggers shake animation |
| `position`         | `{ x: number; y: number }`    | -       | External control for position (controlled)   |
| `onPositionChange` | `(pos: Position) => void`     | -       | Callback when position changes               |

### Advanced Configuration

| Prop          | Type                | Description                                |
| ------------- | ------------------- | ------------------------------------------ |
| `actions`     | `ActionConfig[]`    | Configuration for the action menu          |
| `resultPanel` | `ResultPanelConfig` | Configuration for the result display panel |
| `infoContent` | `ReactNode`         | Custom content for bottom info bar         |

#### Type Definitions

```typescript
// Eye Mode Configuration
type EyeMode =
  | { mode: 'normal'; blinkInterval?: [number, number] }
  | { mode: 'loading' }
  | { mode: 'countdown' }
  | { mode: 'sleeping' };

// Action Menu Configuration
interface ActionConfig {
  label: string;
  onClick?: (value: string) => void;
  disabled?: boolean;
  subActions?: ActionConfig[]; // Recursive submenu
}

// Result Panel Configuration
interface ResultPanelConfig {
  visible: boolean;
  content: string;
  loading: boolean;
  onClose: () => void;
  actions?: ActionConfig[];
  onPlacementChange?: (placement: 'top' | 'bottom') => void;
}

User Callbacks

Prop Type Description
onValueChange (value: string) => void Triggered on input change
onSubmit (value: string) => void Triggered on Enter key
onAbort () => void Triggered on green antenna click

Drag Configuration

Prop Type Default Description
draggable boolean false Enable dragging
defaultPosition { x: number; y: number } Bottom-right Initial position

๐ŸŽฏ External Control Example

const [expanded, setExpanded] = useState(false);

// External buttons to control expand/collapse
<button onClick={() => setExpanded(true)}>Focus & Expand</button>
<button onClick={() => setExpanded(false)}>Blur & Collapse</button>

<RobotPrinter
  expanded={expanded}
  onExpandedChange={setExpanded}
/>

๐Ÿ“ Directory Structure

RobotPrinter/
โ”œโ”€โ”€ index.ts                # Entry point, exports all public APIs
โ”œโ”€โ”€ RobotPrinter.tsx        # Main component
โ”œโ”€โ”€ RobotPrinterRoot.tsx    # Compound component root container
โ”œโ”€โ”€ types.ts                # Unified type definitions
โ”œโ”€โ”€ constants.ts            # Constants configuration
โ”œโ”€โ”€ defaults.ts             # Props defaults management
โ”œโ”€โ”€ presets.ts              # Configuration presets (chat, dark, compact...)
โ”œโ”€โ”€ ErrorBoundary.tsx       # Error boundary component
โ”œโ”€โ”€ Paper.tsx               # Paper input component
โ”œโ”€โ”€ ResultPanel.tsx         # Result panel component
โ”œโ”€โ”€ InfoBar.tsx             # Info bar component
โ”œโ”€โ”€ context/                # Context module
โ”‚   โ”œโ”€โ”€ RobotPrinterContext.tsx  # Provider
โ”‚   โ””โ”€โ”€ useRobotPrinter.ts       # Context Hooks
โ”œโ”€โ”€ hooks/                  # Custom Hooks
โ”‚   โ”œโ”€โ”€ index.ts
โ”‚   โ”œโ”€โ”€ useDrag.ts          # Drag logic
โ”‚   โ”œโ”€โ”€ useAnimationPhase.ts # Animation phase management
โ”‚   โ”œโ”€โ”€ useAnimationMachine.ts # State machine animation (useReducer)
โ”‚   โ”œโ”€โ”€ useIdleDetection.ts  # Idle detection
โ”‚   โ””โ”€โ”€ useTilt.ts          # Tilt/shadow calculation
โ”œโ”€โ”€ utils/                  # Utility functions
โ”‚   โ”œโ”€โ”€ index.ts
โ”‚   โ”œโ”€โ”€ geometry.ts         # Geometry calculations
โ”‚   โ””โ”€โ”€ devWarnings.ts      # Development warnings
โ”œโ”€โ”€ styles/                 # Modular CSS
โ”‚   โ”œโ”€โ”€ index.css           # Style entry
โ”‚   โ”œโ”€โ”€ tokens.css          # Design tokens/CSS variables
โ”‚   โ”œโ”€โ”€ animations.css      # Animation keyframes
โ”‚   โ”œโ”€โ”€ base.css            # Base container styles
โ”‚   โ”œโ”€โ”€ robot-head.css      # Robot head
โ”‚   โ”œโ”€โ”€ paper.css           # Paper styles
โ”‚   โ”œโ”€โ”€ result-panel.css    # Result panel
โ”‚   โ”œโ”€โ”€ action-menu.css     # Action menu
โ”‚   โ””โ”€โ”€ glass-backdrop.css  # Glassmorphism effect
โ”œโ”€โ”€ menus/                  # Menu module
โ”‚   โ”œโ”€โ”€ index.ts
โ”‚   โ”œโ”€โ”€ types.ts
โ”‚   โ”œโ”€โ”€ ActionMenu.tsx
โ”‚   โ””โ”€โ”€ ActionButton.tsx
โ”œโ”€โ”€ robot/                  # Robot head module
โ”‚   โ”œโ”€โ”€ index.ts
โ”‚   โ”œโ”€โ”€ RobotHead.tsx
โ”‚   โ”œโ”€โ”€ Eyes.tsx
โ”‚   โ””โ”€โ”€ Antenna.tsx
โ””โ”€โ”€ __tests__/              # Unit tests (114+ tests)
    โ”œโ”€โ”€ geometry.test.ts
    โ”œโ”€โ”€ useIdleDetection.test.ts
    โ”œโ”€โ”€ useTilt.test.ts
    โ”œโ”€โ”€ useAnimationMachine.test.ts
    โ”œโ”€โ”€ devWarnings.test.ts
    โ”œโ”€โ”€ defaults.test.ts
    โ”œโ”€โ”€ RobotPrinterContext.test.tsx
    โ””โ”€โ”€ RobotPrinterRoot.test.tsx

๐Ÿค Contributing

Issues and PRs welcome!

๐Ÿ“„ License

MIT License

About

๐Ÿค– A fun React component - Robot spitting paper! For AI chat, quick commands, data processing middleware, with unique animation effects and interactive experience.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages