@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.
- package/dist/components/expandable-section.d.ts +37 -0
- package/dist/components/expandable-section.d.ts.map +1 -0
- package/dist/hooks/use-service-worker.d.ts +2 -0
- package/dist/hooks/use-service-worker.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +125 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +119 -4
- package/dist/index.mjs.map +1 -1
- package/dist/lib/formatting.d.ts +27 -0
- package/dist/lib/formatting.d.ts.map +1 -0
- package/package.json +2 -1
- package/src/components/expandable-section.tsx +106 -0
- package/src/components/input.tsx +1 -1
- package/src/components/select.tsx +1 -1
- package/src/components/textarea.tsx +1 -1
- package/src/hooks/use-service-worker.ts +23 -0
- package/src/index.ts +10 -0
- package/src/lib/formatting.ts +51 -0
|
@@ -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.
|
|
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'
|
package/src/components/input.tsx
CHANGED
|
@@ -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
|
+
}
|