npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@juv/codego-react-ui

v3.8.7

Published

Reusable React UI components

Readme

@juv/codego-react-ui

A collection of reusable React UI components built with Tailwind CSS, Motion, and Lucide icons.

Author: Codego https://avatars.githubusercontent.com/u/101083976?v=4&size=64 License: MIT


Prerequisites

Before installing this package, make sure the following are set up in your project:

1. React 18+

npm install react react-dom

2. Tailwind CSS v4

npm install tailwindcss @tailwindcss/vite

Add the plugin to your vite.config.ts:

import tailwindcss from "@tailwindcss/vite"
import { defineConfig } from "vite"

export default defineConfig({
  plugins: [tailwindcss()],
})

Then import Tailwind in your main CSS file:

@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Inter:wght@400;500;600&display=swap');
@import "tailwindcss";
@source "../node_modules/@juv/codego-react-ui/dist/index.js";

@theme {
  --font-sans: "Space Grotesk", "Inter", ui-sans-serif, system-ui, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;

  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);
  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);
  --color-primary: var(--primary);
  --color-primary-hover: var(--primary-hover);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--secondary);
  --color-secondary-hover: var(--secondary-hover);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);
  --color-destructive: var(--destructive);
  --color-destructive-foreground: var(--destructive-foreground);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);
  --color-info: var(--info);
  --color-info-hover: var(--info-hover);
  --color-info-foreground: var(--info-foreground);
  --color-warning: var(--warning);
  --color-warning-hover: var(--warning-hover);
  --color-warning-foreground: var(--warning-foreground);
  --color-danger: var(--danger);
  --color-danger-hover: var(--danger-hover);
  --color-danger-foreground: var(--danger-foreground);
  --color-success: var(--success);
  --color-success-hover: var(--success-hover);
  --color-success-foreground: var(--success-foreground);
  --radius: var(--radius);
}

@layer base {
  :root {
    --background: #ffffff;
    --foreground: #09090b;
    --card: #ffffff;
    --card-foreground: #09090b;
    --popover: #ffffff;
    --popover-foreground: #09090b;
    --primary: #6366f1; /* Indigo 500 */
    --primary-hover: #4f46e5;
    --primary-foreground: #ffffff;
    --secondary: #f4f4f5;
    --secondary-hover: #e4e4e7;
    --secondary-foreground: #18181b;
    --info: #3b82f6; /* Blue 500 */
    --info-hover: #2563eb;
    --info-foreground: #ffffff;
    --warning: #f59e0b; /* Amber 500 */
    --warning-hover: #d97706;
    --warning-foreground: #ffffff;
  --danger: #ef4444; /* Red 500 */
  --danger-hover: #dc2626;
  --danger-foreground: #ffffff;
  --success: #22c55e; /* Green 500 */
  --success-hover: #16a34a;
  --success-foreground: #ffffff;
  --muted: #f4f4f5;
    --muted-foreground: #71717a;
    --accent: #f4f4f5;
    --accent-foreground: #18181b;
    --destructive: #ef4444;
    --destructive-foreground: #ffffff;
    --border: #e4e4e7;
    --input: #e4e4e7;
    --ring: var(--primary);
    --radius: 0.75rem;
  }

  .dark {
    --background: #030303; /* Deep black */
    --foreground: #ededed;
    --card: #0a0a0a; /* Slightly lighter black */
    --card-foreground: #ededed;
    --popover: #0a0a0a;
    --popover-foreground: #ededed;
    --primary: #8b5cf6; /* Neon Violet */
    --primary-hover: #7c3aed;
    --primary-foreground: #ffffff;
    --secondary: #171717;
    --secondary-hover: #262626;
    --secondary-foreground: #ededed;
    --info: #3b82f6;
    --info-hover: #2563eb;
    --info-foreground: #ffffff;
    --warning: #f59e0b;
    --warning-hover: #d97706;
    --warning-foreground: #ffffff;
  --danger: #ef4444;
  --danger-hover: #dc2626;
  --danger-foreground: #ffffff;
  --success: #22c55e;
  --success-hover: #16a34a;
  --success-foreground: #ffffff;
  --muted: #171717;
    --muted-foreground: #a1a1aa;
    --accent: #1f1f2e; /* Subtle purple tint */
    --accent-foreground: #c4b5fd;
    --destructive: #7f1d1d;
    --destructive-foreground: #ededed;
    --border: #1f1f1f;
    --input: #1f1f1f;
    --ring: var(--primary);
  }
}

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply bg-background text-foreground;
    font-feature-settings: "rlig" 1, "calt" 1;
  }
}

