@dilipod/ui 0.4.26 → 0.4.28

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.
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Formatting Utilities
3
+ *
4
+ * Shared text, currency, duration, and date formatting used across apps.
5
+ */
6
+ /**
7
+ * Convert a cent value to euros (numeric).
8
+ * e.g. 2900 → 29
9
+ */
10
+ export declare function formatCentsToEuros(cents: number): string;
11
+ /**
12
+ * Format a euro value as a display string.
13
+ * e.g. 299 → "€299" or 299.5 with decimals=2 → "€299.50"
14
+ */
15
+ export declare function formatEuros(euros: number, decimals?: number): string;
16
+ /**
17
+ * Format milliseconds as a human-readable duration.
18
+ * e.g. 1500 → "1.5s"
19
+ */
20
+ export declare function formatDuration(ms: number): string;
21
+ /**
22
+ * Format a date into a compact relative-time string.
23
+ * Returns "—" for null/undefined, "5m" for < 1 hour, "2h" for < 48 hours,
24
+ * or a relative distance like "3 days" for older dates.
25
+ */
26
+ export declare function formatRelativeTime(date: Date | string | null): string;
27
+ //# sourceMappingURL=formatting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatting.d.ts","sourceRoot":"","sources":["../../src/lib/formatting.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAKpE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM,CAUrE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dilipod/ui",
3
- "version": "0.4.26",
3
+ "version": "0.4.28",
4
4
  "description": "Dilipod Design System - Shared UI components and styles",
5
5
  "author": "Dilipod <hello@dilipod.com>",
6
6
  "license": "MIT",
@@ -81,6 +81,7 @@
81
81
  "@xyflow/react": "^12.10.0",
82
82
  "class-variance-authority": "^0.7.1",
83
83
  "clsx": "^2.1.1",
84
+ "date-fns": "^4.1.0",
84
85
  "tailwind-merge": "^3.3.0",
85
86
  "tailwindcss-animate": "^1.0.7"
86
87
  },
@@ -0,0 +1,106 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { cn } from '../lib/utils'
5
+
6
+ export interface ExpandableSectionProps {
7
+ /** Unique key for this section */
8
+ sectionKey: string
9
+ /** Section label text */
10
+ label: string
11
+ /** Optional icon rendered before the label */
12
+ icon?: React.ReactNode
13
+ /** Optional count badge after the label */
14
+ count?: number
15
+ /** Whether the section is currently expanded */
16
+ expanded: boolean
17
+ /** Toggle callback */
18
+ onToggle: (key: string) => void
19
+ /** Section content */
20
+ children: React.ReactNode
21
+ /** Additional class for the outer container */
22
+ className?: string
23
+ /** Additional class for the content area */
24
+ contentClassName?: string
25
+ /** Whether to render the section at all (default: true) */
26
+ show?: boolean
27
+ }
28
+
29
+ export function ExpandableSection({
30
+ sectionKey,
31
+ label,
32
+ icon,
33
+ count,
34
+ expanded,
35
+ onToggle,
36
+ children,
37
+ className,
38
+ contentClassName,
39
+ show = true,
40
+ }: ExpandableSectionProps) {
41
+ if (!show) return null
42
+
43
+ return (
44
+ <div className={className}>
45
+ <button
46
+ onClick={() => onToggle(sectionKey)}
47
+ className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors w-full text-left"
48
+ type="button"
49
+ >
50
+ <svg
51
+ width="12"
52
+ height="12"
53
+ viewBox="0 0 12 12"
54
+ fill="none"
55
+ className={cn(
56
+ 'shrink-0 transition-transform',
57
+ expanded ? 'rotate-0' : '-rotate-90'
58
+ )}
59
+ >
60
+ <path d="M2.5 4.5L6 8L9.5 4.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
61
+ </svg>
62
+ {icon}
63
+ <span>{label}</span>
64
+ {count !== undefined && (
65
+ <span className="text-muted-foreground/60">({count})</span>
66
+ )}
67
+ </button>
68
+ {expanded && (
69
+ <div className={cn('mt-2 pl-4', contentClassName)}>
70
+ {children}
71
+ </div>
72
+ )}
73
+ </div>
74
+ )
75
+ }
76
+
77
+ /**
78
+ * Hook to manage expandable section state.
79
+ * Returns [expandedSections, toggleSection] tuple.
80
+ */
81
+ export function useExpandedSections(initialExpanded: string[] = []) {
82
+ const [expanded, setExpanded] = React.useState<Set<string>>(
83
+ () => new Set(initialExpanded)
84
+ )
85
+
86
+ const toggle = React.useCallback((key: string) => {
87
+ setExpanded(prev => {
88
+ const next = new Set(prev)
89
+ if (next.has(key)) {
90
+ next.delete(key)
91
+ } else {
92
+ next.add(key)
93
+ }
94
+ return next
95
+ })
96
+ }, [])
97
+
98
+ const isExpanded = React.useCallback(
99
+ (key: string) => expanded.has(key),
100
+ [expanded]
101
+ )
102
+
103
+ return { expanded, toggle, isExpanded } as const
104
+ }
105
+
106
+ ExpandableSection.displayName = 'ExpandableSection'
@@ -15,7 +15,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
15
15
  <input
16
16
  type={type}
