@minutemailer/kit 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,39 +1,48 @@
1
1
  {
2
2
  "name": "@minutemailer/kit",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Minutemailer UI Kit",
5
5
  "license": "MIT",
6
6
  "author": "Minutemailer",
7
7
  "type": "module",
8
8
  "exports": {
9
9
  "./utils": "./utils/index.js",
10
- "./icons": "./icons/index.js"
10
+ "./icons": "./icons/index.js",
11
+ "./store": "./store/index.js"
11
12
  },
12
13
  "sideEffects": false,
13
14
  "scripts": {
14
- "test": "vitest",
15
+ "test": "vitest run",
15
16
  "coverage": "vitest --run --coverage",
16
17
  "start": "storybook dev -p 6006",
17
- "build": "tsc && node esbuild.config.js",
18
+ "build": "npm run icons:build && tsc && node esbuild.config.js",
18
19
  "build-storybook": "storybook build",
19
20
  "icons:build": "npx @svgr/cli --icon --typescript --replace-attr-values \"#3D3D3D=currentColor\" --out-dir src/icons resources/icons",
20
21
  "prepub": "rm -r dist && npm run build && cp package.json dist",
21
- "pub": "npm run prepub && cd dist && npm publish --access public"
22
+ "pub": "npm run prepub && cd dist && npm publish --access public",
23
+ "docs": "npx typedoc src/utils --out docs --plugin typedoc-material-theme --themeColor '#00b869'"
22
24
  },
23
- "dependencies": {
24
- "react": "^19.0.0",
25
- "react-dom": "^19.0.0"
25
+ "peerDependencies": {
26
+ "react": "^18.0.0",
27
+ "react-dom": "^18.0.0"
26
28
  },
27
29
  "devDependencies": {
28
30
  "@storybook/addon-essentials": "^8.6.2",
29
31
  "@storybook/blocks": "^8.6.2",
32
+ "@storybook/manager-api": "^8.6.3",
30
33
  "@storybook/react": "^8.6.2",
31
34
  "@storybook/react-vite": "^8.6.2",
32
35
  "@storybook/test": "^8.6.2",
36
+ "@storybook/theming": "^8.6.3",
33
37
  "@svgr/cli": "^8.1.0",
38
+ "@testing-library/react": "^16.2.0",
34
39
  "@vitest/coverage-v8": "^3.0.7",
35
40
  "esbuild": "^0.25.0",
41
+ "react": "^18.0.0",
42
+ "react-dom": "^18.0.0",
36
43
  "storybook": "^8.6.2",
44
+ "typedoc": "^0.27.9",
45
+ "typedoc-material-theme": "^1.3.0",
37
46
  "vitest": "^3.0.7"
38
47
  }
39
48
  }
@@ -0,0 +1,22 @@
1
+ interface StateMachine<TContext> {
2
+ subscribe: (listener: StateChange<TContext>) => () => void;
3
+ destroy: () => void;
4
+ action: (action: string, input?: any) => void;
5
+ can: (action: string) => boolean;
6
+ useCurrentState: () => string;
7
+ useCurrentContext: <TSelected = any>(selector: (context: TContext) => TSelected) => TSelected;
8
+ getContext: () => TContext;
9
+ }
10
+ interface Transitions {
11
+ [state: string]: {
12
+ on: {
13
+ [action: string]: string;
14
+ };
15
+ };
16
+ }
17
+ interface Actions {
18
+ [action: string]: (context: any, input?: any) => any;
19
+ }
20
+ type StateChange<TContext> = (state: string, previousState: string, context: TContext) => void;
21
+ export declare function createMachine<TContext = any>(transitions: Transitions, actions: Actions, initialContext: TContext, initialState?: string): StateMachine<TContext>;
22
+ export {};
package/state/index.js ADDED
@@ -0,0 +1,53 @@
1
+ import { useEffect, useState } from 'react';
2
+ export function createMachine(transitions, actions, initialContext, initialState = 'IDLE') {
3
+ const listeners = new Set();
4
+ let state = initialState;
5
+ let context = initialContext;
6
+ return {
7
+ subscribe(listener) {
8
+ listeners.add(listener);
9
+ return () => listeners.delete(listener);
10
+ },
11
+ destroy() {
12
+ listeners.clear();
13
+ state = initialState;
14
+ context = initialContext;
15
+ },
16
+ action(action, input) {
17
+ const transition = transitions[state];
18
+ const nextState = transition.on[action];
19
+ if (!nextState) {
20
+ console.warn('Invalid transition', state, action);
21
+ return;
22
+ }
23
+ console.log(`Machine transition: ${state} -> ${nextState} (${action}, ${input})`);
24
+ const previousState = state;
25
+ if (actions[action]) {
26
+ context = actions[action].call(this, context, input) || context;
27
+ }
28
+ state = nextState;
29
+ listeners.forEach((listener) => listener(state, previousState, context));
30
+ },
31
+ can(action) {
32
+ const transition = transitions[state];
33
+ return !!transition.on[action];
34
+ },
35
+ useCurrentState() {
36
+ const [currentState, setCurrentState] = useState(state);
37
+ useEffect(() => {
38
+ return this.subscribe(() => setCurrentState(state));
39
+ }, []);
40
+ return currentState;
41
+ },
42
+ useCurrentContext(selector) {
43
+ const [currentContext, setCurrentContext] = useState(() => selector(context));
44
+ useEffect(() => {
45
+ return this.subscribe(() => setCurrentContext(selector(context)));
46
+ }, []);
47
+ return currentContext;
48
+ },
49
+ getContext() {
50
+ return context;
51
+ },
52
+ };
53
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,118 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { createMachine } from './';
3
+ // Mock timers for testing timeouts
4
+ vi.useFakeTimers();
5
+ describe('State Machine', () => {
6
+ // Test machine setup
7
+ const transitions = {
8
+ STATE1: { on: { NEXT: 'STATE2', RESET: 'STATE1' } },
9
+ STATE2: { on: { NEXT: 'STATE3', RESET: 'STATE1' } },
10
+ STATE3: { on: { NEXT: 'STATE1', RESET: 'STATE1' } },
11
+ };
12
+ const actions = {
13
+ NEXT: (context) => ({ count: context.count + 1 }),
14
+ RESET: () => ({ count: 0 }),
15
+ };
16
+ let machine;
17
+ beforeEach(() => {
18
+ machine = createMachine(transitions, actions, { count: 0 }, 'STATE1');
19
+ });
20
+ it('should initialize with the correct context', () => {
21
+ expect(machine.getContext()).toEqual({ count: 0 });
22
+ });
23
+ it('should transition to the next state when action is triggered', () => {
24
+ let currentState = 'STATE1';
25
+ // Subscribe to state changes
26
+ const unsubscribe = machine['subscribe']((newState) => {
27
+ currentState = newState;
28
+ });
29
+ machine.action('NEXT');
30
+ expect(currentState).toBe('STATE2');
31
+ unsubscribe();
32
+ });
33
+ it('should update context when action is triggered', () => {
34
+ let count = 0;
35
+ // Subscribe to context changes
36
+ const unsubscribe = machine['subscribe']((_, __, context) => {
37
+ count = context.count;
38
+ });
39
+ machine.action('NEXT');
40
+ expect(count).toBe(1);
41
+ unsubscribe();
42
+ });
43
+ it('should check if an action is valid for the current state', () => {
44
+ expect(machine.can('NEXT')).toBe(true);
45
+ expect(machine.can('INVALID')).toBe(false);
46
+ machine.action('NEXT');
47
+ // Now in STATE2
48
+ expect(machine.can('NEXT')).toBe(true);
49
+ expect(machine.can('RESET')).toBe(true);
50
+ });
51
+ it('should warn when invalid transition is attempted', () => {
52
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
53
+ machine.action('INVALID_ACTION');
54
+ expect(consoleSpy).toHaveBeenCalledWith('Invalid transition', 'STATE1', 'INVALID_ACTION');
55
+ consoleSpy.mockRestore();
56
+ });
57
+ it('should reset to initial state when destroy is called', () => {
58
+ let currentState = 'STATE1';
59
+ let currentContext = { count: 0 };
60
+ const unsubscribe = machine['subscribe']((newState, _, context) => {
61
+ currentState = newState;
62
+ currentContext = context;
63
+ });
64
+ machine.action('NEXT');
65
+ machine.action('NEXT');
66
+ // Now in STATE3 with count 2
67
+ expect(currentState).toBe('STATE3');
68
+ expect(currentContext.count).toBe(2);
69
+ machine.destroy();
70
+ // Direct check with getContext() since destroy clears subscribers
71
+ expect(machine.getContext()).toEqual({ count: 0 });
72
+ unsubscribe();
73
+ });
74
+ it('should log transitions to console', () => {
75
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
76
+ machine.action('NEXT', 'test-input');
77
+ expect(consoleSpy).toHaveBeenCalledWith('Machine transition: STATE1 -> STATE2 (NEXT, test-input)');
78
+ consoleSpy.mockRestore();
79
+ });
80
+ it('should notify subscribers when state changes', () => {
81
+ const mockListener = vi.fn();
82
+ const unsubscribe = machine['subscribe'](mockListener);
83
+ machine.action('NEXT');
84
+ expect(mockListener).toHaveBeenCalledWith('STATE2', 'STATE1', { count: 1 });
85
+ // Test unsubscribe
86
+ unsubscribe();
87
+ machine.action('NEXT');
88
+ // Should have been called only once despite two actions
89
+ expect(mockListener).toHaveBeenCalledTimes(1);
90
+ });
91
+ it('should handle context updates with nested objects', () => {
92
+ const complexMachine = createMachine({ STATE1: { on: { UPDATE: 'STATE1' } } }, {
93
+ UPDATE: (ctx) => ({
94
+ ...ctx,
95
+ user: { ...ctx.user, age: ctx.user.age + 1 }
96
+ })
97
+ }, { user: { name: 'Test', age: 30 }, settings: { theme: 'dark' } }, 'STATE1');
98
+ let name = '';
99
+ let age = 0;
100
+ let theme = '';
101
+ const unsubscribe = complexMachine['subscribe']((_, __, context) => {
102
+ name = context.user.name;
103
+ age = context.user.age;
104
+ theme = context.settings.theme;
105
+ });
106
+ // Initial values from initialization
107
+ expect(complexMachine.getContext()).toEqual({
108
+ user: { name: 'Test', age: 30 },
109
+ settings: { theme: 'dark' }
110
+ });
111
+ complexMachine.action('UPDATE');
112
+ // Only age should change
113
+ expect(name).toBe('Test');
114
+ expect(age).toBe(31);
115
+ expect(theme).toBe('dark');
116
+ unsubscribe();
117
+ });
118
+ });
@@ -0,0 +1,2 @@
1
+ export * from './store';
2
+ export { useSelector } from './useSelector';
package/store/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from "./store";
2
+ import { useSelector } from "./useSelector";
3
+ export {
4
+ useSelector
5
+ };
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/store/index.ts"],
4
+ "sourcesContent": ["// Reexport all from the store modules\nexport * from './store';\nexport { useSelector } from './useSelector';"],
5
+ "mappings": "AACA,cAAc;AACd,SAAS,mBAAmB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,17 @@
1
+ export type Action = {
2
+ type: string;
3
+ value?: any;
4
+ };
5
+ export interface Store<T> {
6
+ subscribe: (listener: StateChange<T>) => () => void;
7
+ reset: (persistent?: boolean) => void;
8
+ dispatch: (action: Action, cb?: () => void) => void;
9
+ select<S>(selector: (state: T) => S): S;
10
+ getState: () => T;
11
+ }
12
+ export type StateChange<T> = (state: T, prevState: T, action: Action) => void;
13
+ export declare function getStore<T>(id: string): Store<T> | undefined;
14
+ export declare function createStore<T>(id: string, initialState: T, reducer: (state: T, action: any) => T, shouldUpdate?: (prevState: T, state: T) => boolean, onStateChange?: StateChange<T>, persistent?: string | boolean): Store<T>;
15
+ export declare function deleteStore(id: string, persistent?: boolean): void;
16
+ export declare function resetAllStores(exceptions?: string[], persistent?: boolean): void;
17
+ export declare function deleteAllStores(exceptions?: string[], persistent?: boolean): void;
package/store/store.js ADDED
@@ -0,0 +1,87 @@
1
+ const stores = {};
2
+ function loadPersistentState(id, key) {
3
+ try {
4
+ return JSON.parse(localStorage.getItem(key) || '');
5
+ }
6
+ catch (error) {
7
+ console.error(`Failed to load persistent state for ${id}`, error);
8
+ }
9
+ return null;
10
+ }
11
+ function savePersistentState(id, key, state) {
12
+ try {
13
+ localStorage.setItem(key, JSON.stringify(state));
14
+ }
15
+ catch (error) {
16
+ console.error(`Failed to save persistent state for ${id}`, error);
17
+ }
18
+ }
19
+ export function getStore(id) {
20
+ if (!stores[id]) {
21
+ console.warn(`Store ${id} does not exist`, stores);
22
+ }
23
+ return stores[id];
24
+ }
25
+ export function createStore(id, initialState, reducer, shouldUpdate = () => true, onStateChange = () => { }, persistent = false) {
26
+ if (stores[id]) {
27
+ return stores[id];
28
+ }
29
+ const listeners = new Set();
30
+ let state = initialState;
31
+ if (persistent) {
32
+ const persistentState = loadPersistentState(id, `${persistent}.${id}`);
33
+ if (persistentState) {
34
+ state = persistentState;
35
+ }
36
+ }
37
+ stores[id] = {
38
+ subscribe(listener) {
39
+ listeners.add(listener);
40
+ return () => {
41
+ listeners.delete(listener);
42
+ };
43
+ },
44
+ reset(persistent = false) {
45
+ listeners.clear();
46
+ state = initialState;
47
+ if (persistent) {
48
+ localStorage.removeItem(`${persistent}.${id}`);
49
+ }
50
+ },
51
+ dispatch(action, cb = () => { }) {
52
+ const prevState = state;
53
+ state = reducer(state, action);
54
+ if (persistent) {
55
+ savePersistentState(id, `${persistent}.${id}`, state);
56
+ }
57
+ if (shouldUpdate(prevState, state)) {
58
+ listeners.forEach((listener) => listener(state, prevState, action));
59
+ onStateChange(state, prevState, action);
60
+ cb(state, prevState, action);
61
+ }
62
+ },
63
+ select(selector) {
64
+ return selector(state);
65
+ },
66
+ getState() {
67
+ return state;
68
+ },
69
+ };
70
+ return stores[id];
71
+ }
72
+ export function deleteStore(id, persistent = false) {
73
+ if (persistent) {
74
+ localStorage.removeItem(`${persistent}.${id}`);
75
+ }
76
+ delete stores[id];
77
+ }
78
+ export function resetAllStores(exceptions = [], persistent = false) {
79
+ Object.keys(stores)
80
+ .filter((id) => !exceptions.includes(id))
81
+ .forEach((id) => stores[id].reset(persistent));
82
+ }
83
+ export function deleteAllStores(exceptions = [], persistent = false) {
84
+ Object.keys(stores)
85
+ .filter((id) => !exceptions.includes(id))
86
+ .forEach((id) => deleteStore(id, persistent));
87
+ }
@@ -0,0 +1,12 @@
1
+ import { Store } from './store';
2
+ interface TestStore {
3
+ todos: Array<{
4
+ id: string;
5
+ text: string;
6
+ completed: boolean;
7
+ }>;
8
+ completed: number;
9
+ total: number;
10
+ }
11
+ export declare function createStoreInstance(): Store<TestStore>;
12
+ export {};
@@ -0,0 +1,51 @@
1
+ import { assert, assertType, test } from 'vitest';
2
+ import { createStore } from './store';
3
+ const initialState = {
4
+ todos: [],
5
+ completed: 0,
6
+ total: 0,
7
+ };
8
+ const uniqueId = () => Math.random().toString(36).substring(2, 15);
9
+ const reducer = (state, action) => {
10
+ if (action.type === 'ADD_TODO') {
11
+ return {
12
+ ...state,
13
+ todos: [
14
+ ...state.todos,
15
+ { id: uniqueId(), text: action.value, completed: false },
16
+ ],
17
+ total: state.total + 1,
18
+ };
19
+ }
20
+ if (action.type === 'COMPLETE_TODO') {
21
+ return {
22
+ ...state,
23
+ todos: state.todos.map((todo) => todo.id === action.value ? { ...todo, completed: true } : todo),
24
+ completed: state.completed + 1,
25
+ };
26
+ }
27
+ if (action.type === 'UNCOMPLETE_TODO') {
28
+ return {
29
+ ...state,
30
+ todos: state.todos.map((todo) => todo.id === action.value ? { ...todo, completed: false } : todo),
31
+ completed: state.completed - 1,
32
+ };
33
+ }
34
+ return state;
35
+ };
36
+ export function createStoreInstance() {
37
+ return createStore('todos', initialState, reducer, () => true, () => { });
38
+ }
39
+ test('createStore returns store', () => {
40
+ const store = createStoreInstance();
41
+ assertType(store);
42
+ });
43
+ test('store.getState returns state', () => {
44
+ const store = createStoreInstance();
45
+ assert.equal(store.getState(), initialState);
46
+ });
47
+ test('store.select returns state', () => {
48
+ const store = createStoreInstance();
49
+ const scope = store.select(({ total }) => total);
50
+ assert.equal(scope, initialState.total);
51
+ });
@@ -0,0 +1,2 @@
1
+ import type { Store } from "@/store";
2
+ export declare function useSelector<T, S>(store: Store<T>, selector: (state: T) => S): S;
@@ -0,0 +1,8 @@
1
+ import { useEffect, useState } from "react";
2
+ export function useSelector(store, selector) {
3
+ const [selectedState, setSelectedState] = useState(() => store.select(selector));
4
+ useEffect(() => {
5
+ return store.subscribe(() => setSelectedState(store.select(selector)));
6
+ }, []);
7
+ return selectedState;
8
+ }
@@ -0,0 +1 @@
1
+ export default function Todo(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,71 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { createStore, useSelector } from "@/store";
3
+ import { useState } from "react";
4
+ const initialState = {
5
+ todos: [],
6
+ completed: 0,
7
+ total: 0,
8
+ };
9
+ const uniqueId = () => Math.random().toString(36).substring(2, 15);
10
+ const reducer = (state, action) => {
11
+ if (action.type === 'ADD_TODO') {
12
+ return {
13
+ ...state,
14
+ todos: [
15
+ ...state.todos,
16
+ { id: uniqueId(), text: action.value, completed: false },
17
+ ],
18
+ total: state.total + 1,
19
+ };
20
+ }
21
+ if (action.type === 'COMPLETE_TODO') {
22
+ return {
23
+ ...state,
24
+ todos: state.todos.map((todo) => todo.id === action.value ? { ...todo, completed: true } : todo),
25
+ completed: state.completed + 1,
26
+ };
27
+ }
28
+ if (action.type === 'UNCOMPLETE_TODO') {
29
+ return {
30
+ ...state,
31
+ todos: state.todos.map((todo) => todo.id === action.value ? { ...todo, completed: false } : todo),
32
+ completed: state.completed - 1,
33
+ };
34
+ }
35
+ return state;
36
+ };
37
+ const store = createStore('todos', initialState, reducer, () => true, () => {
38
+ }, 'todos');
39
+ function Todos() {
40
+ const todos = useSelector(store, (state) => state.todos);
41
+ return (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: '1rem' }, children: todos.map((todo) => (_jsx("div", { style: { display: 'flex', alignItems: 'center', gap: '1rem' }, children: _jsxs("div", { className: "flex items-center space-x-3", children: [_jsx("button", { type: "button", role: "checkbox", "aria-checked": todo.completed, "data-state": todo.completed ? 'checked' : 'unchecked', value: "on", className: "peer size-5 shrink-0 rounded-sm border border-input ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-accent-foreground", id: "remember", tabIndex: 3, onClick: () => store.dispatch({
42
+ type: !todo.completed
43
+ ? 'COMPLETE_TODO'
44
+ : 'UNCOMPLETE_TODO',
45
+ value: todo.id,
46
+ }), children: todo.completed && (_jsx("span", { "data-state": "checked", className: "flex items-center justify-center text-current", style: { pointerEvents: 'none' }, children: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", className: "lucide lucide-check size-3.5 stroke-[3]", children: _jsx("path", { d: "M20 6 9 17l-5-5" }) }) })) }), _jsx("input", { "aria-hidden": "true", tabIndex: -1, type: "checkbox", value: "on", style: {
47
+ transform: 'translateX(-100%)',
48
+ position: 'absolute',
49
+ pointerEvents: 'none',
50
+ opacity: 0,
51
+ margin: 0,
52
+ width: '20px', height: '20px'
53
+ } }), _jsx("label", { className: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", htmlFor: "remember", children: todo.text })] }) }, todo.id))) }));
54
+ }
55
+ function Status() {
56
+ const total = useSelector(store, (state) => state.total);
57
+ const completed = useSelector(store, (state) => state.completed);
58
+ return _jsx("p", { className: "mt-4 text-sm text-muted-foreground", children: `${completed} / ${total} todos completed` });
59
+ }
60
+ function Form() {
61
+ const [value, setValue] = useState('');
62
+ function onSubmit(e) {
63
+ e.preventDefault();
64
+ store.dispatch({ type: 'ADD_TODO', value: value });
65
+ setValue('');
66
+ }
67
+ return (_jsxs("form", { onSubmit: onSubmit, className: "flex gap-2 mb-6", children: [_jsx("input", { className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", placeholder: "Add a new todo", value: value, onChange: (e) => setValue(e.target.value) }), _jsx("button", { className: "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 h-10 px-4 py-2 bg-black/80 hover:bg-black/70 text-white backdrop-blur-xl", type: "submit", children: "Add" })] }));
68
+ }
69
+ export default function Todo() {
70
+ return (_jsx("div", { children: _jsxs("div", { className: "max-w-md mx-auto p-6 bg-white rounded-lg border", children: [_jsx("h1", { className: "text-2xl font-bold text-center mb-6 sb-unstyled", children: "My Todo List" }), _jsx(Form, {}), _jsx(Todos, {}), _jsx(Status, {})] }) }));
71
+ }
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ declare const SimpleTrafficLight: React.FC;
3
+ export default SimpleTrafficLight;
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { createMachine } from '@/state';
4
+ // Simple traffic light demo with minimal code
5
+ const SimpleTrafficLight = () => {
6
+ // Define our states, transitions and actions
7
+ const transitions = {
8
+ RED: { on: { NEXT: 'GREEN' } },
9
+ GREEN: { on: { NEXT: 'YELLOW' } },
10
+ YELLOW: { on: { NEXT: 'RED' } }
11
+ };
12
+ const actions = {
13
+ NEXT: (context) => ({
14
+ count: context.count + 1
15
+ })
16
+ };
17
+ // Create the machine with minimal context
18
+ const [machine] = useState(() => createMachine(transitions, actions, { count: 0 }, 'RED'));
19
+ // Get current state and count from the machine
20
+ const currentState = machine.useCurrentState();
21
+ const count = machine.useCurrentContext(ctx => ctx.count);
22
+ // Auto-transition every 1.5 seconds
23
+ useEffect(() => {
24
+ const timer = setTimeout(() => {
25
+ machine.action('NEXT');
26
+ }, 1500);
27
+ return () => clearTimeout(timer);
28
+ }, [currentState, machine]);
29
+ // Color mapping
30
+ const colors = {
31
+ RED: 'bg-rose-500',
32
+ YELLOW: 'bg-amber-400',
33
+ GREEN: 'bg-emerald-500'
34
+ };
35
+ return (_jsxs("div", { className: "flex flex-col items-start p-4", children: [_jsx("div", { className: "bg-gray-800 p-3 rounded-lg mb-4 flex flex-col items-start gap-2", children: Object.entries(colors).map(([state, color]) => (_jsx("div", { className: `w-16 h-16 m-2 rounded-full ${currentState === state ? color : 'bg-gray-300'}` }, state))) }), _jsxs("div", { children: [_jsxs("p", { children: ["Current: ", _jsx("strong", { children: currentState })] }), _jsxs("p", { children: ["Cycles: ", count] }), _jsx("button", { onClick: () => machine.action('NEXT'), className: "mt-2 bg-blue-500 text-white px-4 py-2 rounded", children: "Next State" })] })] }));
36
+ };
37
+ export default SimpleTrafficLight;
@@ -1 +1 @@
1
- export default function choice(string: any, value?: null): any;
1
+ export default function choice(string: string, value?: number | null): string;
@@ -14,7 +14,8 @@ export default function choice(string, value = null) {
14
14
  const [start, end] = bracketMatch[1]
15
15
  .split(',')
16
16
  .map((item) => (item === 'Inf' ? item : parseInt(item, 10)));
17
- if (value >= start &&
17
+ if (typeof start === 'number' &&
18
+ value >= start &&
18
19
  (end === 'Inf' || value < end || end === undefined)) {
19
20
  foundValue = option.replace(brackets, '').trim();
20
21
  }
@@ -1,5 +1,5 @@
1
1
  import { assert, test } from 'vitest';
2
- import choice from './index.js';
2
+ import choice from './index';
3
3
  test('No processing if no value is given', () => {
4
4
  assert.equal(choice('foo'), 'foo');
5
5
  assert.equal(choice('foo|bar'), 'foo|bar');
package/utils/index.d.ts CHANGED
@@ -2,5 +2,5 @@ export { default as strToDate } from './strToDate';
2
2
  export { default as zeroPad } from './zeroPad';
3
3
  export { default as replacePlaceholders } from './replacePlaceholders';
4
4
  export { default as choice } from './choice';
5
- export { default as capitalize } from './capitalize/index.js';
5
+ export { default as capitalize } from './capitalize';
6
6
  export { default as objToQuery } from './objToQuery';
package/utils/index.js CHANGED
@@ -2,7 +2,7 @@ import { default as default2 } from "./strToDate";
2
2
  import { default as default3 } from "./zeroPad";
3
3
  import { default as default4 } from "./replacePlaceholders";
4
4
  import { default as default5 } from "./choice";
5
- import { default as default6 } from "./capitalize/index.js";
5
+ import { default as default6 } from "./capitalize";
6
6
  import { default as default7 } from "./objToQuery";
7
7
  export {
8
8
  default6 as capitalize,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/index.ts"],
4
- "sourcesContent": ["export { default as strToDate } from './strToDate';\nexport { default as zeroPad } from './zeroPad';\nexport { default as replacePlaceholders } from './replacePlaceholders';\nexport { default as choice } from './choice';\nexport { default as capitalize } from './capitalize/index.js';\nexport { default as objToQuery } from './objToQuery';\n"],
4
+ "sourcesContent": ["export { default as strToDate } from './strToDate';\nexport { default as zeroPad } from './zeroPad';\nexport { default as replacePlaceholders } from './replacePlaceholders';\nexport { default as choice } from './choice';\nexport { default as capitalize } from './capitalize';\nexport { default as objToQuery } from './objToQuery';\n"],
5
5
  "mappings": "AAAA,SAAoB,WAAXA,gBAA4B;AACrC,SAAoB,WAAXA,gBAA0B;AACnC,SAAoB,WAAXA,gBAAsC;AAC/C,SAAoB,WAAXA,gBAAyB;AAClC,SAAoB,WAAXA,gBAA6B;AACtC,SAAoB,WAAXA,gBAA6B;",
6
6
  "names": ["default"]
7
7
  }
@@ -1 +1 @@
1
- export default function objToQuery(obj: any, exclude?: any[]): any;
1
+ export default function objToQuery(obj: Record<string, any>, exclude?: string[]): string;
@@ -1,5 +1,5 @@
1
1
  import { assert, test } from 'vitest';
2
- import objToQuery from './index.js';
2
+ import objToQuery from './index';
3
3
  test('Returns string', () => {
4
4
  assert.isString(objToQuery({}));
5
5
  });
@@ -1 +1 @@
1
- export default function replacePlaceholders(string: any, placeholders?: {}): any;
1
+ export default function replacePlaceholders(string: string, placeholders?: Record<string, any>): string;
@@ -1,5 +1,5 @@
1
1
  import { assert, test } from 'vitest';
2
- import replacePlaceholders from './index.js';
2
+ import replacePlaceholders from './index';
3
3
  test('Returns string', () => {
4
4
  assert.isString(replacePlaceholders('foo', {}));
5
5
  });
@@ -1 +1 @@
1
- export default function strToDate(str: any): Date | null;
1
+ export default function strToDate(str?: any): Date | null;
@@ -1,5 +1,5 @@
1
1
  import { assert, test } from 'vitest';
2
- import strToDate from './index.js';
2
+ import strToDate from './index';
3
3
  test('strToDate returns Date for Date', () => {
4
4
  assert.instanceOf(strToDate(new Date()), Date);
5
5
  const date = new Date();
@@ -24,12 +24,18 @@ test('strToDate returns null for invalid input', () => {
24
24
  });
25
25
  test('strToDate returns correct date', () => {
26
26
  const date = strToDate('2020-01-01');
27
+ if (!date) {
28
+ throw new Error('Date is null');
29
+ }
27
30
  assert.equal(date.getFullYear(), 2020);
28
31
  assert.equal(date.getMonth(), 0);
29
32
  assert.equal(date.getDate(), 1);
30
33
  });
31
34
  test('strToDate returns correct date and time', () => {
32
35
  const date = strToDate('2020-01-01 20:00:00');
36
+ if (!date) {
37
+ throw new Error('Date is null');
38
+ }
33
39
  assert.equal(date.getFullYear(), 2020);
34
40
  assert.equal(date.getMonth(), 0);
35
41
  assert.equal(date.getDate(), 1);
@@ -1,5 +1,5 @@
1
1
  import { assert, test } from 'vitest';
2
- import zeroPad from './index.js';
2
+ import zeroPad from './index';
3
3
  test('zeroPad returns string', () => {
4
4
  assert.isString(zeroPad(1));
5
5
  });