diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..1e237ad --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,30 @@ +name: Test + +on: + push: + branches: + - main + pull_request: + +concurrency: + group: test-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Unit tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.1 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Run tests + run: bun run test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..fc8293e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,126 @@ +# Contributing to Framekit + +Thank you for helping improve Framekit. This guide covers local setup, project conventions, and what we expect in pull requests. + +## Ways to contribute + +- **Report bugs** — [open an issue](https://github.com/mdadul/FrameKit/issues) with steps to reproduce, expected vs. actual behavior, and browser/OS +- **Suggest features** — describe the problem you are solving and how it fits store-screenshot workflows +- **Submit code** — bug fixes, tests, docs, and focused refactors are all welcome +- **Improve docs** — README, inline comments for non-obvious logic, and onboarding clarity + +## Development setup + +1. Fork and clone the repository: + + ```bash + git clone https://github.com//FrameKit.git + cd FrameKit + ``` + +2. Install dependencies (Bun recommended): + + ```bash + bun install + ``` + +3. Start the dev server: + + ```bash + bun run dev + ``` + +4. Before opening a PR, run: + + ```bash + bun run test + bun run build + bun run lint + ``` + +All three should pass. `build` runs TypeScript project references (`tsc -b`) in addition to the Vite bundle. + +## Architecture + +Framekit follows a simple layering model: + +| Layer | Location | Responsibility | +|-------|----------|----------------| +| **Lib** | `src/lib/` | Pure functions: export planning, template apply, canvas math, asset persistence. No React, no Zustand. | +| **Stores** | `src/stores/` | Domain state and mutations. `project-store` delegates to `stores/project/*` modules. | +| **Hooks** | `src/hooks/` | Thin React adapters (selection, export form, canvas viewport). | +| **Components** | `src/components/` | UI only — panels, canvas, toolbar, landing. | + +**Rules of thumb:** + +- Put reusable business rules in `src/lib/` with unit tests. +- Keep Zustand actions as thin wrappers: clone project → call lib/module → persist history. +- Prefer extending existing helpers over duplicating logic (especially export, templates, and asset upload). +- Use `@/` path aliases (`@/lib/...`, `@/components/...`). + +## Code style + +- **TypeScript** — strict types; avoid `any` unless unavoidable at a library boundary. +- **Naming** — intention-revealing names; functions do one thing; constants in `src/lib/constants/`. +- **Components** — match surrounding patterns (Tailwind utility classes, Radix primitives, existing panel layout). +- **Comments** — only where behavior is non-obvious; let code and tests document contracts. +- **Scope** — keep PRs focused. A bug fix should not include unrelated refactors. + +## Testing + +Tests use [Vitest](https://vitest.dev/) with jsdom. Shared fixtures live in `src/test/fixtures/`. + +- Add or update tests when changing `src/lib/` behavior. +- Characterization tests are preferred for refactors: lock current behavior before moving code. +- Run `bun run test` locally; CI expects a green suite. + +Example: + +```bash +bun run test # all tests +bun run test src/lib/export/zip.ts # path filter (Vitest) +``` + +## Pull request checklist + +- [ ] `bun run test` passes +- [ ] `bun run build` passes +- [ ] `bun run lint` passes (or no new lint violations in touched files) +- [ ] Behavior change is intentional and described in the PR +- [ ] Refactors preserve existing UI, store actions, export output, and shortcuts unless the PR explicitly changes them +- [ ] No secrets, API keys, or personal data committed + +### Commit messages + +Use clear, imperative subjects. Prefer **why** over **what** when it helps reviewers: + +``` +fix export ZIP paths for Android tablet presets + +Extract prepare-export-screen helper so ExportDialog and zip share resize logic +``` + +### PR description + +Include: + +1. **Summary** — what changed and why (1–3 bullets) +2. **Test plan** — commands run and any manual smoke steps (editor open, export preview, template apply, etc.) + +## Manual smoke testing + +For UI or export changes, quickly verify: + +1. Landing page loads; create or open a project +2. Add text, image, shape, and device elements on the canvas +3. Apply a template; undo/redo +4. Export preview and download a PNG or ZIP +5. Import/export a `.ssgproj` file + +## Project file format + +`.ssgproj` files are JSON project bundles. Changes to the schema or import logic should remain backward compatible when possible, and should be called out in the PR if not. + +## Questions + +Open an issue if you are unsure whether an idea fits. For substantial features, filing an issue first helps align on scope before you invest in a large PR. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dc377c6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Emdadul Islam + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index f9f19a4..12d300f 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,103 @@ -# Screenshot Studio OSS +# Framekit -A browser-based App Store and Google Play screenshot generator. Design once, export all required store sizes, and keep everything local. +**Store-ready screenshots** — a browser-based App Store and Google Play screenshot generator. Design once, export every required store size, and keep everything on your machine. + +[GitHub](https://github.com/mdadul/FrameKit) · [Contributing](CONTRIBUTING.md) + +## Why Framekit + +- **Local-first** — projects autosave to IndexedDB in your browser; no account or cloud upload required +- **Store export built in** — Apple and Google Play size presets, bulk ZIP export with `ios/` and `android/` folders +- **Multi-platform workflow** — design on iOS frames, copy screens to Android with one action +- **Open source** — MIT licensed; fork it, self-host it, or contribute back ## Features - Canvas editor with text, images, shapes, and device mockups - 8 device frames (iPhone, iPad, Pixel, Galaxy) -- Layers, assets, templates, and brand kit +- Layers, assets, starter templates, and per-project brand kit - Multi-screen projects (up to 10 screens) -- Undo/redo (100 states) +- Undo/redo (100 history states) - IndexedDB autosave and `.ssgproj` import/export -- PNG/JPEG export at 1x/2x/3x +- PNG/JPEG export at 1×, 2×, and 3× - Bulk ZIP export for Apple and Android store presets - Light/dark theme and workspace preferences -- Project rename, save status, and confirmation dialogs -- Smart export with ios/ / android/ ZIP folders - Screen overview mode and dashboard thumbnails -- Per-project brand kit and keyboard shortcuts guide -- First-run onboarding tour +- Keyboard shortcuts and first-run onboarding tour + +## Tech stack -## Tech Stack +| Layer | Tools | +|-------|-------| +| UI | React 19, TypeScript, Tailwind CSS v4, Radix UI | +| Routing | TanStack Router | +| Canvas | Konva / React-Konva | +| State | Zustand + Immer | +| Storage | Dexie (IndexedDB) | +| Export | Canvas2D renderer, JSZip, Comlink workers | +| Build | Vite 8, Vitest, ESLint | -- React 19 + TypeScript + Vite -- TanStack Router -- Zustand + Immer -- Konva / React-Konva -- Dexie (IndexedDB) -- Tailwind CSS v4 -- dnd-kit, Radix UI, JSZip +## Getting started -## Getting Started +**Prerequisites:** [Bun](https://bun.sh) (recommended) or Node.js 20+ ```bash +git clone https://github.com/mdadul/FrameKit.git +cd FrameKit bun install bun run dev ``` -Open `http://localhost:5173`. +Open [http://localhost:5173](http://localhost:5173). -## Scripts +With npm: ```bash -bun run dev # Start dev server -bun run build # Production build -bun run preview # Preview production build -bun run test # Run unit tests -bun run lint # Lint project +npm install +npm run dev ``` +## Scripts + +| Command | Description | +|---------|-------------| +| `bun run dev` | Start the Vite dev server | +| `bun run build` | Typecheck and production build → `dist/` | +| `bun run preview` | Preview the production build locally | +| `bun run test` | Run unit tests (Vitest) | +| `bun run lint` | Lint with ESLint | + +## Project layout + +``` +src/ +├── components/ # React UI (canvas, panels, toolbar, landing) +├── hooks/ # Thin React hooks (selection, export, canvas) +├── lib/ # Pure business logic (export, templates, canvas math) +├── routes/ # TanStack Router pages +├── stores/ # Zustand stores (project, editor, history) +└── test/ # Shared test fixtures and setup +``` + +Pure logic lives in `src/lib/`; stores orchestrate mutations; components stay thin. See [CONTRIBUTING.md](CONTRIBUTING.md) for conventions. + ## Deployment -The app is a static SPA. Build with `bun run build` and deploy the `dist/` folder to: +Framekit is a static SPA. Build with `bun run build` and deploy the `dist/` folder to any static host: - GitHub Pages - Cloudflare Pages - Netlify - Vercel -## Project Format +## Project format + +Projects can be exported as `.ssgproj` JSON files. A file contains screens, project settings, and embedded asset data so you can back up or share work outside the browser. + +## Contributing -Projects can be exported as `.ssgproj` JSON files containing screens, settings, and embedded assets. +Bug reports, feature ideas, and pull requests are welcome. Read [CONTRIBUTING.md](CONTRIBUTING.md) for setup, architecture notes, and the PR checklist. ## License -MIT +[MIT](LICENSE) diff --git a/src/components/canvas/CanvasTextEditOverlay.tsx b/src/components/canvas/CanvasTextEditOverlay.tsx new file mode 100644 index 0000000..fbba2ab --- /dev/null +++ b/src/components/canvas/CanvasTextEditOverlay.tsx @@ -0,0 +1,71 @@ +import { forwardRef, type KeyboardEvent } from 'react' +import type { TextElement } from '@/lib/types' + +export interface CanvasTextEditOverlayProps { + element: TextElement + screenOffset: { x: number; y: number } + containerRect: DOMRect + panX: number + panY: number + workspaceZoom: number + value: string + onChange: (value: string) => void + onCommit: () => void + onCancel: () => void +} + +export const CanvasTextEditOverlay = forwardRef( + function CanvasTextEditOverlay( + { + element, + screenOffset, + containerRect, + panX, + panY, + workspaceZoom, + value, + onChange, + onCommit, + onCancel, + }, + ref, + ) { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Enter' && !event.shiftKey) { + event.preventDefault() + onCommit() + } else if (event.key === 'Escape') { + event.preventDefault() + onCancel() + } + } + + return ( +