@firecms/ui 3.1.0 → 3.2.0-canary.44dc65b

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,7 +1,7 @@
1
1
  {
2
2
  "name": "@firecms/ui",
3
3
  "type": "module",
4
- "version": "3.1.0",
4
+ "version": "3.2.0-canary.44dc65b",
5
5
  "description": "Awesome Firebase/Firestore-based headless open-source CMS",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/firecmsco"
@@ -114,7 +114,7 @@
114
114
  "index.css",
115
115
  "tailwind.config.js"
116
116
  ],
117
- "gitHead": "40f8d9860cb2649c0a195ecebd1a92ccb37f33a6",
117
+ "gitHead": "af4cd57871ac430b4ffac0295c40b9cd3cee24ff",
118
118
  "publishConfig": {
119
119
  "access": "public"
120
120
  }
@@ -51,7 +51,7 @@ export const DateTimeField: React.FC<DateTimeFieldProps> = ({
51
51
  const [focused, setFocused] = useState(false);
52
52
  const [internalValue, setInternalValue] = useState<string>("");
53
53
  const [isTyping, setIsTyping] = useState(false);
54
- const invalidValue = value !== undefined && value !== null && !(value instanceof Date);
54
+ const invalidValue = value !== undefined && value !== null && (!(value instanceof Date) || isNaN((value as Date).getTime()));
55
55
 
56
56
  useInjectStyles("DateTimeField", inputStyles);
57
57
 
@@ -84,7 +84,12 @@ export const DateTimeField: React.FC<DateTimeFieldProps> = ({
84
84
  };
85
85
 
86
86
  const formatter = new Intl.DateTimeFormat("en-CA", options);
87
- const parts = formatter.formatToParts(dateValue);
87
+ let parts: Intl.DateTimeFormatPart[];
88
+ try {
89
+ parts = formatter.formatToParts(dateValue);
90
+ } catch {
91
+ return "";
92
+ }
88
93
 
89
94
  const getPart = (type: string) => parts.find(p => p.type === type)?.value ?? "";
90
95
 
@@ -1,7 +1,9 @@
1
- import React from "react";
1
+ import React, { useRef, useState, useEffect } from "react";
2
2
  import * as TabsPrimitive from "@radix-ui/react-tabs";
3
3
  import { cls } from "../util";
4
4
  import { defaultBorderMixin } from "../styles";
5
+ import { IconButton } from "./IconButton";
6
+ import { ChevronLeftIcon, ChevronRightIcon } from "../icons";
5
7
 
6
8
  export type TabsProps = {
7
9
  value: string,
@@ -12,22 +14,108 @@ export type TabsProps = {
12
14
  };
13
15
 
14
16
  export function Tabs({
15
- value,
16
- onValueChange,
17
- className,
18
- innerClassName,
19
- children
20
- }: TabsProps) {
21
- return <TabsPrimitive.Root value={value} onValueChange={onValueChange} className={className}>
22
- <TabsPrimitive.List className={cls(
23
- "border",
24
- defaultBorderMixin,
25
- "gap-2",
26
- "inline-flex h-10 items-center justify-center rounded-md bg-surface-50 p-1 text-surface-600 dark:bg-surface-900 dark:text-surface-400",
27
- innerClassName)
28
- }>
29
- {children}
30
- </TabsPrimitive.List>
17
+ value,
18
+ onValueChange,
19
+ className,
20
+ innerClassName,
21
+ children
22
+ }: TabsProps) {
23
+ const scrollContainerRef = useRef<HTMLDivElement>(null);
24
+ const [showLeftScroll, setShowLeftScroll] = useState(false);
25
+ const [showRightScroll, setShowRightScroll] = useState(false);
26
+ const [isScrollable, setIsScrollable] = useState(false);
27
+
28
+ const checkScroll = () => {
29
+ if (scrollContainerRef.current) {
30
+ const { scrollLeft, scrollWidth, clientWidth } = scrollContainerRef.current;
31
+ setShowLeftScroll(scrollLeft > 0);
32
+ setShowRightScroll(Math.ceil(scrollLeft + clientWidth) < scrollWidth);
33
+ setIsScrollable(scrollWidth > clientWidth);
34
+ }
35
+ };
36
+
37
+ useEffect(() => {
38
+ checkScroll();
39
+ window.addEventListener("resize", checkScroll);
40
+
41
+ let observer: ResizeObserver;
42
+ if (scrollContainerRef.current) {
43
+ observer = new ResizeObserver(checkScroll);
44
+ observer.observe(scrollContainerRef.current);
45
+ if (scrollContainerRef.current.firstElementChild) {
46
+ observer.observe(scrollContainerRef.current.firstElementChild);
47
+ }
48
+ }
49
+
50
+ return () => {
51
+ window.removeEventListener("resize", checkScroll);
52
+ observer?.disconnect();
53
+ };
54
+ }, [children]);
55
+
56
+ const scroll = (direction: "left" | "right") => {
57
+ if (scrollContainerRef.current) {
58
+ const container = scrollContainerRef.current;
59
+ const scrollAmount = Math.max(container.clientWidth / 2, 200);
60
+ const targetScroll = container.scrollLeft + (direction === "left" ? -scrollAmount : scrollAmount);
61
+
62
+ container.scrollTo({
63
+ left: targetScroll,
64
+ behavior: "smooth"
65
+ });
66
+ // checkScroll will be called by onScroll event
67
+ }
68
+ };
69
+
70
+ return <TabsPrimitive.Root value={value} onValueChange={onValueChange} className={cls("flex flex-row items-center min-w-0", className)}>
71
+ {isScrollable && (
72
+ <button
73
+ type="button"
74
+ disabled={!showLeftScroll}
75
+ onClick={() => scroll("left")}
76
+ className={cls(
77
+ "flex-shrink-0 z-10 flex items-center justify-center rounded-md px-0.5 py-1.5 transition-all h-10 w-6",
78
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-surface-400",
79
+ "disabled:pointer-events-none disabled:opacity-0",
80
+ "text-surface-600 dark:text-surface-400 hover:bg-surface-200 dark:hover:bg-surface-800",
81
+ "mr-1 bg-surface-50 dark:bg-surface-900 border", defaultBorderMixin
82
+ )}
83
+ >
84
+ <ChevronLeftIcon size="small" />
85
+ </button>
86
+ )}
87
+ <div
88
+ ref={scrollContainerRef}
89
+ className="flex-1 overflow-x-auto no-scrollbar min-w-0"
90
+ onScroll={checkScroll}
91
+ style={{ scrollbarWidth: "none", msOverflowStyle: "none" }}
92
+ >
93
+ <TabsPrimitive.List className={cls(
94
+ "border",
95
+ defaultBorderMixin,
96
+ "gap-2",
97
+ "inline-flex h-10 items-center justify-center rounded-md bg-surface-50 p-1 text-surface-600 dark:bg-surface-900 dark:text-surface-400",
98
+ innerClassName)
99
+ }>
100
+ {children}
101
+ </TabsPrimitive.List>
102
+ </div>
103
+ {isScrollable && (
104
+ <button
105
+ type="button"
106
+ disabled={!showRightScroll}
107
+ onClick={() => scroll("right")}
108
+ className={cls(
109
+ "flex-shrink-0 z-10 flex items-center justify-center rounded-md px-0.5 py-1.5 transition-all h-10 w-6",
110
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-surface-400",
111
+ "disabled:pointer-events-none disabled:opacity-0",
112
+ "text-surface-600 dark:text-surface-400 hover:bg-surface-200 dark:hover:bg-surface-800",
113
+ "ml-1 bg-surface-50 dark:bg-surface-900 border", defaultBorderMixin
114
+ )}
115
+ >
116
+ <ChevronRightIcon size="small" />
117
+ </button>
118
+ )}
31
119
  </TabsPrimitive.Root>
32
120
  }
33
121
 
@@ -40,23 +128,23 @@ export type TabProps = {
40
128
  };
41
129
 
42
130
  export function Tab({
43
- value,
44
- className,
45
- innerClassName,
46
- children,
47
- disabled
48
- }: TabProps) {
131
+ value,
132
+ className,
133
+ innerClassName,
134
+ children,
135
+ disabled
136
+ }: TabProps) {
49
137
  return <TabsPrimitive.Trigger value={value}
50
- disabled={disabled}
51
- className={cls(
52
- "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-white transition-all",
53
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-surface-400 focus-visible:ring-offset-2",
54
- "disabled:pointer-events-none disabled:opacity-50",
55
- "data-[state=active]:bg-white data-[state=active]:text-surface-900 dark:data-[state=active]:bg-surface-950 dark:data-[state=active]:text-surface-50",
56
- // "data-[state=active]:border",
57
- // defaultBorderMixin,
58
- className,
59
- innerClassName)}>
138
+ disabled={disabled}
139
+ className={cls(
140
+ "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-white transition-all",
141
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-surface-400 focus-visible:ring-offset-2",
142
+ "disabled:pointer-events-none disabled:opacity-50",
143
+ "data-[state=active]:bg-white data-[state=active]:text-surface-900 dark:data-[state=active]:bg-surface-950 dark:data-[state=active]:text-surface-50",
144
+ // "data-[state=active]:border",
145
+ // defaultBorderMixin,
146
+ className,
147
+ innerClassName)}>
60
148
  {children}
61
149
  </TabsPrimitive.Trigger>;
62
150
  }