nitro-web 0.0.17 → 0.0.18
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.
|
@@ -17,9 +17,10 @@ export type CalendarProps = {
|
|
|
17
17
|
numberOfMonths?: number
|
|
18
18
|
month?: number // the value may be updated from an outside source, thus the month may have changed
|
|
19
19
|
className?: string
|
|
20
|
+
preserveTime?: boolean
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
export function Calendar({ mode='single', onChange, value, numberOfMonths, month: monthProp, className }: CalendarProps) {
|
|
23
|
+
export function Calendar({ mode='single', onChange, value, numberOfMonths, month: monthProp, className, preserveTime }: CalendarProps) {
|
|
23
24
|
const isFirstRender = IsFirstRender()
|
|
24
25
|
const isRange = mode == 'range'
|
|
25
26
|
|
|
@@ -41,6 +42,7 @@ export function Calendar({ mode='single', onChange, value, numberOfMonths, month
|
|
|
41
42
|
switch (mode as T) {
|
|
42
43
|
case 'single': {
|
|
43
44
|
const date = newDate as ModeSelection<'single'>
|
|
45
|
+
preserveTimeFn(date)
|
|
44
46
|
onChange?.(mode, date?.getTime() ?? null)
|
|
45
47
|
break
|
|
46
48
|
}
|
|
@@ -56,6 +58,19 @@ export function Calendar({ mode='single', onChange, value, numberOfMonths, month
|
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
}
|
|
61
|
+
|
|
62
|
+
function preserveTimeFn(date?: Date) {
|
|
63
|
+
// Preserve time from the original date if needed
|
|
64
|
+
if (preserveTime && dates[0] && date) {
|
|
65
|
+
const originalDate = dates[0]
|
|
66
|
+
date.setHours(
|
|
67
|
+
originalDate.getHours(),
|
|
68
|
+
originalDate.getMinutes(),
|
|
69
|
+
originalDate.getSeconds(),
|
|
70
|
+
originalDate.getMilliseconds()
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
59
74
|
|
|
60
75
|
const d = getDefaultClassNames()
|
|
61
76
|
const common = {
|
|
@@ -104,7 +104,7 @@ export const Dropdown = forwardRef(function Dropdown({
|
|
|
104
104
|
return cloneElement(el, { key, onMouseDown, onKeyDown }) // adds onClick
|
|
105
105
|
})
|
|
106
106
|
}
|
|
107
|
-
<ul
|
|
107
|
+
<ul
|
|
108
108
|
style={{ minWidth }}
|
|
109
109
|
class={`${menuStyle} absolute invisible opacity-0 select-none min-w-full z-[1]`}
|
|
110
110
|
>
|
|
@@ -132,7 +132,9 @@ export const Dropdown = forwardRef(function Dropdown({
|
|
|
132
132
|
|
|
133
133
|
const style = css`
|
|
134
134
|
ul {
|
|
135
|
-
transition: transform 0.15s ease, opacity 0.15s ease, visibility 0s 0.15s ease;
|
|
135
|
+
transition: transform 0.15s ease, opacity 0.15s ease, visibility 0s 0.15s ease, max-width 0s 0.15s ease, max-height 0s 0.15s ease;
|
|
136
|
+
max-width: 0; // handy if the dropdown ul exceeds the viewport width
|
|
137
|
+
max-height: 0; // handy if the dropdown ul exceeds the viewport height
|
|
136
138
|
}
|
|
137
139
|
&.is-bottom-right,
|
|
138
140
|
&.is-top-right {
|
|
@@ -166,6 +168,8 @@ const style = css`
|
|
|
166
168
|
opacity: 1;
|
|
167
169
|
visibility: visible;
|
|
168
170
|
transition: transform 0.15s ease, opacity 0.15s ease;
|
|
171
|
+
max-width: 1000px;
|
|
172
|
+
max-height: 1000px;
|
|
169
173
|
}
|
|
170
174
|
&.is-bottom-left > ul,
|
|
171
175
|
&.is-bottom-right > ul {
|
|
@@ -10,6 +10,7 @@ export type FieldDateProps = React.InputHTMLAttributes<HTMLInputElement> & {
|
|
|
10
10
|
name: string
|
|
11
11
|
id?: string
|
|
12
12
|
mode?: Mode
|
|
13
|
+
showTime?: boolean
|
|
13
14
|
// an array is returned for non-single modes
|
|
14
15
|
onChange?: (e: { target: { id: string, value: null|number|(null|number)[] } }) => void
|
|
15
16
|
prefix?: string
|
|
@@ -18,8 +19,8 @@ export type FieldDateProps = React.InputHTMLAttributes<HTMLInputElement> & {
|
|
|
18
19
|
Icon?: React.ReactNode
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
export function FieldDate({ mode='single', onChange, prefix='', value, numberOfMonths, Icon, ...props }: FieldDateProps) {
|
|
22
|
-
const localePattern =
|
|
22
|
+
export function FieldDate({ mode='single', onChange, prefix='', value, numberOfMonths, Icon, showTime, ...props }: FieldDateProps) {
|
|
23
|
+
const localePattern = `d MMM yyyy${showTime && mode == 'single' ? ' HH:mm aa' : ''}`
|
|
23
24
|
const [prefixWidth, setPrefixWidth] = useState(0)
|
|
24
25
|
const dropdownRef = useRef<DropdownRef>(null)
|
|
25
26
|
const id = props.id || props.name
|
|
@@ -40,7 +41,7 @@ export function FieldDate({ mode='single', onChange, prefix='', value, numberOfM
|
|
|
40
41
|
}, [prefix])
|
|
41
42
|
|
|
42
43
|
function onCalendarChange(mode: Mode, value: null|number|(null|number)[]) {
|
|
43
|
-
if (mode == 'single') dropdownRef.current?.setIsActive(false) // Close the dropdown
|
|
44
|
+
if (mode == 'single' && !showTime) dropdownRef.current?.setIsActive(false) // Close the dropdown
|
|
44
45
|
setInputValue(getInputValue(value))
|
|
45
46
|
if (onChange) onChange({ target: { id: id, value: value }})
|
|
46
47
|
}
|
|
@@ -80,11 +81,19 @@ export function FieldDate({ mode='single', onChange, prefix='', value, numberOfM
|
|
|
80
81
|
<Dropdown
|
|
81
82
|
ref={dropdownRef}
|
|
82
83
|
menuToggles={false}
|
|
83
|
-
animate={false}
|
|
84
|
+
// animate={false}
|
|
84
85
|
// menuIsOpen={true}
|
|
85
86
|
minWidth={0}
|
|
86
87
|
menuChildren={
|
|
87
|
-
<
|
|
88
|
+
<div className="flex">
|
|
89
|
+
<Calendar
|
|
90
|
+
{...{ mode, value, numberOfMonths, month }}
|
|
91
|
+
preserveTime={!!showTime}
|
|
92
|
+
onChange={onCalendarChange}
|
|
93
|
+
className="pt-1 pb-2 px-3"
|
|
94
|
+
/>
|
|
95
|
+
{!!showTime && mode == 'single' && <TimePicker date={dates?.[0]} onChange={onCalendarChange} />}
|
|
96
|
+
</div>
|
|
88
97
|
}
|
|
89
98
|
>
|
|
90
99
|
<div className="grid grid-cols-1">
|
|
@@ -110,3 +119,77 @@ export function FieldDate({ mode='single', onChange, prefix='', value, numberOfM
|
|
|
110
119
|
</Dropdown>
|
|
111
120
|
)
|
|
112
121
|
}
|
|
122
|
+
|
|
123
|
+
type TimePickerProps = {
|
|
124
|
+
date: Date|null
|
|
125
|
+
onChange: (mode: Mode, value: number|null) => void
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function TimePicker({ date, onChange }: TimePickerProps) {
|
|
129
|
+
const lists = [
|
|
130
|
+
[12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], // hours
|
|
131
|
+
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55], // minutes
|
|
132
|
+
['AM', 'PM'], // AM/PM
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
// Get current values from date or use defaults
|
|
136
|
+
const hour = date ? parseInt(format(date, 'h')) : undefined
|
|
137
|
+
const minute = date ? parseInt(format(date, 'm')) : undefined
|
|
138
|
+
const period = date ? format(date, 'a') : undefined
|
|
139
|
+
|
|
140
|
+
const handleTimeChange = (type: 'hour' | 'minute' | 'period', value: string | number) => {
|
|
141
|
+
// Create a new date object from the current date or current time
|
|
142
|
+
const newDate = new Date(date || new Date())
|
|
143
|
+
|
|
144
|
+
if (type === 'hour') {
|
|
145
|
+
// Parse the time with the new hour value
|
|
146
|
+
const timeString = `${value}:${format(newDate, 'mm')} ${format(newDate, 'a')}`
|
|
147
|
+
const updatedDate = parse(timeString, 'h:mm a', newDate)
|
|
148
|
+
newDate.setHours(updatedDate.getHours(), updatedDate.getMinutes())
|
|
149
|
+
} else if (type === 'minute') {
|
|
150
|
+
// Parse the time with the new minute value
|
|
151
|
+
const timeString = `${format(newDate, 'h')}:${value} ${format(newDate, 'a')}`
|
|
152
|
+
const updatedDate = parse(timeString, 'h:mm a', newDate)
|
|
153
|
+
newDate.setMinutes(updatedDate.getMinutes())
|
|
154
|
+
} else if (type === 'period') {
|
|
155
|
+
// Parse the time with the new period value
|
|
156
|
+
const timeString = `${format(newDate, 'h')}:${format(newDate, 'mm')} ${value}`
|
|
157
|
+
const updatedDate = parse(timeString, 'h:mm a', newDate)
|
|
158
|
+
newDate.setHours(updatedDate.getHours())
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
onChange('single', newDate.getTime())
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
lists.map((list, i) => {
|
|
166
|
+
const type = i === 0 ? 'hour' : i === 1 ? 'minute' : 'period'
|
|
167
|
+
const currentValue = i === 0 ? hour : i === 1 ? minute : period
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<div key={i} className="w-[60px] py-1 relative overflow-hidden hover:overflow-y-auto border-l border-gray-100">
|
|
171
|
+
<div className="w-[60px] absolute flex flex-col items-center">
|
|
172
|
+
{list.map(item => (
|
|
173
|
+
<div
|
|
174
|
+
className="py-1 flex group cursor-pointer"
|
|
175
|
+
key={item}
|
|
176
|
+
onClick={() => handleTimeChange(type, item)}
|
|
177
|
+
>
|
|
178
|
+
<button
|
|
179
|
+
key={item}
|
|
180
|
+
className={
|
|
181
|
+
'size-[33px] rounded-full flex justify-center items-center group-hover:bg-gray-100 '
|
|
182
|
+
+ (item === currentValue ? '!bg-primary-dark text-white' : '')
|
|
183
|
+
}
|
|
184
|
+
onClick={() => handleTimeChange(type, item)}
|
|
185
|
+
>
|
|
186
|
+
{item.toString().padStart(2, '0').toLowerCase()}
|
|
187
|
+
</button>
|
|
188
|
+
</div>
|
|
189
|
+
))}
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
)
|
|
193
|
+
})
|
|
194
|
+
)
|
|
195
|
+
}
|
|
@@ -14,6 +14,7 @@ export function Styleguide({ config }: { config: Config }) {
|
|
|
14
14
|
currency: 'nzd', // can be commented too
|
|
15
15
|
date: Date.now(),
|
|
16
16
|
'date-range': [Date.now(), Date.now() + 1000 * 60 * 60 * 24 * 33],
|
|
17
|
+
'date-time': Date.now(),
|
|
17
18
|
calendar: [Date.now(), Date.now() + 1000 * 60 * 60 * 24 * 8],
|
|
18
19
|
firstName: 'Bruce',
|
|
19
20
|
errors: [
|
|
@@ -251,15 +252,19 @@ export function Styleguide({ config }: { config: Config }) {
|
|
|
251
252
|
</div>
|
|
252
253
|
|
|
253
254
|
<h2 class="h3">Date Inputs</h2>
|
|
254
|
-
<div class="grid grid-cols-
|
|
255
|
+
<div class="grid grid-cols-1 gap-x-6 mb-4 sm:grid-cols-3">
|
|
255
256
|
<div>
|
|
256
|
-
<label for="date">Date</label>
|
|
257
|
-
<Field name="date" type="date" state={state} onChange={onInputChange} />
|
|
257
|
+
<label for="date">Date with time</label>
|
|
258
|
+
<Field name="date-time" type="date" showTime={true} state={state} onChange={onInputChange} />
|
|
258
259
|
</div>
|
|
259
260
|
<div>
|
|
260
261
|
<label for="date-range">Date range with prefix</label>
|
|
261
262
|
<Field name="date-range" type="date" mode="range" prefix="Date:" state={state} onChange={onInputChange} />
|
|
262
263
|
</div>
|
|
264
|
+
<div>
|
|
265
|
+
<label for="date">Date</label>
|
|
266
|
+
<Field name="date" type="date" state={state} onChange={onInputChange} />
|
|
267
|
+
</div>
|
|
263
268
|
</div>
|
|
264
269
|
|
|
265
270
|
<h2 class="h3">File Inputs & Calendar</h2>
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Nitro is a battle-tested, modular base project to turbocharge your projects, styled using Tailwind 🚀",
|
|
4
4
|
"repository": "github:boycce/nitro-web",
|
|
5
5
|
"homepage": "https://boycce.github.io/nitro-web/",
|
|
6
|
-
"version": "0.0.
|
|
6
|
+
"version": "0.0.18",
|
|
7
7
|
"main": "./client/index.ts",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"keywords": [
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"twin.macro": "^3.4.1"
|
|
89
89
|
},
|
|
90
90
|
"engines": {
|
|
91
|
-
"node": "
|
|
91
|
+
"node": ">=18"
|
|
92
92
|
},
|
|
93
93
|
"standard-version": {
|
|
94
94
|
"releaseCommitMessageFormat": "{{currentTag}}",
|
package/readme.md
CHANGED
|
@@ -11,7 +11,7 @@ npm i nitro-web
|
|
|
11
11
|
### Install
|
|
12
12
|
|
|
13
13
|
1. Copy ./_example into your project
|
|
14
|
-
2. In package.json, replace `"nitro-web": "file:.."` with `"nitro-web": "~0.0.
|
|
14
|
+
2. In package.json, replace `"nitro-web": "file:.."` with `"nitro-web": "~0.0.18"`
|
|
15
15
|
3. In package.json, replace `"../.eslintrc.json"` with `"./node_modules/nitro-web/.eslintrc.json"`
|
|
16
16
|
4. Run `npm i`
|
|
17
17
|
|