nitro-web 0.0.40 → 0.0.41
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.
|
@@ -11,8 +11,6 @@ import {
|
|
|
11
11
|
EyeSlashIcon,
|
|
12
12
|
} from '@heroicons/react/20/solid'
|
|
13
13
|
// Maybe use fill-current tw class for lucide icons (https://github.com/lucide-icons/lucide/discussions/458)
|
|
14
|
-
import FieldTime, { FieldTimeProps } from './field-time'
|
|
15
|
-
import ClockIcon from '../icons/clock'
|
|
16
14
|
|
|
17
15
|
type InputProps = React.InputHTMLAttributes<HTMLInputElement>
|
|
18
16
|
type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>
|
|
@@ -22,7 +20,7 @@ type FieldExtraProps = {
|
|
|
22
20
|
id?: string
|
|
23
21
|
// state object to get the value, and check errors against
|
|
24
22
|
state?: { errors?: Errors, [key: string]: unknown }
|
|
25
|
-
type?: 'text' | 'password' | 'email' | 'filter' | 'search' | 'textarea' | 'currency' | 'date' | '
|
|
23
|
+
type?: 'text' | 'password' | 'email' | 'filter' | 'search' | 'textarea' | 'currency' | 'date' | 'color'
|
|
26
24
|
icon?: React.ReactNode
|
|
27
25
|
iconPos?: 'left' | 'right'
|
|
28
26
|
}
|
|
@@ -38,7 +36,6 @@ export type FieldProps = (
|
|
|
38
36
|
| ({ type: 'currency' } & FieldCurrencyProps & FieldExtraProps)
|
|
39
37
|
| ({ type: 'color' } & FieldColorProps & FieldExtraProps)
|
|
40
38
|
| ({ type: 'date' } & FieldDateProps & FieldExtraProps)
|
|
41
|
-
| ({ type: 'time' } & FieldTimeProps & FieldExtraProps)
|
|
42
39
|
)
|
|
43
40
|
|
|
44
41
|
export function Field({ state, icon, iconPos: ip, ...props }: FieldProps) {
|
|
@@ -89,8 +86,6 @@ export function Field({ state, icon, iconPos: ip, ...props }: FieldProps) {
|
|
|
89
86
|
Icon = <IconWrapper iconPos={iconPos} icon={icon || <ColorSvg hex={value}/>} className="size-[17px]" />
|
|
90
87
|
} else if (type == 'date') {
|
|
91
88
|
Icon = <IconWrapper iconPos={iconPos} icon={icon || <CalendarIcon />} className="size-4" />
|
|
92
|
-
} else if (type == 'time') {
|
|
93
|
-
Icon = <IconWrapper iconPos={iconPos} icon={icon || <ClockIcon />} className="size-4" />
|
|
94
89
|
} else {
|
|
95
90
|
Icon = <IconWrapper iconPos={iconPos} icon={icon} />
|
|
96
91
|
}
|
|
@@ -130,12 +125,6 @@ export function Field({ state, icon, iconPos: ip, ...props }: FieldProps) {
|
|
|
130
125
|
<FieldDate {...props} {...commonProps} Icon={Icon} />
|
|
131
126
|
</FieldContainer>
|
|
132
127
|
)
|
|
133
|
-
} else if (type == 'time') {
|
|
134
|
-
return (
|
|
135
|
-
<FieldContainer error={error} className={props.className}>
|
|
136
|
-
<FieldTime {...props} {...commonProps} Icon={Icon} />
|
|
137
|
-
</FieldContainer>
|
|
138
|
-
)
|
|
139
128
|
}
|
|
140
129
|
}
|
|
141
130
|
|
|
@@ -14,7 +14,6 @@ export function Styleguide() {
|
|
|
14
14
|
date: Date.now(),
|
|
15
15
|
'date-range': [Date.now(), Date.now() + 1000 * 60 * 60 * 24 * 33],
|
|
16
16
|
'date-time': Date.now(),
|
|
17
|
-
time: '0',
|
|
18
17
|
calendar: [Date.now(), Date.now() + 1000 * 60 * 60 * 24 * 8],
|
|
19
18
|
firstName: 'Bruce',
|
|
20
19
|
errors: [
|
|
@@ -40,30 +39,13 @@ export function Styleguide() {
|
|
|
40
39
|
]
|
|
41
40
|
|
|
42
41
|
function onInputChange (e: { target: { id: string, value: unknown } }) {
|
|
43
|
-
|
|
44
|
-
let {value} = e.target
|
|
45
|
-
if ((id == 'customer' || id == 'customer2') && value == '') {
|
|
42
|
+
if ((e.target.id == 'customer' || e.target.id == 'customer2') && e.target.value == '') {
|
|
46
43
|
setCustomerSearch('')
|
|
47
|
-
value = null // clear the selected value
|
|
44
|
+
e.target.value = null // clear the selected value
|
|
48
45
|
}
|
|
49
|
-
|
|
50
|
-
console.dir('newValue')
|
|
51
|
-
console.dir(newValue)
|
|
52
|
-
setState(s => ({ ...s, [id]: newValue }))
|
|
46
|
+
setState(s => ({ ...s, [e.target.id]: e.target.value }))
|
|
53
47
|
}
|
|
54
48
|
|
|
55
|
-
/**
|
|
56
|
-
* handleTimeChange
|
|
57
|
-
*/
|
|
58
|
-
const handleTimeChange = () => ({ id, value }: { id: string, value: number | number[] }) => {
|
|
59
|
-
if (Array.isArray(value)) return
|
|
60
|
-
|
|
61
|
-
setState((state) => ({
|
|
62
|
-
...state,
|
|
63
|
-
[id]: value,
|
|
64
|
-
}))
|
|
65
|
-
}
|
|
66
|
-
|
|
67
49
|
function onCustomerSearch (search: string) {
|
|
68
50
|
setCustomerSearch(search || '')
|
|
69
51
|
}
|
|
@@ -287,10 +269,6 @@ export function Styleguide() {
|
|
|
287
269
|
<label for="date">Date (right aligned)</label>
|
|
288
270
|
<Field name="date" type="date" state={state} onChange={onInputChange} dir="bottom-right" />
|
|
289
271
|
</div>
|
|
290
|
-
<div>
|
|
291
|
-
<label for="time">Time</label>
|
|
292
|
-
<Field name="time" type="time" state={state} value={state.time} onChange={handleTimeChange} />
|
|
293
|
-
</div>
|
|
294
272
|
</div>
|
|
295
273
|
|
|
296
274
|
<h2 class="h3">File Inputs & Calendar</h2>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nitro-web",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.41",
|
|
4
4
|
"repository": "github:boycce/nitro-web",
|
|
5
5
|
"homepage": "https://boycce.github.io/nitro-web/",
|
|
6
6
|
"description": "Nitro is a battle-tested, modular base project to turbocharge your projects, styled using Tailwind 🚀",
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import React, { useState, useMemo, useCallback, JSX, useEffect } from 'react'
|
|
2
|
-
import { css, theme } from 'twin.macro'
|
|
3
|
-
import { Dropdown } from 'nitro-web'
|
|
4
|
-
import ClockIcon from '../icons/clock'
|
|
5
|
-
|
|
6
|
-
export interface FieldTimeProps {
|
|
7
|
-
className?: string;
|
|
8
|
-
placeholder?: string;
|
|
9
|
-
id?: string;
|
|
10
|
-
onChange: (values: { id: string; value: number; isFalse: boolean }) => void;
|
|
11
|
-
value: string;
|
|
12
|
-
isFull?: boolean;
|
|
13
|
-
Icon?: JSX.Element;
|
|
14
|
-
required?: boolean;
|
|
15
|
-
onFalseCondition?: (id: string, time: number) => boolean
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function FieldTime({
|
|
19
|
-
className, placeholder = '', id = '', onChange, value, isFull = true, Icon = <ClockIcon />, required = false, onFalseCondition, ...rest
|
|
20
|
-
}: FieldTimeProps) {
|
|
21
|
-
|
|
22
|
-
// Parse the incoming time value
|
|
23
|
-
const [time, setTime] = useState(() => Number(value) || 0)
|
|
24
|
-
|
|
25
|
-
const handleTimeChange = useCallback((isHour: boolean, e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
26
|
-
const timeStr = e.target.value
|
|
27
|
-
const number = parseInt(timeStr)
|
|
28
|
-
const hours = Math.floor(time / (60 * 60 * 1000))
|
|
29
|
-
const minutes = Math.floor((time % (60 * 60 * 1000)) / (60 * 1000))
|
|
30
|
-
const newTime = isHour ? (number * 60 * 60 * 1000) + (minutes * 60 * 1000) : (hours * 60 * 60 * 1000) + (number * 60 * 1000)
|
|
31
|
-
|
|
32
|
-
const isFalse = onFalseCondition ? onFalseCondition(id, newTime) : false // if false, not update
|
|
33
|
-
|
|
34
|
-
if (!isFalse) setTime(newTime)
|
|
35
|
-
|
|
36
|
-
if (onChange) onChange({ id: id, value: newTime, isFalse: isFalse })
|
|
37
|
-
|
|
38
|
-
}, [time, id, onChange, onFalseCondition])
|
|
39
|
-
|
|
40
|
-
const hoursTime = useMemo(() => Math.floor(time / (60 * 60 * 1000)), [time])
|
|
41
|
-
|
|
42
|
-
const minutesTime = useMemo(() => Math.floor((time % (60 * 60 * 1000)) / (60 * 1000)), [time])
|
|
43
|
-
|
|
44
|
-
const [displayTime, setDisplayTime] = useState('00:00')
|
|
45
|
-
|
|
46
|
-
const secondOptions = useMemo(() => {
|
|
47
|
-
const [_hours, minutes] = displayTime.split(':').map(Number)
|
|
48
|
-
return [...new Set([minutes, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55])]
|
|
49
|
-
}, [displayTime])
|
|
50
|
-
|
|
51
|
-
useEffect(() => {
|
|
52
|
-
setDisplayTime(`${String(hoursTime).padStart(2, '0')}:${String(minutesTime).padStart(2, '0')}`)
|
|
53
|
-
}, [hoursTime, minutesTime])
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<Dropdown
|
|
57
|
-
className={isFull ? 'w-full' : 'w-auto'}
|
|
58
|
-
// @ts-ignore
|
|
59
|
-
css={style}
|
|
60
|
-
menuToggles={false}
|
|
61
|
-
animate={false}
|
|
62
|
-
menuChildren={
|
|
63
|
-
<div className="time-picker-container">
|
|
64
|
-
<div className="time-picker-selectors">
|
|
65
|
-
<select
|
|
66
|
-
className="time-select"
|
|
67
|
-
value={hoursTime}
|
|
68
|
-
onChange={(e) => handleTimeChange(true, e)}
|
|
69
|
-
>
|
|
70
|
-
{[...Array(24).keys()].map(i => (
|
|
71
|
-
<option key={i} value={i}>{String(i).padStart(2, '0')}</option>
|
|
72
|
-
))}
|
|
73
|
-
</select>
|
|
74
|
-
<span className="time-separator">:</span>
|
|
75
|
-
<select
|
|
76
|
-
className="time-select"
|
|
77
|
-
value={minutesTime}
|
|
78
|
-
onChange={(e) => handleTimeChange(false, e)}
|
|
79
|
-
>
|
|
80
|
-
{secondOptions.map(option => (
|
|
81
|
-
<option key={option} value={option}>{String(option).padStart(2, '0')}</option>
|
|
82
|
-
))}
|
|
83
|
-
</select>
|
|
84
|
-
</div>
|
|
85
|
-
</div>
|
|
86
|
-
}
|
|
87
|
-
>
|
|
88
|
-
<div className="grid grid-cols-1">
|
|
89
|
-
{Icon}
|
|
90
|
-
<input
|
|
91
|
-
{...rest}
|
|
92
|
-
key={id}
|
|
93
|
-
id={id}
|
|
94
|
-
autoComplete="off"
|
|
95
|
-
className={`hide-time-icon font-medium text-sm placeholder-font-medium placeholder-sm ${className}`}
|
|
96
|
-
value={displayTime}
|
|
97
|
-
onChange={(e) => {
|
|
98
|
-
const newStr = e.target.value
|
|
99
|
-
const isValid = /^[0-9]{2}:[0-9]{2}$/.test(newStr)
|
|
100
|
-
if (!isValid) {
|
|
101
|
-
setDisplayTime(newStr)
|
|
102
|
-
return
|
|
103
|
-
}
|
|
104
|
-
const [hours, minutes] = newStr.split(':').map(Number)
|
|
105
|
-
const newNum = (hours * 60 + minutes) * 60 * 1000
|
|
106
|
-
|
|
107
|
-
setTime(newNum)
|
|
108
|
-
setDisplayTime(newStr)
|
|
109
|
-
}}
|
|
110
|
-
placeholder={placeholder}
|
|
111
|
-
required={required}
|
|
112
|
-
/>
|
|
113
|
-
</div>
|
|
114
|
-
</Dropdown>
|
|
115
|
-
)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export default FieldTime
|
|
119
|
-
|
|
120
|
-
const style = css`
|
|
121
|
-
.time-picker-container {
|
|
122
|
-
padding: 15px;
|
|
123
|
-
font-size: 14px;
|
|
124
|
-
background-color: white;
|
|
125
|
-
border-radius: 8px;
|
|
126
|
-
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.time-picker-selectors {
|
|
130
|
-
display: flex;
|
|
131
|
-
justify-content: center;
|
|
132
|
-
align-items: center;
|
|
133
|
-
gap: 5px;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
.time-separator {
|
|
137
|
-
font-size: 18px;
|
|
138
|
-
font-weight: bold;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
.time-select {
|
|
142
|
-
width: 60px;
|
|
143
|
-
padding: 8px;
|
|
144
|
-
font-size: 14px;
|
|
145
|
-
border: 1px solid #ccc;
|
|
146
|
-
border-radius: 4px;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.time-confirm-btn {
|
|
150
|
-
padding: 8px 16px;
|
|
151
|
-
font-size: 16px;
|
|
152
|
-
background-color: ${theme`colors.primary`};
|
|
153
|
-
color: white;
|
|
154
|
-
border: none;
|
|
155
|
-
border-radius: 4px;
|
|
156
|
-
cursor: pointer;
|
|
157
|
-
margin-top: 10px;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
.time-confirm-btn:hover {
|
|
161
|
-
background-color: ${theme`colors.primary`};
|
|
162
|
-
}
|
|
163
|
-
`
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { theme } from 'twin.macro'
|
|
2
|
-
|
|
3
|
-
const ClockIcon = () => {
|
|
4
|
-
return (
|
|
5
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
6
|
-
<g opacity="0.5" clipPath="url(#clip0_12681_10783)">
|
|
7
|
-
<path d="M8.00016 3.99998V7.99998L10.6668 9.33331M14.6668 7.99998C14.6668 11.6819 11.6821 14.6666 8.00016 14.6666C4.31826 14.6666 1.3335 11.6819 1.3335 7.99998C1.3335 4.31808 4.31826 1.33331 8.00016 1.33331C11.6821 1.33331 14.6668 4.31808 14.6668 7.99998Z" stroke={theme`colors.input`} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
8
|
-
</g>
|
|
9
|
-
<defs>
|
|
10
|
-
<clipPath id="clip0_12681_10783">
|
|
11
|
-
<rect width="16" height="16" fill="white" />
|
|
12
|
-
</clipPath>
|
|
13
|
-
</defs>
|
|
14
|
-
</svg>
|
|
15
|
-
)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export default ClockIcon
|