@layer utilities {
  .glass {
    @apply bg-background/60 backdrop-blur-xl border border-white/10;
  }
  .dark .glass {
    @apply bg-black/40 border-white/5;
  }
  .text-gradient {
    background: linear-gradient(to right, var(--primary), var(--info));
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
  }
  .glow {
    box-shadow: 0 0 20px -5px var(--primary);
  }
}
/* Hide calendar icon (Chrome, Edge, Safari) */
input[type="datetime-local"]::-webkit-calendar-picker-indicator {
  display: none;
  -webkit-appearance: none;
}

/* Optional: remove clear (X) button */
input[type="datetime-local"]::-webkit-clear-button {
  display: none;
}

/* Optional: remove spin buttons */
input[type="datetime-local"]::-webkit-inner-spin-button {
  display: none;
}

in LARAVEL "resources\css\app.css"

@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Inter:wght@400;500;600&display=swap');
@import "tailwindcss";

@source "../../resources/js";
@source "../../node_modules/@juv/codego-react-ui/dist";

@theme {
  --font-sans: "Space Grotesk", "Inter", ui-sans-serif, system-ui, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;

  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);
  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);
  --color-primary: var(--primary);
  --color-primary-hover: var(--primary-hover);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--secondary);
  --color-secondary-hover: var(--secondary-hover);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);
  --color-destructive: var(--destructive);
  --color-destructive-foreground: var(--destructive-foreground);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);
  --color-info: var(--info);
  --color-info-hover: var(--info-hover);
  --color-info-foreground: var(--info-foreground);
  --color-warning: var(--warning);
  --color-warning-hover: var(--warning-hover);
  --color-warning-foreground: var(--warning-foreground);
  --color-danger: var(--danger);
  --color-danger-hover: var(--danger-hover);
  --color-danger-foreground: var(--danger-foreground);
  --color-success: var(--success);
  --color-success-hover: var(--success-hover);
  --color-success-foreground: var(--success-foreground);
  --radius: var(--radius);
}

@layer base {
  :root {
    --background: #ffffff;
    --foreground: #09090b;
    --card: #ffffff;
    --card-foreground: #09090b;
    --popover: #ffffff;
    --popover-foreground: #09090b;
    --primary: #6366f1; /* Indigo 500 */
    --primary-hover: #4f46e5;
    --primary-foreground: #ffffff;
    --secondary: #f4f4f5;
    --secondary-hover: #e4e4e7;
    --secondary-foreground: #18181b;
    --info: #3b82f6; /* Blue 500 */
    --info-hover: #2563eb;
    --info-foreground: #ffffff;
    --warning: #f59e0b; /* Amber 500 */
    --warning-hover: #d97706;
    --warning-foreground: #ffffff;
  --danger: #ef4444; /* Red 500 */
  --danger-hover: #dc2626;
  --danger-foreground: #ffffff;
  --success: #22c55e; /* Green 500 */
  --success-hover: #16a34a;
  --success-foreground: #ffffff;
  --muted: #f4f4f5;
    --muted-foreground: #71717a;
    --accent: #f4f4f5;
    --accent-foreground: #18181b;
    --destructive: #ef4444;
    --destructive-foreground: #ffffff;
    --border: #e4e4e7;
    --input: #e4e4e7;
    --ring: var(--primary);
    --radius: 0.75rem;
  }

  .dark {
    --background: #030303; /* Deep black */
    --foreground: #ededed;
    --card: #0a0a0a; /* Slightly lighter black */
    --card-foreground: #ededed;
    --popover: #0a0a0a;
    --popover-foreground: #ededed;
    --primary: #8b5cf6; /* Neon Violet */
    --primary-hover: #7c3aed;
    --primary-foreground: #ffffff;
    --secondary: #171717;
    --secondary-hover: #262626;
    --secondary-foreground: #ededed;
    --info: #3b82f6;
    --info-hover: #2563eb;
    --info-foreground: #ffffff;
    --warning: #f59e0b;
    --warning-hover: #d97706;
    --warning-foreground: #ffffff;
  --danger: #ef4444;
  --danger-hover: #dc2626;
  --danger-foreground: #ffffff;
  --success: #22c55e;
  --success-hover: #16a34a;
  --success-foreground: #ffffff;
  --muted: #171717;
    --muted-foreground: #a1a1aa;
    --accent: #1f1f2e; /* Subtle purple tint */
    --accent-foreground: #c4b5fd;
    --destructive: #7f1d1d;
    --destructive-foreground: #ededed;
    --border: #1f1f1f;
    --input: #1f1f1f;
    --ring: var(--primary);
  }
}

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply bg-background text-foreground;
    font-feature-settings: "rlig" 1, "calt" 1;
  }
}

