@slm-solusi-digital/telescope-ui
v0.2.0
Published
UI component library built on HeroUI v2 — by SLM Solusi Digital
Maintainers
Readme
@slm-solusi-digital/telescope-ui
A React component library built on top of HeroUI v2. Telescope-UI wraps HeroUI primitives with project-specific defaults and ships a handful of domain components (shipping/voyage status chips, RHF-integrated form inputs, modals, headers) used across the SLM Solusi Digital maritime apps.
Distributed as an ES module with bundled CSS and .d.ts files.
Installation
npm install @slm-solusi-digital/telescope-ui @heroui/[email protected] framer-motionPeer dependencies
| Package | Version |
|---|---|
| react | ^18.0.0 \|\| ^19.0.0 |
| react-dom | ^18.0.0 \|\| ^19.0.0 |
| @heroui/react | 2.8.0 |
| framer-motion | >=10.0.0 |
Everything else (react-hook-form, react-select, react-number-format, lucide-react, @internationalized/date) is bundled into dist/index.js.
Setup
1. Configure Tailwind CSS v4
Create a HeroUI plugin file:
// src/heroui-plugin.ts
import { heroui } from "@heroui/react";
export default heroui();Wire it into your CSS entry. This package ships no CSS — its components are styled by your app's Tailwind build, so you must point Tailwind at both the HeroUI theme and this library's dist so the utility classes used inside the components get generated:
/* src/index.css */
@import "tailwindcss";
@plugin "./heroui-plugin.ts";
@source "../../node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}";
/* Required — without this, telescope-ui components render unstyled */
@source "../../node_modules/@slm-solusi-digital/telescope-ui/dist/**/*.js";The components also rely on a couple of project theme tokens that are not part of Tailwind's defaults. Define them in your theme so classes like text-xxxs and the primary brand color resolve:
@theme {
--color-primary: #0550a9;
--text-xxs: 11px;
--text-xxxs: 10px;
}2. Wrap your app with HeroUIProvider
import { HeroUIProvider } from "@slm-solusi-digital/telescope-ui";
function App() {
return (
<HeroUIProvider>
<YourApp />
</HeroUIProvider>
);
}Components
All components are named exports from the package root.
Buttons
| Export | Notes |
|---|---|
| ButtonActions | Three-dot actions menu trigger. |
| ButtonAdd | Icon-only primary "+" button. ButtonAdd.WithText adds a label. |
| ButtonDelete | Trash-icon danger button. |
import { ButtonAdd, ButtonDelete } from "@slm-solusi-digital/telescope-ui";
<ButtonAdd onPress={onCreate} />
<ButtonAdd.WithText onPress={onCreate}>New voyage</ButtonAdd.WithText>
<ButtonDelete onPress={onDelete} />Status chips
Voyage / vessel / port-call status indicators built on HeroUI Chip.
| Export | Purpose |
|---|---|
| ChipBallast, ChipLaden, ChipSailing | Voyage leg state |
| ChipScheduled, ChipCompleted, ChipCanceled | Lifecycle state |
| ChipPol, ChipPod | Port of loading / discharge |
| ChipVoyageStatus | Router — dispatches to one of the above by status string |
| ChipVesselStatus | Router for vessel-level status |
import { ChipVoyageStatus } from "@slm-solusi-digital/telescope-ui";
<ChipVoyageStatus status="SAILING" />Headers
| Export | Notes |
|---|---|
| HeaderBack | Back-arrow banner for detail pages. |
| HeaderDetailPage | Title + actions slot for record detail screens. |
| HeaderForm | Section heading for form blocks. |
Inputs
Plain HeroUI wrappers and react-hook-form (*RHF) variants:
| Export | Description |
|---|---|
| InputText | Themed Input with outside label + white background. InputText.WithRHFControl adds RHF support. |
| InputCheckbox | Labeled CheckboxGroup over an options array. |
| InputRadioGroup | Labeled RadioGroup over an options array. |
| InputSelect | HeroUI Autocomplete with project styling. |
| InputSelectRHF | RHF-bound Autocomplete with required handling. |
| InputCreatableSelectRHF | RHF-bound react-select creatable variant. |
| InputSelectMultiple | react-select multi-select with label + error display. |
| InputSelectUnitRHF | Small unit dropdown that pairs with a numeric input (place: "right" \| "left"). |
| InputUnit | Static unit-label chip that pairs with an input. |
| InputNumericRHF | RHF-bound numeric input (react-number-format) with thousand-separators, prefix/suffix support. |
| InputDateRHF | RHF-bound date picker. |
| InputDateTimePickerRHF | RHF-bound date + time picker. .V2 flows values as ISO strings. |
| InputDateRangePicker | Date range picker. |
import { useForm } from "react-hook-form";
import { InputText, InputNumericRHF } from "@slm-solusi-digital/telescope-ui";
function VoyageForm() {
const { control } = useForm({ defaultValues: { vesselName: "", quantity: 0 } });
return (
<>
<InputText.WithRHFControl
control={control}
name="vesselName"
label="Vessel name"
isRequired
/>
<InputNumericRHF
control={control}
name="quantity"
label="Quantity"
suffix=" tons"
thousandSeparator
/>
</>
);
}Modals
| Export | Purpose |
|---|---|
| ModalAttachments | File-list + upload modal. |
| ModalDelete | Confirmation dialog for destructive actions. |
| ModalSuccess | Success-state confirmation. |
Feedback & misc
| Export | Notes |
|---|---|
| BannerError | Inline error banner. |
| IconEstimate | Styled "E" badge used to mark estimated values. |
| TimeStatus | Renders an absolute time + relative time chip. |
Utilities
Date formatters built on @internationalized/date, pinned to Asia/Jakarta / en-GB:
import {
formatDateTime,
formatDate,
formatTime,
formatDateISO,
convertDaysToDuration,
convertDateStringToLocal,
convertParamsToDateRange,
getYearRange,
getYearRanges,
type YearRange,
} from "@slm-solusi-digital/telescope-ui";Development
npm install
npm run storybook # Storybook on http://localhost:6006 — primary dev surface
npm run build # tsc -b && vite build → emits dist/
npm run test:unit # Vitest (jsdom)
npm run lint # ESLintRun a single test file:
npx vitest run --project unit src/components/InputText/InputText.test.tsxStorybook stories doubling as headless-browser tests:
npx playwright install chromium # one-time
npx vitest run --project storybookAdding a new component
Each component lives in src/components/<Name>/ with three files:
<Name>.tsx # default export, named after the folder
<Name>.stories.tsx # Storybook stories under "Components/<Group>/<Name>"
<Name>.test.tsx # Vitest + @testing-library/reactThen add the re-export to src/index.ts — components that aren't listed there are invisible to consumers.
Publishing
This is a private, internal package ("access": "restricted"). Publishing requires authentication to a registry where the @slm-solusi-digital scope is private (npm Org/Teams, GitHub Packages, or a self-hosted registry).
npm run build
npm publishThe prepublishOnly hook runs the build automatically, and publishConfig.access in package.json keeps the release restricted — do not pass --access public.
License
UNLICENSED — proprietary and confidential. © SLM Solusi Digital. Internal use only; not licensed for redistribution.
