diff --git a/CHANGELOG.md b/CHANGELOG.md index fee600b..bf1233b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [1.3.0] - 2026-03-29 + +### Updated + +- timepicker-ui dependency from 4.2.1 to 4.3.0 + +### Added + +- Re-exported `TemplateProvider` type - interface for plugin `templateProvider` hook +- Re-exported `ClearHandler` type - interface for plugin `clearHandler` hook + +### Notes + +- Plugins now support a `templateProvider` hook to register their own modal templates without core static imports +- Plugins now support a `clearHandler` hook to register custom clear logic executed when the clear button is pressed +- Plugin system refactored for true tree-shaking: unused plugins (wheel, range, timezone) are completely excluded from the final bundle when not imported +- `PluginRegistry.getTemplateProvider()` and `PluginRegistry.getClearHandler()` methods now available on the re-exported `PluginRegistry` + +--- + ## [1.2.0] - 2026-03-24 ### Added diff --git a/README.md b/README.md index 84ff16e..63cfb13 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,21 @@ # timepicker-ui-react -Official React wrapper for [timepicker-ui](https://github.com/pglejzer/timepicker-ui) v4.x. +Official React wrapper for [timepicker-ui](https://github.com/pglejzer/timepicker-ui) v4.x - clock & wheel time picker, full TypeScript, SSR-safe. [![npm version](https://badge.fury.io/js/timepicker-ui-react.svg)](https://badge.fury.io/js/timepicker-ui-react) [![license](https://img.shields.io/badge/license-MIT-green.svg)](https://img.shields.io/npm/l/timepicker-ui-react) -[![size](https://img.shields.io/bundlephobia/minzip/timepicker-ui-react)](https://bundlephobia.com/package/timepicker-ui-react) +[![downloads](https://img.shields.io/npm/dm/timepicker-ui-react)](https://npmcharts.com/compare/timepicker-ui-react?minimal=true) -A lightweight, SSR-safe React component that provides a thin wrapper around the powerful timepicker-ui library with full TypeScript support. +[Live Demo](https://timepicker-ui.vercel.app/react) • [Documentation](https://timepicker-ui.vercel.app/react) • [Changelog](./CHANGELOG.md) • [Core Library](https://github.com/pglejzer/timepicker-ui) -**[View Live Documentation](https://timepicker-ui.vercel.app/react)** • **[Try Interactive Demo](https://timepicker-ui.vercel.app/react/examples)** +## Why timepicker-ui-react? -## Features - -- **Full TypeScript Support** - All types directly from timepicker-ui core -- **SSR-Safe** - Works with Next.js, Remix, Gatsby, and other SSR frameworks -- **Zero Type Duplication** - Re-exports core types, no duplicated interfaces -- **Event-Driven** - Direct mapping to timepicker-ui's EventEmitter API -- **Controlled & Uncontrolled** - Support for both value patterns -- **ESM Only** - Modern, tree-shakeable bundle +- **Thin wrapper** - all power comes from the battle-tested timepicker-ui core +- **Zero type duplication** - types re-exported directly from `timepicker-ui` +- **SSR-safe** - works in Next.js, Remix, Astro out of the box +- **Controlled & uncontrolled** - both `value` and `defaultValue` patterns +- **All input props** - extends `InputHTMLAttributes`, pass anything directly +- **Plugin support** - Timezone, Range, Wheel via `PluginRegistry` ## Installation @@ -25,421 +23,141 @@ A lightweight, SSR-safe React component that provides a thin wrapper around the npm install timepicker-ui-react ``` -```bash -yarn add timepicker-ui-react -``` - -```bash -pnpm add timepicker-ui-react -``` +> `timepicker-ui` is included as a dependency - no need to install it separately. -> **Note:** `timepicker-ui` is automatically installed as a dependency, no need to install it separately. - -## Usage - -### Basic Example +## Quick Start ```tsx -import React from "react"; import { Timepicker } from "timepicker-ui-react"; +import "timepicker-ui/main.css"; function App() { - const handleConfirm = (data) => { - console.log("Time confirmed:", data); - }; - - return ; -} -``` - -### With Options - -```tsx -import React from "react"; -import { Timepicker, type TimepickerOptions } from "timepicker-ui-react"; - -function App() { - const options: TimepickerOptions = { - clock: { - type: "24h", - autoSwitchToMinutes: true, - }, - ui: { - theme: "m3-green", - mobile: false, - }, - labels: { - ok: "Confirm", - cancel: "Close", - }, - }; - - return ; -} -``` - -### Controlled Component - -```tsx -import React, { useState } from "react"; -import { Timepicker } from "timepicker-ui-react"; - -function App() { - const [time, setTime] = useState("12:00 AM"); - return ( { - setTime(`${data.hour}:${data.minutes} ${data.type}`); - }} - onConfirm={(data) => { - console.log("Confirmed:", data); - }} + placeholder="Select time" + onConfirm={(data) => console.log("Selected:", data)} /> ); } ``` -### With All Callbacks - -```tsx -import React from "react"; -import { Timepicker } from "timepicker-ui-react"; - -function App() { - return ( - console.log("Opened:", data)} - onConfirm={(data) => console.log("Confirmed:", data)} - onCancel={() => console.log("Cancelled")} - onUpdate={(data) => console.log("Updated:", data)} - onSelectHour={(data) => console.log("Hour selected:", data)} - onSelectMinute=(data) => console.log("Minute selected:", data)} - onSelectAM={() => console.log("AM selected")} - onSelectPM={() => console.log("PM selected")} - onError={(data) => console.log("Error:", data)} - /> - ); -} -``` - -### Using Plugins (Timezone & Range) - -> **Important:** Timezone and Range features are **plugins** that must be manually imported and registered. They are **not included by default** for tree-shaking optimization. - -```tsx -"use client"; - -import React, { useEffect } from "react"; -import { Timepicker, PluginRegistry } from "timepicker-ui-react"; - -function App() { - useEffect(() => { - // Register plugins once when component mounts - const registerPlugins = async () => { - const { TimezonePlugin } = await import("timepicker-ui/plugins/timezone"); - const { RangePlugin } = await import("timepicker-ui/plugins/range"); - - PluginRegistry.register(TimezonePlugin); - PluginRegistry.register(RangePlugin); - }; - - registerPlugins(); - }, []); - - return ( - <> - {/* Timezone example */} - console.log("Timezone changed:", data)} - /> - - {/* Range example */} - console.log("Range confirmed:", data)} - onRangeSwitch={(data) => console.log("Range switch:", data)} - onRangeValidation={(data) => console.log("Range validation:", data)} - /> - - ); -} -``` - -**Why plugins must be registered manually:** - -- **Tree-shaking** - Bundle only what you use -- **Performance** - Avoid loading unnecessary code -- **SSR compatibility** - Plugins load on client-side only - -### SSR (Next.js Example) - -```tsx -"use client"; - -import { Timepicker } from "timepicker-ui-react"; - -export default function TimepickerPage() { - return ( -
-