@layer utilities {
  .glass {
    @apply bg-background/60 backdrop-blur-xl border border-white/10;
  }
  .dark .glass {
    @apply bg-black/40 border-white/5;
  }
  .text-gradient {
    background: linear-gradient(to right, var(--primary), var(--info));
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
  }
  .glow {
    box-shadow: 0 0 20px -5px var(--primary);
  }
}
/* Hide calendar icon (Chrome, Edge, Safari) */
input[type="datetime-local"]::-webkit-calendar-picker-indicator {
  display: none;
  -webkit-appearance: none;
}

/* Optional: remove clear (X) button */
input[type="datetime-local"]::-webkit-clear-button {
  display: none;
}

/* Optional: remove spin buttons */
input[type="datetime-local"]::-webkit-inner-spin-button {
  display: none;
}

3. Lucide React (icons)

npm install lucide-react

4. Motion (animations)

npm install motion

5. date-fns (date utilities — required by Calendar, Date Picker, Date Range Picker)

npm install date-fns

6. tailwind-merge + clsx (utility — used internally)

npm install tailwind-merge clsx

7. Leaflet

npm install leaflet react-leaflet @types/leaflet

8. MapLibre GL

npm install maplibre-gl

Installation

Once all prerequisites are installed:

npm install @juv/codego-react-ui

Usage

import { Button, Card, Modal } from "@juv/codego-react-ui"

Form Validation Props

All form input components support two shared validation props:

| Prop | Type | Description | |---|---|---| | required | boolean | Marks the field as required. Shows a * indicator next to the label. Also sets aria-required on the underlying input. | | error | string | External error message rendered below the field in destructive color. Also sets aria-invalid on the underlying input. |

Applies to: Input, Select, Textarea, Checkbox, ToggleSwitch, RadioGroup, Combobox, TagInput, OtpInput, ColorPicker, Slider, RangeSlider, FileUpload.

// Required field with * indicator
<Input label="Email" required placeholder="[email protected]" />

// External error message (e.g. from server validation)
<Input label="Email" required error="Email is already taken" />

// Works on all form components
<Select label="Role" required error="Please select a role" options={options} />
<Checkbox inline label="Accept terms" required error="You must accept the terms" />
<ToggleSwitch inline label="Enable 2FA" required />
<Textarea label="Bio" required error="Bio is required" />
<RadioGroup options={plans} required error="Please select a plan" />
<Combobox options={frameworks} required error="Please select a framework" />
<TagInput required error="At least one tag is required" />
<OtpInput length={6} required error="Please enter the code" />
<ColorPicker required error="Please select a color" />
<Slider label="Volume" required error="Please set a value" />
<FileUpload label="Avatar" required error="Please upload a file" />

Components

