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

@kuroshio-lab/components

v0.3.0

Published

Domain-specific React components for Kuroshio Lab projects

Readme

@kuroshio-lab/components

Domain-specific React components for the Kuroshio Lab marine observation platform. Built on top of @kuroshio-lab/ui primitives with Tailwind CSS and a dark glass-morphism design language.

Installation

npm install @kuroshio-lab/components

Peer dependencies

npm install react react-dom @kuroshio-lab/ui

Components

Observation

ObservationCard

Full-detail card for displaying a single observation with metadata, image, and actions.

import { ObservationCard } from "@kuroshio-lab/components";

<ObservationCard
  observation={obs}
  onSelectObservation={(obs) => {}}
  onDeleteObservation={(id) => {}}
  onEditObservationClick={(obs) => {}}
/>;

| Prop | Type | Required | | ------------------------ | ---------------------------- | -------- | | observation | Observation | Yes | | onSelectObservation | (obs: Observation) => void | Yes | | onDeleteObservation | (id: number) => void | Yes | | onEditObservationClick | (obs: Observation) => void | Yes | | className | string | No |

Note: Action labels ("Edit", "Delete"), emoji icons, and data field labels are currently hardcoded. Customisable labels will be added in a future release.


MiniObservationCard

Compact card variant, intended for use in map popups.

import { MiniObservationCard } from "@kuroshio-lab/components";

<MiniObservationCard observation={obs} />;

| Prop | Type | Required | | ------------- | ------------- | -------- | | observation | Observation | Yes |

Note: Field labels and emoji prefixes (depth, temperature, visibility, etc.) are currently hardcoded.


ObservationModal

Portal-based modal for creating or editing an observation. Includes full Zod-validated form.

import ObservationModal from "@kuroshio-lab/components";

<ObservationModal
  isOpen={open}
  onClose={() => setOpen(false)}
  onObservationUpserted={() => refetch()}
  mode="add"
/>;

| Prop | Type | Required | Default | | ------------------------ | ----------------------------------------------- | -------- | ------- | | isOpen | boolean | Yes | | | onClose | () => void | Yes | | | onObservationUpserted | () => void | Yes | | | mode | "add" \| "edit" | Yes | | | observation | Observation \| null | No | | | onSubmit | (data: any) => Promise<void> | No | | | onSpeciesSearch | (q: string) => Promise<SpeciesSearchResult[]> | No | | | SpeciesSearchComponent | React.ComponentType | No | | | useUserHook | () => any | No | |

Note: Modal titles, button labels, and field placeholders are currently hardcoded in English. Internationalisation support is planned for a future release.


ObservationFilterAndSort

Dropdown to filter a list of observations by validation status, with automatic sort by date descending.

import { ObservationFilterAndSort } from "@kuroshio-lab/components";

<ObservationFilterAndSort
  observations={observations}
  onFilteredObservationsChange={(filtered) => setDisplayed(filtered)}
/>;

| Prop | Type | Required | | ------------------------------ | ------------------------------------------------------------------- | -------- | | observations | Observation[] | Yes | | onFilteredObservationsChange | (obs: Observation[]) => void | Yes | | onFilterChange | (filter: "all" \| "validated" \| "pending" \| "rejected") => void | No |

Note: Filter option labels are currently hardcoded.


Species

SpeciesSearch

Debounced autocomplete input for scientific and common species name lookup.

import SpeciesSearch from "@kuroshio-lab/components";

<SpeciesSearch
  value={selected}
  onChange={(species) => setSelected(species)}
  onSearch={async (query) => await fetchSpecies(query)}
/>;

| Prop | Type | Required | Default | | ------------- | ----------------------------------------------------- | -------- | ------------------------- | | value | { speciesName: string; commonName: string } \| null | No | | | onChange | (species: SpeciesSearchResult \| null) => void | Yes | | | onBlur | () => void | No | | | disabled | boolean | No | false | | placeholder | string | No | "Search for species..." | | error | boolean | No | false | | id | string | No | | | onSearch | (q: string) => Promise<SpeciesSearchResult[]> | No | |

Minimum 2 characters required to trigger a search. Supports keyboard navigation (↑ ↓ Enter Escape).


Header

Header

Main navigation bar with branding, filter controls, export action, and user menu.

import Header from "@kuroshio-lab/components";

<Header
  onApplyFilters={(filters) => setFilters(filters)}
  initialFilters={{
    speciesName: null,
    commonName: null,
    minDate: null,
    maxDate: null,
  }}
  onSpeciesSearch={async (q) => await fetchSpecies(q)}
  user={user}
  onLogout={logout}
  onExport={exportData}
  observationCount={42}
/>;

| Prop | Type | Required | | ------------------------ | ----------------------------------------------- | -------- | | onApplyFilters | (filters: ActiveFilters) => void | Yes | | initialFilters | ActiveFilters | Yes | | onSpeciesSearch | (q: string) => Promise<SpeciesSearchResult[]> | Yes | | user | any | No | | loading | boolean | No | | onLogout | () => Promise<void> | No | | UserRoleBadgeComponent | React.ComponentType | No | | onExport | () => void | No | | observationCount | number | No |

Note: Branding ("Kuroshio-Lab", "Marine Species Observation Tracker"), the tagline, and all button labels are currently hardcoded. These will be made configurable in a future release.


Modals

FilterModal

Portal modal for filtering observations by species and date range. Embeds SpeciesSearch.

import FilterModal from "@kuroshio-lab/components";

<FilterModal
  isOpen={open}
  onClose={() => setOpen(false)}
  onApplyFilters={(filters) => setFilters(filters)}
  initialFilters={filters}
  onSearch={async (q) => await fetchSpecies(q)}
/>;

