diff --git a/.github/workflows/cn-utility.yml b/.github/workflows/cn-utility.yml new file mode 100644 index 00000000..85a1a3bc --- /dev/null +++ b/.github/workflows/cn-utility.yml @@ -0,0 +1,40 @@ +name: 'cn utility: lint, type-check, test, build' + +on: + pull_request: + branches: [main, dev] + push: + branches: [main, dev] + +concurrency: + group: '${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}' + cancel-in-progress: true + +jobs: + quality: + name: Code Quality & Build + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Bun runtime + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies (locked versions) + run: bun install --frozen-lockfile + + - name: Format check & lint (Biome) + run: bunx biome check . --diagnostic-level=warn + + - name: Type check (TypeScript) + run: bunx tsc --noEmit + + - name: Run unit tests with coverage + run: bunx vitest run --coverage + + - name: Build extension (Chrome) + run: bun run build diff --git a/.github/workflows/mirror-to-gitlab.yml b/.github/workflows/mirror-to-gitlab.yml new file mode 100644 index 00000000..01f98fdc --- /dev/null +++ b/.github/workflows/mirror-to-gitlab.yml @@ -0,0 +1,24 @@ +name: 🔁 Mirror to GitLab + +on: + push: + branches: ['**'] + tags: ['**'] + delete: + branches: ['**'] + tags: ['**'] + +jobs: + mirror: + runs-on: ubuntu-latest + steps: + - name: Checkout with full history + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Push to GitLab + run: | + git remote add gitlab https://oauth2:${{ secrets.GITLAB_TOKEN }}@gitlab.com/${{ secrets.GITLAB_USERNAME }}/${{ github.event.repository.name }}.git + git push gitlab --all --force + git push gitlab --tags --force diff --git a/src/components/button/button.tsx b/src/components/button/button.tsx index 5ac12d5d..e1368661 100644 --- a/src/components/button/button.tsx +++ b/src/components/button/button.tsx @@ -1,23 +1,79 @@ import { Icon } from '@/src/icons' +import { cn } from '@/src/utils/cn' import type React from 'react' +/** + * Props interface for the Button component. + * + * Defines all configurable aspects of button rendering: sizing, styling, + * interaction state, and content. Follows semantic HTML conventions for + * accessibility and compatibility with form submission workflows. + */ interface ButtonProps { + /** Callback fired when the button is clicked */ onClick?: () => void + /** Disables user interaction and updates visual state */ disabled?: boolean + /** Additional CSS classes merged with default styles (overrides via Tailwind merge) */ className?: string + /** Inline CSS styles (lowest precedence, for special cases only) */ style?: React.CSSProperties + /** Optional icon element displayed alongside or instead of text */ icon?: React.ReactNode + /** If true, replaces children with loading indicator and optional text */ loading?: boolean + /** Text or element shown during loading state (defaults to spinner + Persian "صبر کنید...") */ loadingText?: React.ReactNode + /** HTML button type attribute for form semantics */ type?: 'button' | 'submit' | 'reset' + /** If true, button width expands to fill its container */ fullWidth?: boolean + /** Border radius scale: 'sm' (0.375rem), 'md' (0.5rem), 'lg' (0.75rem), 'xl' (1rem), 'full' (9999px) */ rounded?: 'sm' | 'md' | 'lg' | 'xl' | 'full' + /** Button content—text, nodes, or components */ children?: React.ReactNode + /** If true, applies primary theme colors (blue background, white text) */ isPrimary?: boolean + /** Controls button dimensions: 'xs' (24px), 'sm' (32px), 'md' (40px), 'lg' (48px), 'xl' (56px) */ size: 'xs' | 'sm' | 'md' | 'lg' | 'xl' + /** React ref for imperative access (e.g., .focus(), .blur()) */ ref?: any } + +/** + * A reusable button component with flexible sizing, theming, and states. + * + * This component wraps a native HTML ` + * + * + * ``` + */ export function Button(prop: ButtonProps) { + /** + * Maps size prop to Tailwind height and padding utility tokens. + * These classes are combined via `cn()` to avoid conflicts. + */ const sizes: Record = { xs: 'btn-xs', sm: 'btn-sm', @@ -31,7 +87,15 @@ export function Button(prop: ButtonProps) { type={prop.type || 'button'} onClick={prop.onClick} disabled={prop.disabled} - className={`btn cursor-pointer ${prop.fullWidth ? 'full-width' : ''} ${prop.className} ${prop.rounded ? `rounded-${prop.rounded}` : ''} ${prop.isPrimary ? 'btn-primary text-white' : ''} ${sizes[prop.size] || 'btn-md'} active:!translate-y-0`} + className={cn( + 'btn cursor-pointer', + prop.fullWidth && 'full-width', + prop.rounded && `rounded-${prop.rounded}`, + prop.isPrimary && 'btn-primary text-white', + sizes[prop.size] || 'btn-md', + 'active:!translate-y-0', + prop.className + )} style={prop.style} ref={prop.ref} > diff --git a/src/components/checkbox.tsx b/src/components/checkbox.tsx index f22e874d..bfbd90d3 100644 --- a/src/components/checkbox.tsx +++ b/src/components/checkbox.tsx @@ -1,17 +1,58 @@ +import { cn } from '@/src/utils/cn' import { memo } from 'react' +/** + * Props for the CustomCheckbox component. + * + * Provides granular control over styling, state management, and interaction + * handlers. Supports both controlled and uncontrolled patterns via onChange. + */ interface CustomCheckboxProps { + /** Whether the checkbox is currently checked (controlled prop) */ checked: boolean + /** Fired when the user toggles the checkbox (controlled mode) */ onChange?: (e: React.ChangeEvent) => void + /** Label text displayed next to the checkbox */ label?: string + /** Click handler for custom interaction logic (fires after onChange if not disabled) */ onClick?: (e: React.MouseEvent) => void + /** Additional CSS classes merged with computed styles via cn() */ className?: string + /** Disables interaction and dims visual appearance */ disabled?: boolean + /** CSS classes applied when checkbox is unchecked (overrides default border-content) */ unCheckedCheckBoxClassName?: string + /** CSS classes applied when checkbox is checked (overrides default blue background) */ checkedCheckBoxClassName?: string + /** Font weight for label text: 'font-light', 'font-normal', or 'font-bold' */ fontSize?: 'font-light' | 'font-normal' | 'font-bold' } +/** + * A styled, accessible checkbox component built with HTML5 ``. + * + * Features: + * - Animated checkmark that scales in/out on toggle + * - Customizable checked/unchecked styles via className props + * - Disabled state prevents interaction and changes cursor + * - Memoized to prevent unnecessary re-renders + * - Uses semantic `