| Component | Export(s) | |---|---| | Accordion | Accordion | | Avatar Stack | AvatarStack | | Badge | Badge | | Breadcrumb | Breadcrumb | | Bulletin Board | BulletinBoard, BulletinPreview, useServerBulletin | | Button | Button | | Calendar | Calendar | | Card | Card | | Checkbox | Checkbox | | Color Picker | ColorPicker | | Combobox | Combobox | | Command Palette | CommandPalette | | Context Menu | ContextMenu | | Dashboard Widget | StatsWidget, ChartWidget, TableWidget, ComposableWidget, MetricRow | | Data Grid | DataGrid | | Date Picker | DatePickerPopup | | Date Range Picker | DateRangePicker, CalendarDateRangePicker | | Document Generator | DocumentGenerator, PreviewDocument | | Drawer | Drawer | | Dropdown | Dropdown | | File Upload | FileUpload | | Flex Layout | FlexLayout | | Grid Layout | GridLayout | | Input | Input | | Kanban Board | KanbanBoard | | Label | Label | | Leaflet Map | LeafletMap | | MapLibre Map | MapLibreMap | | Modal | Modal | | Modal Variants | ModalUnchange, ModalConfirmation, ModalWithForms | | Navigation | Navigation, Topbar, LeftSidebar, RightSidebar, GroupNavigation | | Notification | NotificationPanel, NotificationBanner, useToast, ToastProvider | | OTP Input | OtpInput | | Pagination | Pagination | | Panel | Panel | | Popover | Popover | | Progress | Progress, CircularProgress | | Radio Group | RadioGroup | | Repeater | Repeater | | Resizable Panels | ResizablePanels | | Rich Text Editor | RichTextEditor | | Scroll Area | ScrollArea | | Section Block | SectionBlock | | Select | Select | | Skeleton | Skeleton | | Slider | Slider, RangeSlider | | Stat Card | StatCard | | Stepper | Stepper | | Table | Table | | Tabs | Tabs | | Tag Input | TagInput | | Textarea | Textarea | | Timeline | Timeline | | Toggle Switch | ToggleSwitch | | Tooltip | Tooltip | | Tree View | TreeView | | Widget | Widget | | Wizard | Wizard |


BulletinBoard Props

| Prop | Type | Default | Description | |---|---|---|---| | items | BulletinItem[] | — | ✓ Array of bulletin post items. | | layout | "grid" \| "list" \| "masonry" | "grid" | Board layout mode. | | columns | 1 \| 2 \| 3 \| 4 | 3 | Number of grid columns (responsive). | | variant | "card" \| "minimal" \| "bordered" | "card" | Visual style of each post card. | | searchable | boolean | false | Show a search input above the board. | | filterable | boolean | false | Show category filter chips above the board. | | categories | string[] | | Explicit category list. Auto-derived from items if omitted. | | title | ReactNode | "Bulletin Board" | Board header title. | | headerAction | ReactNode | | Trailing element in the board header (e.g. a New Post button). | | showHeader | boolean | true | Show or hide the board header bar. | | emptyMessage | ReactNode | "No posts found." | Content shown when the filtered list is empty. | | loading | boolean | false | Show skeleton cards instead of real content. | | loadingCount | number | 6 | Number of skeleton cards to render while loading. | | preview | boolean | false | Open a BulletinPreview modal when a card is clicked. | | onEdit | (item: BulletinItem) => void | | Called when the Edit button is clicked inside the preview. | | onDelete | (item: BulletinItem) => void | | Called after a successful delete (or when no deleteBaseUrl is set). | | deleteBaseUrl | string | | Base URL for built-in DELETE {baseUrl}/{id}/delete request. | | deleteIdKey | string | "id" | Item key used as the id segment in the delete URL. | | serverPagination | BulletinServerPaginationProp \| null | | Pass the serverPagination from useServerBulletin to enable server-driven pagination. | | onItemClick | (item: BulletinItem) => void | | Fired when a card is clicked (ignored when preview=true). | | className | string | | Additional CSS classes on the outer wrapper. |

BulletinPreview Props

| Prop | Type | Required | Description | |---|---|---|---| | item | BulletinItem | ✓ | The bulletin item to display. | | onClose | () => void | ✓ | Called when the modal is dismissed. | | onEdit | (item: BulletinItem) => void | | Show an Edit button; called when clicked. | | onDelete | (item: BulletinItem) => void | | Show a Delete button; called when clicked. |

useServerBulletin Options

| Option | Type | Required | Description | |---|---|---|---| | url | string | ✓ | API endpoint. ?page=N appended automatically. | | params | Record<string, string \| number> | | Extra query params merged on every request. | | encrypt | boolean | | Expect a Laravel-encrypted response payload. | | key | string | | Laravel APP_KEY for decryption. | | decryptPayloadLog | boolean | | Log the decrypted payload to the console. | | transform | (row: any) => BulletinItem | | Map a raw API row to a BulletinItem. |

useServerBulletin Return

| Field | Type | Description | |---|---|---| | items | BulletinItem[] | Fetched (and optionally transformed) items. | | loading | boolean | true while the request is in-flight. | | error | string \| null | Error message if the request failed. | | pagination | ServerPagination \| null | Raw pagination metadata. | | serverPagination | BulletinServerPaginationProp \| null | Pass directly as <BulletinBoard serverPagination={...} />. | | goToPage | (page: number) => void | Navigate to a specific page. | | reload | () => void | Re-fetch the current page (e.g. after a delete). |