| Prop | Type | Required | | ---------------- | ----------------------------------------------- | -------- | | isOpen | boolean | Yes | | onClose | () => void | Yes | | onApplyFilters | (filters: ActiveFilters) => void | Yes | | initialFilters | ActiveFilters | Yes | | onSearch | (q: string) => Promise<SpeciesSearchResult[]> | Yes |

Note: All labels and button text are currently hardcoded.


ExportConfirmModal

Confirmation modal before exporting observations as GeoJSON. Displays active filters and observation count.

import ExportConfirmModal from "@kuroshio-lab/components";

<ExportConfirmModal
  isOpen={open}
  onClose={() => setOpen(false)}
  onConfirm={handleExport}
  filters={activeFilters}
  observationCount={42}
/>;

| Prop | Type | Required | | ------------------ | --------------- | -------- | | isOpen | boolean | Yes | | onClose | () => void | Yes | | onConfirm | () => void | Yes | | filters | ActiveFilters | Yes | | observationCount | number | No |

Note: The export format label ("GeoJSON · marine_observations_export.json") and all copy are currently hardcoded.


Loaders

Loader

Full-screen loading overlay with a default spinner. Accepts a custom loader component.

import Loader from '@kuroshio-lab/components';

<Loader isLoading={isLoading} />
// or with custom spinner:
<Loader isLoading={isLoading} LoaderComponent={MySpinner} />

| Prop | Type | Required | | ----------------- | --------------------- | -------- | | isLoading | boolean | Yes | | LoaderComponent | React.ComponentType | No |


GlobalLoader

Wrapper around Loader that accepts either a hook or a prop for loading state.

import GlobalLoader from '@kuroshio-lab/components';

// Hook-based (takes priority)
<GlobalLoader useLoadingHook={useMyLoadingHook} />

// Prop-based
<GlobalLoader isLoading={isLoading} />

| Prop | Type | Required | Default | | ----------------- | ------------------------------ | -------- | ------- | | useLoadingHook | () => { isLoading: boolean } | No | | | isLoading | boolean | No | false | | LoaderComponent | React.ComponentType | No | |


User

UserProvider + useUser

Context provider for authenticated user state. Fetches the current user and handles unauthenticated redirects.

import { UserProvider, useUser } from "@kuroshio-lab/components";

// Wrap your app
<UserProvider apiUrl="https://api.example.com" onLogout={logout}>
  <App />
</UserProvider>;

// Consume in any child
const { user, loading, refetchUser, logout } = useUser();

UserProvider props

| Prop | Type | Required | | ------------- | ----------------------------- | -------- | | children | ReactNode | Yes | | apiUrl | string | No | | onFetchUser | () => Promise<User \| null> | No | | onLogout | () => Promise<void> | No |

useUser() return value

| Key | Type | | ------------- | --------------------- | | user | User \| null | | loading | boolean | | refetchUser | () => void | | logout | () => Promise<void> |

Note: The redirect path (/sign-in) and excluded auth routes are currently hardcoded.


UserRoleBadge

Displays a user's role with a colour-coded badge and icon.

import UserRoleBadge from "@kuroshio-lab/components";

<UserRoleBadge user={user} variant="inline" />;

| Prop | Type | Required | Default | | --------- | --------------------------------- | -------- | -------- | | user | User | Yes | | | variant | "compact" \| "full" \| "inline" | No | "full" |

Role mapping:

| Role | Label | Style | | -------------------------- | ------------ | ------- | | hobbyist | Observer | Neutral | | researcher_pending | Under Review | Warning | | researcher_community | Can Validate | Success | | researcher_institutional | Full Access | Primary |

Note: Role labels are currently hardcoded.


UserObservationSection

Full container component combining a map, observation list, filter/sort, and add/edit modal.

import UserObservationSection from "@kuroshio-lab/components";

<UserObservationSection
  filterSpeciesName={filters.speciesName}
  filterCommonName={filters.commonName}
  filterMinDate={filters.minDate}
  filterMaxDate={filters.maxDate}
  onFetchObservations={fetchObservations}
  onDeleteObservation={deleteObservation}
  onCreateObservation={createObservation}
  onUpdateObservation={updateObservation}
  MapComponent={MyMapComponent}
/>;

| Prop | Type | Required | | ----------------------------------- | ------------------------------------------ | -------- | | filterSpeciesName | string \| null | Yes | | filterCommonName | string \| null | Yes | | filterMinDate | string \| null | Yes | | filterMaxDate | string \| null | Yes | | className | string | No | | ObservationModalComponent | React.ComponentType | No | | ObservationCardComponent | React.ComponentType | No | | ObservationFilterAndSortComponent | React.ComponentType | No | | LoaderComponent | React.ComponentType | No | | MapComponent | React.ComponentType | No | | onFetchObservations | () => Promise<Observation[]> | No | | onDeleteObservation | (id: number) => Promise<void> | No | | onUpdateObservation | (id: number, data: any) => Promise<void> | No | | onCreateObservation | (data: any) => Promise<void> | No |

Note: Section heading ("Your Observations"), status messages, and the "Add Observation" button label are currently hardcoded. These will be made configurable in a future release.


Exported types

import type {
  Observation,
  SpeciesSearchResult,
  ActiveFilters,
  User,
  FormField,
} from "@kuroshio-lab/components";

Design language

All components use a dark glass-morphism theme. They are not designed to support light mode at this time. Colour semantics follow a consistent pattern across components:

| Meaning | Colour | | ------------------- | --------------- | | Validated / Success | Green | | Pending / Warning | Yellow / Orange | | Rejected / Error | Red | | Primary action | Blue / Indigo |


Development

# From repo root
npm run dev              # Watch all packages
cd packages/components
npm run build            # Build this package only

See the root DEVELOPMENT.md for full monorepo setup instructions.