@apptimate/ui 2.3.0 → 2.5.0
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 +1 -1
- package/src/base-components/DateLabel.tsx +6 -2
- package/src/base-components/Dropdown.tsx +5 -4
- package/src/base-components/Input.tsx +2 -2
- package/src/base-components/Select.tsx +1 -1
- package/src/base-components/TableMobileOptions.tsx +21 -16
- package/src/common-components/DashboardLayout.tsx +2 -2
- package/src/common-components/PaymentSection.tsx +4 -4
package/package.json
CHANGED
|
@@ -3,17 +3,21 @@ import React from 'react';
|
|
|
3
3
|
export interface DateLabelProps {
|
|
4
4
|
date: string | Date;
|
|
5
5
|
className?: string;
|
|
6
|
+
isDateOnly?: boolean;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
|
-
export function DateLabel({ date, className = '' }: DateLabelProps) {
|
|
9
|
+
export function DateLabel({ date, className = '', isDateOnly: forceDateOnly = false }: DateLabelProps) {
|
|
9
10
|
if (!date) return <span className="text-gray-300">-</span>;
|
|
10
11
|
|
|
11
|
-
let isDateOnly =
|
|
12
|
+
let isDateOnly = forceDateOnly;
|
|
12
13
|
let parsedDate: string | Date = date;
|
|
13
14
|
|
|
14
15
|
if (typeof date === 'string') {
|
|
15
16
|
if (/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
16
17
|
isDateOnly = true;
|
|
18
|
+
} else if (date.includes('T00:00:00.000000Z') || date.includes('T00:00:00.000Z')) {
|
|
19
|
+
isDateOnly = true;
|
|
20
|
+
date = date.split('T')[0];
|
|
17
21
|
} else if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(date)) {
|
|
18
22
|
parsedDate = date.replace(' ', 'T') + 'Z';
|
|
19
23
|
}
|
|
@@ -6,14 +6,15 @@ import { cn } from '@apptimate/core-lib';
|
|
|
6
6
|
|
|
7
7
|
interface DropdownProps {
|
|
8
8
|
trigger: ReactNode;
|
|
9
|
-
children: ReactNode;
|
|
9
|
+
children: ReactNode | ((close: () => void) => ReactNode);
|
|
10
10
|
align?: 'left' | 'right';
|
|
11
11
|
valign?: 'top' | 'bottom';
|
|
12
12
|
className?: string;
|
|
13
13
|
contentClassName?: string;
|
|
14
|
+
closeOnContentClick?: boolean;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export function Dropdown({ trigger, children, align = 'left', valign = 'bottom', className, contentClassName }: DropdownProps) {
|
|
17
|
+
export function Dropdown({ trigger, children, align = 'left', valign = 'bottom', className, contentClassName, closeOnContentClick = true }: DropdownProps) {
|
|
17
18
|
const [isOpen, setIsOpen] = useState(false);
|
|
18
19
|
const [coords, setCoords] = useState({ top: 0, bottom: 0, left: 0, right: 0 });
|
|
19
20
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
@@ -99,9 +100,9 @@ export function Dropdown({ trigger, children, align = 'left', valign = 'bottom',
|
|
|
99
100
|
valign === 'bottom' ? "mt-1.5" : "mb-1.5",
|
|
100
101
|
contentClassName
|
|
101
102
|
)}
|
|
102
|
-
onClick={closeDropdown}
|
|
103
|
+
onClick={closeOnContentClick ? closeDropdown : undefined}
|
|
103
104
|
>
|
|
104
|
-
{children}
|
|
105
|
+
{typeof children === 'function' ? children(closeDropdown) : children}
|
|
105
106
|
</div>,
|
|
106
107
|
document.body
|
|
107
108
|
)}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import React, { useState } from 'react';
|
|
4
3
|
import { cn } from '@apptimate/core-lib';
|
|
5
4
|
import { Eye, EyeOff } from 'lucide-react';
|
|
5
|
+
import React, { useState } from 'react';
|
|
6
6
|
import { Label } from './Label';
|
|
7
7
|
|
|
8
8
|
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
@@ -35,7 +35,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
35
35
|
ref={ref}
|
|
36
36
|
type={inputType}
|
|
37
37
|
className={cn(
|
|
38
|
-
|
|
38
|
+
"w-full bg-surface-0 border-[1.5px] border-border-subtle rounded-[10px] px-3.5 py-2.5 text-[13.5px] text-foreground-1 outline-none transition-all hover:border-gray-300 focus:border-gray-300 focus:bg-surface-1 placeholder:text-foreground-disabled",
|
|
39
39
|
icon && "pl-10",
|
|
40
40
|
isPassword && "pr-10",
|
|
41
41
|
error && "border-danger-alt focus:border-danger-alt hover:border-danger-alt"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import React from 'react';
|
|
4
3
|
import { cn } from '@apptimate/core-lib';
|
|
5
4
|
import { ChevronDown } from 'lucide-react';
|
|
5
|
+
import React from 'react';
|
|
6
6
|
import { Label } from './Label';
|
|
7
7
|
|
|
8
8
|
export interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {
|
|
@@ -9,7 +9,7 @@ export interface TableMobileOptionsProps {
|
|
|
9
9
|
searchPlaceholder?: string;
|
|
10
10
|
searchLabel?: string;
|
|
11
11
|
actionsLabel?: string;
|
|
12
|
-
children?: React.ReactNode;
|
|
12
|
+
children?: React.ReactNode | ((close: () => void) => React.ReactNode);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export const TableMobileOptions = ({
|
|
@@ -24,27 +24,32 @@ export const TableMobileOptions = ({
|
|
|
24
24
|
<Dropdown
|
|
25
25
|
align="right"
|
|
26
26
|
contentClassName="w-[280px] p-2"
|
|
27
|
+
closeOnContentClick={false}
|
|
27
28
|
trigger={
|
|
28
29
|
<button className="flex items-center justify-center gap-2 px-3 py-2 rounded-[10px] bg-white border border-gray-200 text-gray-700 hover:bg-gray-50 transition-colors shadow-sm font-bold text-[13px]">
|
|
29
30
|
<Settings2 size={16} /> Options
|
|
30
31
|
</button>
|
|
31
32
|
}
|
|
32
33
|
>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
34
|
+
{(close) => (
|
|
35
|
+
<>
|
|
36
|
+
<div className="px-2 pb-2 pt-1 mb-1 border-b border-gray-100">
|
|
37
|
+
<p className="text-[11px] font-bold text-gray-400 uppercase tracking-wider mb-2">{searchLabel}</p>
|
|
38
|
+
<Input
|
|
39
|
+
placeholder={searchPlaceholder}
|
|
40
|
+
icon={<Search size={16} className="text-gray-400" />}
|
|
41
|
+
className="[&_input]:rounded-[8px] [&_input]:border [&_input]:border-gray-200 [&_input]:bg-white [&_input]:hover:border-gray-300 [&_input]:focus:border-gray-300 w-full"
|
|
42
|
+
value={searchValue}
|
|
43
|
+
onChange={(e) => onSearchChange(e.target.value)}
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
{children && (
|
|
47
|
+
<div className="px-2 pt-1 pb-1">
|
|
48
|
+
<p className="text-[11px] font-bold text-gray-400 uppercase tracking-wider mb-1">{actionsLabel}</p>
|
|
49
|
+
{typeof children === 'function' ? children(close) : children}
|
|
50
|
+
</div>
|
|
51
|
+
)}
|
|
52
|
+
</>
|
|
48
53
|
)}
|
|
49
54
|
</Dropdown>
|
|
50
55
|
);
|
|
@@ -131,7 +131,7 @@ export function DashboardLayout({
|
|
|
131
131
|
}`}
|
|
132
132
|
>
|
|
133
133
|
{/* Clone the icon to dynamically apply styling based on active state */}
|
|
134
|
-
{React.cloneElement(menu.icon as React.ReactElement
|
|
134
|
+
{React.cloneElement(menu.icon as React.ReactElement<any>, {
|
|
135
135
|
size: 22,
|
|
136
136
|
className: isActive ? "stroke-[2.5]" : "stroke-[2]"
|
|
137
137
|
})}
|
|
@@ -305,7 +305,7 @@ export function DashboardLayout({
|
|
|
305
305
|
{menus.map((menu) => (
|
|
306
306
|
<div key={menu.id}>
|
|
307
307
|
<div className="flex items-center gap-3 text-[#2D3142] font-bold mb-3 px-2">
|
|
308
|
-
{React.cloneElement(menu.icon as React.ReactElement
|
|
308
|
+
{React.cloneElement(menu.icon as React.ReactElement<any>, {
|
|
309
309
|
size: 20,
|
|
310
310
|
className: "stroke-[2.5]"
|
|
311
311
|
})}
|
|
@@ -198,7 +198,7 @@ export function PaymentSection({ totalAmount, payments, onPaymentsChange, compac
|
|
|
198
198
|
.map((mode) => {
|
|
199
199
|
const isActive = selectionMode === "single" && payments.length === 1 && payments[0].mode_id === mode.id;
|
|
200
200
|
return (
|
|
201
|
-
<button key={mode.id} onClick={() => selectSingleMode(mode)}
|
|
201
|
+
<button key={mode.id} type="button" onClick={() => selectSingleMode(mode)}
|
|
202
202
|
className={cn(
|
|
203
203
|
"inline-flex items-center gap-2 px-4 py-2.5 text-[12px] font-semibold rounded-[10px] border transition-all",
|
|
204
204
|
isActive
|
|
@@ -211,7 +211,7 @@ export function PaymentSection({ totalAmount, payments, onPaymentsChange, compac
|
|
|
211
211
|
);
|
|
212
212
|
})}
|
|
213
213
|
{/* Split Button */}
|
|
214
|
-
<button onClick={activateSplitMode}
|
|
214
|
+
<button type="button" onClick={activateSplitMode}
|
|
215
215
|
className={cn(
|
|
216
216
|
"inline-flex items-center gap-2 px-4 py-2.5 text-[12px] font-semibold rounded-[10px] border transition-all",
|
|
217
217
|
selectionMode === "split"
|
|
@@ -248,7 +248,7 @@ export function PaymentSection({ totalAmount, payments, onPaymentsChange, compac
|
|
|
248
248
|
return canBeMultiple || !payments.some(p => p.mode_id === m.id);
|
|
249
249
|
})
|
|
250
250
|
.map((mode) => (
|
|
251
|
-
<button key={mode.id} onClick={() => addPayment(mode)}
|
|
251
|
+
<button key={mode.id} type="button" onClick={() => addPayment(mode)}
|
|
252
252
|
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-[11px] font-semibold rounded-[8px] bg-surface-0 border border-border-subtle text-foreground-subtle hover:border-primary/40 hover:text-primary transition-all">
|
|
253
253
|
{getModeIcon(mode.code, 12)}
|
|
254
254
|
+ {mode.name}
|
|
@@ -334,7 +334,7 @@ function PaymentEntryRow({ entry, mode, bankAccounts, materialTypes = [], onUpda
|
|
|
334
334
|
{entry.mode_name}
|
|
335
335
|
</div>
|
|
336
336
|
{showRemove && (
|
|
337
|
-
<button onClick={onRemove}
|
|
337
|
+
<button type="button" onClick={onRemove}
|
|
338
338
|
className="w-7 h-7 rounded-[6px] flex items-center justify-center text-foreground-disabled hover:text-danger-alt hover:bg-danger-alt/10 transition-all">
|
|
339
339
|
<Trash2 size={13} />
|
|
340
340
|
</button>
|