useServerBulletin Example

import { BulletinBoard, useServerBulletin } from "@juv/codego-react-ui"

function AnnouncementsPage() {
  const { items, loading, serverPagination, reload } = useServerBulletin({
    url: "/api/bulletins",
    params: { per_page: 9 },
    transform: (row) => ({
      id: row.id,
      title: row.subject,
      body: row.content,
      author: row.posted_by,
      date: row.created_at,
      category: row.department,
      priority: row.level,
      pinned: row.is_pinned,
      tags: row.tags ?? [],
    }),
  })

  return (
    <BulletinBoard
      items={items}
      loading={loading}
      serverPagination={serverPagination}
      columns={3}
      searchable
      filterable
      preview
      onEdit={(item) => openEditModal(item)}
      deleteBaseUrl="/api/bulletins"
      onDelete={() => reload()}
    />
  )
}

Note: useServerBulletin uses the internal codego api client which automatically attaches the Bearer token from localStorage.getItem("token"). For encrypted Laravel responses, pass encrypt: true and set VITE_LARAVEL_KEY in your .env:

VITE_LARAVEL_KEY=base64:your_app_key_here

Or pass the key directly: key: "base64:...". The response can be a plain array [...] or a paginated object { data: [...], total, per_page, ... } — both are handled automatically.


LeafletMap Props

| Prop | Type | Default | Description | |---|---|---|---| | center | [number, number] | [51.505, -0.09] | Map center [lat, lng]. | | zoom | number | 13 | Initial zoom level. | | height | string \| number | 480 | Map height in px or CSS string. | | markers | MapMarker[] | [] | Array of markers to render. | | routes | MapRoute[] | [] | Array of routes to draw via OSRM. | | cluster | boolean | false | Enable marker clustering. | | clusterVariant | "default" \| "bubble" \| "donut" | "default" | Cluster icon style. | | tileUrl | string | OSM tiles | Custom tile URL template. | | tileAttribution | string | OSM attribution | Custom tile attribution. | | darkTile | boolean | false | Use CartoDB dark tile preset. | | className | string | | Additional CSS classes on the wrapper. | | onMarkerClick | (marker: MapMarker) => void | | Fired when a marker is clicked. |

MapMarker

| Prop | Type | Required | Description | |---|---|---|---| | id | string \| number | ✓ | Unique marker identifier. | | lat | number | ✓ | Latitude. | | lng | number | ✓ | Longitude. | | label | string | | Text shown in default popup. | | color | MarkerColor | | Pin color: primary | info | success | warning | danger | any hex. | | icon | string | | Emoji or character rendered inside the pin. | | image | string | | Image URL — renders a circular avatar pin. | | popup | React.ReactNode | | Custom popup content. Overrides label. |

MapRoute

| Prop | Type | Required | Description | |---|---|---|---| | start | { lat: number; lng: number } | ✓ | Route start point. | | end | { lat: number; lng: number } | ✓ | Route end point. | | waypoints | { lat: number; lng: number }[] | | Intermediate waypoints. | | routeType | "drive" \| "walk" | | Routing profile. Default "drive". | | color | string | | Line color hex. | | weight | number | | Line weight in px. Default 5. | | label | string | | Label shown in start/end popups. |


MapLibreMap Props

| Prop | Type | Default | Description | |---|---|---|---| | style | MapLibreStyle | "street" | Map style: globe | 3d | 3d-globe | street | satellite | dark | light. | | center | [number, number] | [0, 20] | Initial center [lng, lat] (MapLibre order). | | zoom | number | 2 | Initial zoom level. | | minZoom | number | 0 | Minimum zoom level. | | maxZoom | number | 22 | Maximum zoom level. | | pitch | number | 0 | Camera pitch in degrees (0–85). Reactive. Auto 55 for 3d/3d-globe. | | minPitch | number | 0 | Minimum pitch constraint. | | maxPitch | number | 85 | Maximum pitch constraint. | | bearing | number | 0 | Camera bearing in degrees (-180–180). Reactive. Auto -20 for 3d/3d-globe. | | minBearing | number | -180 | Minimum bearing constraint. | | maxBearing | number | 180 | Maximum bearing constraint. | | flyTo | FlyToOptions | | Triggers animated camera flight when reference changes. | | markers | MapLibreMarker[] | [] | Array of markers to render. | | routes | MapLibreRoute[] | [] | Array of routes to draw via OSRM. | | cluster | boolean | false | Enable marker clustering. | | clusterVariant | "default" \| "bubble" \| "donut" | "default" | Cluster icon style. | | height | string \| number | 480 | Map height in px or CSS string. | | showControls | boolean | true | Show navigation, scale, and fullscreen controls. | | showStyleSwitcher | boolean | true | Show the bottom style switcher bar. | | showCameraControls | boolean | false | Always show pitch/bearing slider panel. | | className | string | | Additional CSS classes on the wrapper. | | onMarkerClick | (marker: MapLibreMarker) => void | | Fired when a marker is clicked. |

