@apptimate/ui 2.3.0 → 2.4.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apptimate/ui",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "main": "src/index.tsx",
5
5
  "types": "src/index.tsx",
6
6
  "dependencies": {
@@ -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 = false;
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
- "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",
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
- <div className="px-2 pb-2 pt-1 mb-1 border-b border-gray-100">
34
- <p className="text-[11px] font-bold text-gray-400 uppercase tracking-wider mb-2">{searchLabel}</p>
35
- <Input
36
- placeholder={searchPlaceholder}
37
- icon={<Search size={16} className="text-gray-400" />}
38
- 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"
39
- value={searchValue}
40
- onChange={(e) => onSearchChange(e.target.value)}
41
- />
42
- </div>
43
- {children && (
44
- <div className="px-2 pt-1 pb-1">
45
- <p className="text-[11px] font-bold text-gray-400 uppercase tracking-wider mb-1">{actionsLabel}</p>
46
- {children}
47
- </div>
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
  );
@@ -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>