Select Time

- -
- ); -} -``` - -The component is SSR-safe by default and will render a basic input during server-side rendering, then hydrate with the full timepicker on the client. - ## API -### `TimepickerProps` - -| Prop | Type | Description | -| ------------------- | --------------------------------------- | ------------------------------------------ | -| `options` | `TimepickerOptions` | Full configuration from timepicker-ui core | -| `value` | `string` | Controlled value | -| `defaultValue` | `string` | Default value for uncontrolled usage | -| `onConfirm` | `CallbacksOptions['onConfirm']` | Triggered when user confirms time | -| `onCancel` | `CallbacksOptions['onCancel']` | Triggered when user cancels | -| `onOpen` | `CallbacksOptions['onOpen']` | Triggered when timepicker opens | -| `onUpdate` | `CallbacksOptions['onUpdate']` | Triggered during real-time interaction | -| `onSelectHour` | `CallbacksOptions['onSelectHour']` | Triggered when hour mode is activated | -| `onSelectMinute` | `CallbacksOptions['onSelectMinute']` | Triggered when minute mode is activated | -| `onSelectAM` | `CallbacksOptions['onSelectAM']` | Triggered when AM is selected | -| `onSelectPM` | `CallbacksOptions['onSelectPM']` | Triggered when PM is selected | -| `onTimezoneChange` | `CallbacksOptions['onTimezoneChange']` | Triggered when timezone changes (plugin) | -| `onRangeConfirm` | `CallbacksOptions['onRangeConfirm']` | Triggered when range is confirmed (plugin) | -| `onRangeSwitch` | `CallbacksOptions['onRangeSwitch']` | Triggered when range switches (plugin) | -| `onRangeValidation` | `CallbacksOptions['onRangeValidation']` | Triggered on range validation (plugin) | - -The component extends `React.InputHTMLAttributes`, so all standard input props can be passed directly: +Full reference: [Props](https://timepicker-ui.vercel.app/react) · [Options](https://timepicker-ui.vercel.app/docs/api/options) · [Events](https://timepicker-ui.vercel.app/docs/api/events) · [TypeScript](https://timepicker-ui.vercel.app/docs/api/typescript) ```tsx {}} + onCancel={(data) => {}} + onOpen={(data) => {}} + onUpdate={(data) => {}} + onSelectHour={(data) => {}} + onSelectMinute={(data) => {}} + onSelectAM={() => {}} + onSelectPM={() => {}} + onClear={(data) => {}} + onError={(data) => {}} + // Plugin callbacks + onTimezoneChange={(data) => {}} + onRangeConfirm={(data) => {}} + onRangeSwitch={(data) => {}} + onRangeValidation={(data) => {}} + // Any standard prop className="my-input" placeholder="Select time" disabled={false} - readOnly={false} - required={true} - name="time" id="timepicker-1" - style={{ width: "200px" }} - // ... any other input props /> ``` -### Exported Types +## Options Overview -All types are re-exported from `timepicker-ui` core: +Same options as timepicker-ui core. Full reference: [Options docs](https://timepicker-ui.vercel.app/docs/api/options) · [Configuration guide](https://timepicker-ui.vercel.app/docs/configuration) ```tsx -import type { - TimepickerOptions, - ClockOptions, - UIOptions, - LabelsOptions, - BehaviorOptions, - CallbacksOptions, - OptionTypes, - OpenEventData, - CancelEventData, - ConfirmEventData, - UpdateEventData, - SelectHourEventData, - SelectMinuteEventData, - SelectAMEventData, - SelectPMEventData, - ErrorEventData, - ShowEventData, - HideEventData, - SwitchViewEventData, -} from "timepicker-ui-react"; + ``` -## Configuration Options +## Themes -For detailed documentation on all available options, please refer to the [timepicker-ui documentation](https://github.com/pglejzer/timepicker-ui). +Same 10 themes as core. [Browse all](https://timepicker-ui.vercel.app/docs/features/themes) · [Live examples](https://timepicker-ui.vercel.app/examples/themes/basic) -### Quick Reference +Available: `basic`, `crane`, `crane-straight`, `m3-green`, `m2`, `dark`, `glassmorphic`, `pastel`, `ai`, `cyberpunk` ```tsx -interface OptionTypes { - // Clock configuration - clockType?: "12h" | "24h"; - incrementHours?: number; - incrementMinutes?: number; - autoSwitchToMinutes?: boolean; - disabledTime?: { - hours?: Array; - minutes?: Array; - interval?: string | string[]; - }; - currentTime?: - | boolean - | { - time?: Date; - updateInput?: boolean; - locales?: string | string[]; - preventClockType?: boolean; - }; - - // UI configuration - theme?: - | "basic" - | "crane" - | "crane-straight" - | "m2" - | "m3-green" - | "dark" - | "glassmorphic" - | "pastel" - | "ai" - | "cyberpunk"; - animation?: boolean; - backdrop?: boolean; - mobile?: boolean; - enableSwitchIcon?: boolean; - editable?: boolean; - enableScrollbar?: boolean; - cssClass?: string; - appendModalSelector?: string; - iconTemplate?: string; - iconTemplateMobile?: string; - inline?: { - enabled: boolean; - containerId: string; - showButtons?: boolean; - autoUpdate?: boolean; - }; - - // Labels - amLabel?: string; - pmLabel?: string; - okLabel?: string; - cancelLabel?: string; - timeLabel?: string; - mobileTimeLabel?: string; - hourMobileLabel?: string; - minuteMobileLabel?: string; - - // Behavior - focusInputAfterCloseModal?: boolean; - focusTrap?: boolean; - delayHandler?: number; - id?: string; - - // Callbacks (use React props instead) - onOpen?: (data: OpenEventData) => void; - onCancel?: (data: CancelEventData) => void; - onConfirm?: (data: ConfirmEventData) => void; - onUpdate?: (data: UpdateEventData) => void; - onSelectHour?: (data: SelectHourEventData) => void; - onSelectMinute?: (data: SelectMinuteEventData) => void; - onSelectAM?: (data: SelectAMEventData) => void; - onSelectPM?: (data: SelectPMEventData) => void; - onError?: (data: ErrorEventData) => void; -} -``` +import "timepicker-ui/main.css"; +import "timepicker-ui/theme-dark.css"; -## Architecture +; +``` -This package follows strict architectural principles: +## Plugins -- **Zero Type Duplication** - All types come directly from `timepicker-ui` -- **SSR-Safe** - Dynamic imports ensure browser-only code runs client-side -- **Event Mapping** - React callbacks map to timepicker-ui's EventEmitter API -- **Composition Over Inheritance** - Clean, maintainable wrapper pattern -- **Modular Hooks** - Separation of concerns with dedicated hooks for instance, events, value, options, and callbacks +Docs: [Plugins overview](https://timepicker-ui.vercel.app/docs/features/plugins) · Examples: [Range](https://timepicker-ui.vercel.app/examples/plugins/range) · [Timezone](https://timepicker-ui.vercel.app/examples/plugins/timezone) · [Wheel](https://timepicker-ui.vercel.app/examples/plugins/wheel) -## Package Structure +```tsx +import { PluginRegistry } from "timepicker-ui-react"; +import { RangePlugin } from "timepicker-ui/plugins/range"; +import { TimezonePlugin } from "timepicker-ui/plugins/timezone"; +import { WheelPlugin } from "timepicker-ui/plugins/wheel"; -This repository uses a dual-package structure: +PluginRegistry.register(RangePlugin); +PluginRegistry.register(TimezonePlugin); +PluginRegistry.register(WheelPlugin); -``` -timepicker-ui-react/ -├── src/ # Library source code -│ ├── Timepicker/ -│ │ ├── Timepicker.tsx -│ │ ├── types.ts -│ │ ├── utils.ts -│ │ └── hooks/ -│ └── index.ts -├── docs/ # Demo application (separate package) -│ ├── package.json # Demo dependencies -│ ├── vite.config.ts -│ └── src/ -│ ├── main.tsx -│ └── App.tsx -├── package.json # Main library package -└── tsup.config.ts # Build configuration + + {}} /> + {}} /> ``` -### Main Package +## SSR / Next.js -- Production-ready build configuration -- Only essential dependencies (tsup, typescript, @types/\*) -- Exports ESM bundle with TypeScript definitions +```tsx +"use client"; -### Demo Package +import { Timepicker } from "timepicker-ui-react"; +import "timepicker-ui/main.css"; -- Separate Vite dev server for live testing -- Independent dependency management -- Not published to npm (private: true) +export default function Page() { + return ; +} +``` -## Development +Renders a plain `` on the server, hydrates with the full picker on the client. -### Build Library +## Exported Types -```bash -npm run build +All types re-exported from `timepicker-ui`. Full list: [TypeScript docs](https://timepicker-ui.vercel.app/docs/api/typescript) + +```tsx +import type { TimepickerOptions, CallbacksOptions, ConfirmEventData, ... } from "timepicker-ui-react"; +import { TimepickerUI, EventEmitter, PluginRegistry } from "timepicker-ui-react"; ``` -### Run Demo +## Development ```bash -cd docs -npm install -npm run dev +cd src && npm run build # Build library +cd src/docs && npm run dev # Run demo ``` -Demo will be available at `http://localhost:3000` - -### Project Structure +## Contributing -- `src/Timepicker/Timepicker.tsx` - Main component with forwardRef -- `src/Timepicker/types.ts` - TypeScript interfaces (extends InputHTMLAttributes) -- `src/Timepicker/utils.ts` - SSR detection helper -- `src/Timepicker/hooks/useTimepickerInstance.ts` - Dynamic import and instance creation -- `src/Timepicker/hooks/useEventHandlers.ts` - Event attachment/detachment with callback merging -- `src/Timepicker/hooks/useTimepickerValue.ts` - Controlled value synchronization -- `src/Timepicker/hooks/useTimepickerOptions.ts` - Options update handling -- `src/Timepicker/hooks/useTimepickerCallbacks.ts` - Callback re-attachment on changes +Contributions welcome! [Open an issue or PR](https://github.com/pglejzer/timepicker-ui-react/issues). ## License -MIT - -## Links - -- [timepicker-ui GitHub](https://github.com/pglejzer/timepicker-ui) — Core library repository -- [timepicker-ui Documentation](https://timepicker-ui.vercel.app) — Full documentation for core library -- [timepicker-ui-react Documentation](https://timepicker-ui.vercel.app/react) — React wrapper documentation -- [timepicker-ui-react GitHub](https://github.com/pglejzer/timepicker-ui-react) — This repository -- [Report Issues](https://github.com/pglejzer/timepicker-ui-react/issues) — Bug reports and feature requests +MIT © [Piotr Glejzer](https://github.com/pglejzer) diff --git a/package.json b/package.json index 1c13ad3..387e4fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "timepicker-ui-react", - "version": "1.2.0", + "version": "1.3.0", "description": "Official React wrapper for timepicker-ui v4.x", "type": "module", "sideEffects": false, @@ -44,6 +44,6 @@ "react-dom": ">=17" }, "dependencies": { - "timepicker-ui": "4.2.2" + "timepicker-ui": "4.3.0" } } diff --git a/src/index.ts b/src/index.ts index fcce8ed..cf38cbd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,6 +37,8 @@ export type { WheelOptions, WheelScrollStartEventData, WheelScrollEndEventData, + TemplateProvider, + ClearHandler, } from "timepicker-ui"; export { TimepickerUI, EventEmitter, PluginRegistry } from "timepicker-ui"; diff --git a/src/package.json b/src/package.json index cc2421b..9a34978 100644 --- a/src/package.json +++ b/src/package.json @@ -13,7 +13,7 @@ "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1", - "timepicker-ui": "4.2.2" + "timepicker-ui": "4.3.0" }, "devDependencies": { "@testing-library/jest-dom": "^6.9.1",