MapLibreRoute

| Prop | Type | Required | Description | |---|---|---|---| | start | { lat: number; lng: number } | ✓ | Route start coordinate. | | end | { lat: number; lng: number } | ✓ | Route end coordinate. | | waypoints | { lat: number; lng: number }[] | | Optional intermediate stops. | | routeType | "drive" \| "walk" | | OSRM routing profile. Default "drive". | | color | string | | Line color hex. Defaults to #6366f1 (drive) or #22c55e (walk). | | weight | number | | Line weight in px. Default 5. | | label | string | | Label shown in start/end popups. |

MapLibreMarker

| Prop | Type | Required | Description | |---|---|---|---| | id | string \| number | ✓ | Unique marker identifier. | | lat | number | ✓ | Latitude. | | lng | number | ✓ | Longitude. | | label | string | | Text shown in default popup. | | color | string | | Pin color: primary | info | success | warning | danger | any hex. | | icon | string | | Emoji or character rendered inside the pin. | | image | string | | Image URL — renders a circular avatar pin. | | popup | string \| HTMLElement | | Custom popup — HTML string or DOM element. Overrides label. |

FlyToOptions

| Prop | Type | Default | Description | |---|---|---|---| | center | [number, number] | | Target center [lng, lat]. | | zoom | number | | Target zoom level. | | pitch | number | | Target pitch in degrees. Clamped to minPitch/maxPitch. | | bearing | number | | Target bearing in degrees. Clamped to minBearing/maxBearing. | | duration | number | 1500 | Animation duration in milliseconds. | | curve | number | 1.42 | Zoom curve during flight. Higher = more zoom-out arc. | | essential | boolean | true | If true, bypasses prefers-reduced-motion. |


Laravel Response Decryption

Use decryptResponse to decrypt Laravel-encrypted API responses (AES-256-CBC).

Setup

Add your Laravel APP_KEY to .env:

VITE_LARAVEL_KEY=base64:your_laravel_app_key_here

Usage

import { api, decryptResponse } from "@juv/codego-react-ui"

type Certificate = { id: number; name: string }

const fetchCertificates = async () => {
  const data = await api.get<string>('/certificate')
  const decoded = decryptResponse<Certificate[]>(data)
  console.log(decoded)
}

api.get should be typed as string since the raw response is an encrypted payload. decryptResponse reads VITE_LARAVEL_KEY automatically.

You can also pass the key explicitly:

const decoded = decryptResponse<Certificate[]>(data, "base64:your_key_here")

API

| Export | Description | |---|---| | decryptResponse(response, key?) | Decrypts a Laravel-encrypted string or { data: string } object. | | decryptLaravelPayload(payload, key?) | Low-level decryption of a raw encrypted payload string. | | getLaravelSecretKey() | Reads the key from VITE_LARAVEL_KEY, REACT_APP_LARAVEL_KEY, or window.__LARAVEL_KEY__. | | parseLaravelKey(secretKey) | Parses a base64:... or raw key string into a CryptoJS.WordArray. | | parseLaravelEncryptedPayload(payload) | Parses a base64-encoded Laravel encrypted payload into { iv, value, mac }. | | LaravelEncryptedPayload | Type for the parsed payload object. |


Run Locally

Prerequisites: Node.js

npm install
npm run dev        # dev server at http://localhost:3000

Build

npm run build      # outputs ESM + CJS + types to /dist

Scripts

| Script | Description | |---|---| | npm run dev | Start dev server on port 3000 | | npm run build | Build library with tsup | | npm run lint | Type-check with TypeScript | | npm run preview | Preview production build | | npm run clean | Remove dist folder |