@meta-1/design 0.0.161 → 0.0.163

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": "@meta-1/design",
3
- "version": "0.0.161",
3
+ "version": "0.0.163",
4
4
  "keywords": [
5
5
  "easykit",
6
6
  "design",
@@ -1,4 +1,4 @@
1
- import { forwardRef, useContext, useState } from "react";
1
+ import { forwardRef, type MouseEvent, useContext, useEffect, useState } from "react";
2
2
  import { Cross2Icon } from "@radix-ui/react-icons";
3
3
  import { addDays, format } from "date-fns";
4
4
  import get from "lodash/get";
@@ -13,52 +13,135 @@ export type DatePickerProps = {
13
13
  preset?: boolean;
14
14
  className?: string;
15
15
  allowClear?: boolean;
16
+ value?: Date;
17
+ onChange?: (value: Date | undefined) => void;
18
+ open?: boolean;
19
+ onOpenChange?: (open: boolean) => void;
16
20
  };
17
21
 
18
22
  export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((props, _ref) => {
19
- const { preset = false, allowClear = false } = props;
20
- const [date, setDate] = useState<Date>();
23
+ const {
24
+ placeholder,
25
+ format: formatProp,
26
+ preset = false,
27
+ allowClear = false,
28
+ className,
29
+ value,
30
+ onChange,
31
+ open,
32
+ onOpenChange,
33
+ } = props;
34
+
35
+ const hasValueProp = Object.hasOwn(props, "value");
36
+ const isValueControlled = hasValueProp;
37
+ const [internalDate, setInternalDate] = useState<Date | undefined>(value);
21
38
  const [presetValue, setPresetValue] = useState<string>("");
39
+ const hasOpenProp = Object.hasOwn(props, "open");
40
+ const isOpenControlled = hasOpenProp;
41
+ const [internalOpen, setInternalOpen] = useState(false);
42
+
43
+ useEffect(() => {
44
+ if (isValueControlled) {
45
+ setInternalDate(value);
46
+ }
47
+ }, [isValueControlled, value]);
48
+
49
+ useEffect(() => {
50
+ if (isOpenControlled) {
51
+ setInternalOpen(Boolean(open));
52
+ }
53
+ }, [isOpenControlled, open]);
22
54
 
23
55
  const config = useContext(UIXContext);
24
56
  const locale = get(config.locale, "DatePicker.locale");
25
- const formatConfig = props.format || get(config.locale, "DatePicker.format") || "yyyy-MM-dd";
57
+ const formatConfig = formatProp || get(config.locale, "DatePicker.format") || "yyyy-MM-dd";
26
58
  const options = get(config.locale, "DatePicker.options");
59
+ const selectedDate = isValueControlled ? value : internalDate;
60
+ const popoverOpen = isOpenControlled ? open : internalOpen;
27
61
 
28
- const calendar = (
29
- <Calendar
30
- locale={locale}
31
- mode="single"
32
- onSelect={(v) => {
33
- setDate(v);
34
- setPresetValue("");
35
- }}
36
- selected={date}
37
- />
38
- );
62
+ const closePopover = () => {
63
+ if (!isOpenControlled) {
64
+ setInternalOpen(false);
65
+ }
66
+ onOpenChange?.(false);
67
+ };
68
+
69
+ const handleSelect = (nextDate?: Date) => {
70
+ if (!nextDate) {
71
+ return;
72
+ }
73
+ if (!isValueControlled) {
74
+ setInternalDate(nextDate);
75
+ }
76
+ setPresetValue("");
77
+ onChange?.(nextDate);
78
+ closePopover();
79
+ };
80
+
81
+ const handlePresetChange = (valueStr: string) => {
82
+ setPresetValue(valueStr);
83
+ const offset = Number.parseInt(valueStr, 10);
84
+ if (Number.isNaN(offset)) {
85
+ if (!isValueControlled) {
86
+ setInternalDate(undefined);
87
+ }
88
+ onChange?.(undefined);
89
+ return;
90
+ }
91
+ const nextDate = addDays(new Date(), offset);
92
+ if (!isValueControlled) {
93
+ setInternalDate(nextDate);
94
+ }
95
+ onChange?.(nextDate);
96
+ closePopover();
97
+ };
98
+
99
+ const handleClear = (event: MouseEvent<SVGSVGElement | HTMLSpanElement>) => {
100
+ event.preventDefault();
101
+ event.stopPropagation();
102
+ if (!isValueControlled) {
103
+ setInternalDate(undefined);
104
+ }
105
+ setPresetValue("");
106
+ onChange?.(undefined);
107
+ };
108
+
109
+ const handleOpenChange = (nextOpen: boolean) => {
110
+ onOpenChange?.(nextOpen);
111
+ if (!isOpenControlled) {
112
+ setInternalOpen(nextOpen);
113
+ }
114
+ };
115
+
116
+ const calendar = <Calendar locale={locale} mode="single" onSelect={handleSelect} selected={selectedDate} />;
39
117
 
40
118
  return (
41
- <Popover>
119
+ <Popover onOpenChange={handleOpenChange} open={popoverOpen}>
42
120
  <PopoverTrigger asChild>
43
121
  <Button
44
122
  className={cn(
45
123
  "group w-full justify-start space-x-1 text-left font-normal",
46
- !date && "text-muted-foreground",
47
- props.className,
124
+ !selectedDate && "text-muted-foreground",
125
+ className,
48
126
  )}
49
127
  variant="outline"
50
128
  >
51
129
  <CalendarIcon className="mr-2 h-4 w-4" />
52
- <span className="flex-1">{date ? format(date, formatConfig) : props.placeholder}</span>
53
- {allowClear && date ? (
54
- <Cross2Icon
55
- className="hidden group-hover:block"
56
- onClick={(e) => {
57
- setDate(undefined);
58
- setPresetValue("");
59
- e.stopPropagation();
130
+ <span className="flex-1">{selectedDate ? format(selectedDate, formatConfig) : placeholder}</span>
131
+ {allowClear && selectedDate ? (
132
+ <span
133
+ aria-label="Clear date"
134
+ className="hidden cursor-pointer items-center justify-center group-hover:flex"
135
+ onClick={handleClear}
136
+ onPointerDown={(pointerEvent) => {
137
+ pointerEvent.preventDefault();
138
+ pointerEvent.stopPropagation();
60
139
  }}
61
- />
140
+ role="button"
141
+ tabIndex={-1}
142
+ >
143
+ <Cross2Icon />
144
+ </span>
62
145
  ) : null}
63
146
  </Button>
64
147
  </PopoverTrigger>
@@ -67,10 +150,7 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((props, _r
67
150
  <>
68
151
  <Select
69
152
  className="w-full"
70
- onChange={(value) => {
71
- setDate(addDays(new Date(), Number.parseInt(value, 10)));
72
- setPresetValue(value);
73
- }}
153
+ onChange={handlePresetChange}
74
154
  options={options || []}
75
155
  placeholder="请选择"
76
156
  value={presetValue}
@@ -25,6 +25,8 @@ export interface SelectProps {
25
25
  empty?: ReactNode;
26
26
  allowClear?: boolean;
27
27
  triggerClassName?: string;
28
+ open?: boolean;
29
+ onOpenChange?: (open: boolean) => void;
28
30
  }
29
31
 
30
32
  export const Select = forwardRef<HTMLSelectElement, SelectProps>((props, _ref) => {
@@ -38,6 +40,8 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>((props, _ref) =
38
40
  empty,
39
41
  allowClear,
40
42
  triggerClassName,
43
+ open,
44
+ onOpenChange,
41
45
  ...rest
42
46
  } = props;
43
47
 
@@ -60,7 +64,14 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>((props, _ref) =
60
64
  };
61
65
 
62
66
  return (
63
- <UISelect {...rest} defaultValue={defaultValue} onValueChange={handleChange} value={currentValue}>
67
+ <UISelect
68
+ {...rest}
69
+ defaultValue={defaultValue}
70
+ onOpenChange={onOpenChange}
71
+ onValueChange={handleChange}
72
+ open={open}
73
+ value={currentValue}
74
+ >
64
75
  <div className={cn("relative inline-block", allowClear && currentValue ? "group" : "", className)}>
65
76
  <SelectTrigger className={cn("flex w-full", triggerClassName)}>
66
77
  <SelectValue placeholder={placeholder} />