17
17
  className={cn(
18
- 'flex h-10 w-full rounded-sm border bg-white px-3 py-2 text-base text-[var(--black)] file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-[var(--black)] placeholder:text-gray-500 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm transition-colors',
18
+ 'flex h-10 w-full rounded-sm border bg-white px-3 py-2 text-base text-[var(--black)] file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-[var(--black)] placeholder:text-gray-500 focus-visible:outline-none focus-visible:border-gray-500 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm transition-colors',
19
19
  error
20
20
  ? 'border-red-500'
21
21
  : 'border-gray-300',
@@ -17,7 +17,7 @@ const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
17
17
  className={cn(
18
18
  'h-10 w-full rounded-sm border bg-white px-3 py-2 text-base text-[var(--black)]',
19
19
  'placeholder:text-gray-500',
20
- 'focus-visible:outline-none',
20
+ 'focus-visible:outline-none focus-visible:border-gray-500',
21
21
  'disabled:cursor-not-allowed disabled:opacity-50 md:text-sm transition-colors',
22
22
  'appearance-none pr-10',
23
23
  error
@@ -14,7 +14,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
14
14
  return (
15
15
  <textarea
16
16
  className={cn(
17
- 'flex min-h-[80px] w-full rounded-sm border bg-white px-3 py-2 text-base text-[var(--black)] placeholder:text-gray-500 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm transition-colors resize-none',
17
+ 'flex min-h-[80px] w-full rounded-sm border bg-white px-3 py-2 text-base text-[var(--black)] placeholder:text-gray-500 focus-visible:outline-none focus-visible:border-gray-500 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm transition-colors resize-none',
18
18
  error
19
19
  ? 'border-red-500'
20
20
  : 'border-gray-300',
@@ -0,0 +1,23 @@
1
+ 'use client'
2
+
3
+ import { useEffect } from 'react'
4
+
5
+ export function useServiceWorker() {
6
+ useEffect(() => {
7
+ if ('serviceWorker' in navigator) {
8
+ navigator.serviceWorker
9
+ .register('/sw.js')
10
+ .then((registration) => {
11
+ console.log('Service Worker registered:', registration.scope)
12
+
13
+ // Check for updates periodically
14
+ setInterval(() => {
15
+ registration.update()
16
+ }, 60 * 60 * 1000) // Check every hour
17
+ })
18
+ .catch((error) => {
19
+ console.error('Service Worker registration failed:', error)
20
+ })
21
+ }
22
+ }, [])
23
+ }
package/src/index.ts CHANGED
@@ -305,6 +305,16 @@ export type { WorkerSpecProps, WorkerSpecDocumentation, AnalysisSource, Analysis
305
305
  export { FlowchartDiagram } from './components/flowchart-diagram'
306
306
  export type { FlowchartDiagramProps } from './components/flowchart-diagram'
307
307
 
308
+ // Expandable Section
309
+ export { ExpandableSection, useExpandedSections } from './components/expandable-section'
310
+ export type { ExpandableSectionProps } from './components/expandable-section'
311
+
312
+ // Hooks
313
+ export { useServiceWorker } from './hooks/use-service-worker'
314
+
315
+ // Formatting Utilities
316
+ export { formatCentsToEuros, formatEuros, formatDuration, formatRelativeTime } from './lib/formatting'
317
+
308
318
  // Utilities
309
319
  export { cn } from './lib/utils'
310
320
 
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Formatting Utilities
3
+ *
4
+ * Shared text, currency, duration, and date formatting used across apps.
5
+ */
6
+
7
+ import { differenceInHours, differenceInMinutes, formatDistanceToNow } from 'date-fns'
8
+
9
+ /**
10
+ * Convert a cent value to euros (numeric).
11
+ * e.g. 2900 → 29
12
+ */
13
+ export function formatCentsToEuros(cents: number): string {
14
+ return `€${(cents / 100).toLocaleString()}`
15
+ }
16
+
17
+ /**
18
+ * Format a euro value as a display string.
19
+ * e.g. 299 → "€299" or 299.5 with decimals=2 → "€299.50"
20
+ */
21
+ export function formatEuros(euros: number, decimals?: number): string {
22
+ if (decimals !== undefined) {
23
+ return `€${euros.toFixed(decimals)}`
24
+ }
25
+ return `€${euros.toLocaleString()}`
26
+ }
27
+
28
+ /**
29
+ * Format milliseconds as a human-readable duration.
30
+ * e.g. 1500 → "1.5s"
31
+ */
32
+ export function formatDuration(ms: number): string {
33
+ return `${(ms / 1000).toFixed(1)}s`
34
+ }
35
+
36
+ /**
37
+ * Format a date into a compact relative-time string.
38
+ * Returns "—" for null/undefined, "5m" for < 1 hour, "2h" for < 48 hours,
39
+ * or a relative distance like "3 days" for older dates.
40
+ */
41
+ export function formatRelativeTime(date: Date | string | null): string {
42
+ if (!date) return '—'
43
+ const d = typeof date === 'string' ? new Date(date) : date
44
+ const hours = differenceInHours(new Date(), d)
45
+ if (hours < 1) {
46
+ const mins = differenceInMinutes(new Date(), d)
47
+ return `${mins}m`
48
+ }
49
+ if (hours < 48) return `${hours}h`
50
+ return formatDistanceToNow(d, { addSuffix: false })
51
+ }