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 = 'd MMM yyyy'
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
- <Calendar {...{ mode, value, numberOfMonths, month }} onChange={onCalendarChange} className="px-3 pt-1 pb-2" />
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-3 gap-x-6 mb-4">
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.17",
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": "^18"
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.17"`
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