create-kaiwen-island
v1.0.7
Published
Scaffolding CLI tool to create desktop widgets styled as Apple's Dynamic Island with Electron, React, and TypeScript.
Maintainers
Readme
create-kaiwen-island
A highly modular, customizable, and professional desktop widget scaffolding tool that generates an apple-style Dynamic Island wrapper powered by Electron, React, TypeScript, Vite, and Framer Motion.
This project comes pre-configured with a Finite State Machine (FSM) for layout transitions, global operating system shortcut bindings, and screen positioning options (docking and dragging).
Key Features
Finite State Machine (FSM) State Management Manage capsule state, width, height, and border radius dynamically. Transitions are verified before executing using state configuration rules.
Self-Contained State Modules Each visual state resides in its own folder containing a component view (
index.tsx), a local stylesheet (styles.css), and an FSM config definition (config.ts).Global Shortcut Manager Register and capture global keyboard accelerators (e.g.,
Ctrl+Alt+Ito hide/show,Ctrl+Alt+Spaceto pause/resume) dynamically from the renderer process.Dynamic Overlay Positioning Dock the island in 9 quadrants across the screen (e.g., top-left, top-center, bottom-right) or enable free-form dragging using Framer Motion.
Multi-Window Support Spawn secondary windows from the primary island widget using a secure IPC preload bridge.
Type-Safe Development Full TypeScript declarations for Electron IPC methods and state configs.
Capsule Rotation Rotate the Dynamic Island capsule dynamically to 0°, 90°, 180°, or 270° with smooth Framer Motion spring transitions.
Bilingual Localization (i18n) Supports switching between English (default) and Turkish. Instantly translates all UI strings, states, tooltips, settings, and desktop notification prompts.
Multi-Style Visual Themes (Matte / Frosted Glass / Liquid Glass) Toggle between Matte Dark (classic Apple capsule), Frosted Glass (smooth 20px blur), and Liquid Glass (warped refraction using dynamic SVG turbulence filters) in real-time.
Auto-Clamping Drag & Rotation Constraints Dragging or rotating the capsule vertically (90° or 270°) automatically calculates spatial layout constraints ($W_v$ & $H_v$), smoothly animating the widget back inside safe viewport margins (
pad = 20) without edge overflow.Draggable & Compact Scrollable Settings Panel The settings gear button and overlay card are unified into a draggable entity that supports screen containment. The menu layout is kept highly compact with hidden native scrollbars.
Scaffolding a New Project
To create a new project using this template, execute the following command:
npx create-kaiwen-island my-new-islandAlternatively, run it locally:
node bin/cli.js my-new-islandGetting Started
Once the project is generated, navigate to the directory, install dependencies, and launch the application:
cd my-new-island
npm install
npm run startBuild for Production
To build the client assets for production:
npm run buildTechnical Architecture & State Customization
This section details how to configure the Finite State Machine (FSM), implement new layout states, define transition constraints, and manage navigation history.
1. Finite State Machine (FSM) Mechanics
States are defined as standalone modules representing a particular visual layout. The state structure complies with the StateConfig interface:
export interface StateConfig {
name: string; // Unique identifier for the state (e.g., 'DRAW_MODE')
width: number; // Width of the capsule when this state is active
height: number; // Height of the capsule when this state is active
borderRadius: number; // Border radius of the capsule container
component: ComponentType<any>; // React component to render inside the capsule
allowedTransitions?: string[]; // Allowed source states. If empty, entry is unrestricted.
}Transition Restrictions (allowedTransitions)
The FSM verifies transition requests before updating the layout:
- If
allowedTransitionsis omitted or empty, the state can be accessed from any source state. - If
allowedTransitionscontains specific state names (e.g.,['DASHBOARD', 'IDLE']), the FSM will reject requests to transition to this state unless the current state is one of those listed.
2. Creating a Custom State (Step-by-Step)
To add a new state named DrawMode to the Dynamic Island:
Step 2.1: Create the Module Folder
Create a new directory at src/components/DynamicIsland/states/DrawMode/.
Step 2.2: Implement the Component View
Create src/components/DynamicIsland/states/DrawMode/index.tsx:
import './styles.css';
import type { AppState } from '../../../../App';
interface Props {
goBack: () => void;
setAppState: (state: AppState) => void;
}
export default function DrawMode({ goBack }: Props) {
return (
<div className="draw-mode-container">
<span className="draw-mode-title">Draw Mode Active</span>
<button onClick={goBack} className="draw-mode-btn">Go Back</button>
</div>
);
}Step 2.3: Implement the Local Stylesheet
Create src/components/DynamicIsland/states/DrawMode/styles.css containing the component's private styling rules:
.draw-mode-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
padding: 16px;
color: #f8fafc;
}
.draw-mode-title {
font-size: 14px;
font-weight: 700;
margin-bottom: 12px;
}
.draw-mode-btn {
background-color: #374151;
color: #fff;
border: none;
border-radius: 6px;
padding: 6px 12px;
font-size: 11px;
cursor: pointer;
}Step 2.4: Define the FSM Configuration
Create src/components/DynamicIsland/states/DrawMode/config.ts:
import type { StateConfig } from '../../stateManager';
import DrawMode from './index';
export const DrawModeState: StateConfig = {
name: 'DRAW_MODE',
width: 400,
height: 300,
borderRadius: 24,
component: DrawMode,
allowedTransitions: ['DASHBOARD', 'IDLE'], // Only allow transition from Dashboard or Idle
};
export default DrawModeState;Step 2.5: Register the State in the FSM Registry
Import and register the config in src/components/DynamicIsland/states/registry.ts:
import { stateManager } from '../stateManager';
import { DrawModeState } from './DrawMode/config';
// ... other imports
export function registerAllStates() {
// ... other registrations
stateManager.registerState(DrawModeState);
}You can now trigger the transition to your new state by calling navigateTo('DRAW_MODE') from either the DASHBOARD or IDLE state.
3. Navigation Stack Mechanics
The application distinguishes between user-initiated UI navigation and event-driven state transitions to manage the browser history stack cleanly.
- User Navigations (
navigateTo&goBack): When a user opens a screen (e.g., clicking the idle capsule to open the dashboard), invokenavigateTo('DASHBOARD'). This pushes the previous state onto the history stack (navigationStack). Clicking a back button triggersgoBack(), popping the stack and returning to the caller. - Direct Event Transitions (
changeAppState): Used for background events (e.g., a Pomodoro timer ticking to zero and changing the state toBREAK_PROMPT). Direct transitions bypass pushing to the stack to prevent background updates from polluting the user's navigation history. - History Reset (
clearHistory): Resets the navigation stack. Useful when logging out or returning to the landing screen where backward navigation is no longer valid.
All transitions are printed directly to the developer console indicating transition type, target state, and active navigation stack details.
4. Click-Through Overlay Window
The application utilizes a fullscreen transparent BrowserWindow in Electron. By default, mouse pointer events are passed directly through the window using the setIgnoreMouseEvents Electron API.
- When the pointer enters the Dynamic Island capsule, the renderer process instructs Electron to capture mouse clicks.
- When the pointer leaves the capsule, the window returns to a click-through state, ensuring the desktop remains fully usable.
Global Shortcut Bindings
Default shortcuts are registered inside electron/main.js using the custom ShortcutManager:
Ctrl+Alt+I: Toggle widget visibility (hide/show).Ctrl+Alt+Space: Pause or resume active timers.Ctrl+Alt+D: Force transition to the dashboard view.
Developers can register custom accelerators dynamically by dispatching events over the preload channel:
window.electronAPI.registerShortcut('Ctrl+Alt+X', 'custom-renderer-action');And handle them inside React:
useEffect(() => {
const unsubscribe = window.electronAPI.onShortcutAction((action) => {
if (action === 'custom-renderer-action') {
// Execute custom logic
}
});
return () => unsubscribe();
}, []);