react#FocusEvent TypeScript Examples
The following examples show how to use
react#FocusEvent.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: QueryOptions.tsx From grafana-chinese with Apache License 2.0 | 6 votes |
onOverrideTime = (event: FocusEvent<HTMLInputElement>, status: InputStatus) => {
const { value } = event.target;
const { panel } = this.props;
const emptyToNullValue = emptyToNull(value);
if (status === InputStatus.Valid && panel.timeFrom !== emptyToNullValue) {
panel.timeFrom = emptyToNullValue;
panel.refresh();
}
};
Example #2
Source File: useFocus.ts From use-platform with MIT License | 6 votes |
export function useFocus<T extends HTMLElement>(props: UseFocusProps<T>): UseFocusResult<T> {
const propsRef = useRef(props)
propsRef.current = props
const onFocus = useCallback((event: FocusEvent<T>) => {
const { onFocus, onFocusChange } = propsRef.current
if (event.target === event.currentTarget) {
onFocus?.(event)
onFocusChange?.(true)
}
}, [])
const onBlur = useCallback((event: FocusEvent<T>) => {
const { onBlur, onFocusChange } = propsRef.current
if (event.target === event.currentTarget) {
onBlur?.(event)
onFocusChange?.(false)
}
}, [])
const focusProps: HTMLAttributes<T> = {}
if (!props.disabled) {
if (props.onFocus || props.onFocusChange) {
focusProps.onFocus = onFocus
}
if (props.onBlur || props.onFocusChange) {
focusProps.onBlur = onBlur
}
}
return {
focusProps,
}
}
Example #3
Source File: SimVarControlElement.tsx From ace with GNU Affero General Public License v3.0 | 6 votes |
EditableSimVarControlValue: FC<EditableSimVarControlValueProps> = ({ value, unit, onInput }) => {
const editSpanRef = useRef<HTMLSpanElement>(null);
useEffect(() => {
if (editSpanRef.current) {
editSpanRef.current.textContent = String(value);
}
}, [value]);
const handleBlur = useCallback((e: FocusEvent) => {
onInput((e.target as HTMLElement).textContent);
}, [onInput]);
return (
<code className="mr-auto font-semibold">
<span
ref={editSpanRef}
contentEditable
suppressContentEditableWarning
className="inline-block outline-none bg-navy-medium text-green-500 rounded-md px-2 py-1"
onBlur={handleBlur}
style={{
minWidth: '4rem',
maxWidth: '8rem',
}}
/>
{' '}
<span className="text-green-400">
{unit}
</span>
</code>
);
}
Example #4
Source File: QueryOptions.tsx From grafana-chinese with Apache License 2.0 | 6 votes |
onTimeShift = (event: FocusEvent<HTMLInputElement>, status: InputStatus) => {
const { value } = event.target;
const { panel } = this.props;
const emptyToNullValue = emptyToNull(value);
if (status === InputStatus.Valid && panel.timeShift !== emptyToNullValue) {
panel.timeShift = emptyToNullValue;
panel.refresh();
}
};
Example #5
Source File: layouts.ts From geist-ui with MIT License | 6 votes |
useRect = (initialState?: ReactiveDomReact | (() => ReactiveDomReact)) => {
const [rect, setRect] = useState<ReactiveDomReact>(initialState || defaultRect)
const updateRect = (
eventOrRef:
| MouseEvent<HTMLElement>
| FocusEvent<HTMLElement>
| MutableRefObject<HTMLElement | null>,
getContainer?: () => HTMLElement | null,
) => {
if (isRefTarget(eventOrRef)) return setRect(getRefRect(eventOrRef, getContainer))
setRect(getEventRect(eventOrRef, getContainer))
}
return {
rect,
setRect: updateRect,
}
}
Example #6
Source File: TextBoxVariablePicker.tsx From grafana-chinese with Apache License 2.0 | 5 votes |
onQueryBlur = (event: FocusEvent<HTMLInputElement>) => {
if (this.props.variable.current.value !== this.props.variable.query) {
variableAdapters.get(this.props.variable.type).updateOptions(this.props.variable);
}
};
Example #7
Source File: SearchInput.tsx From react-search-autocomplete with MIT License | 5 votes |
export default function SearchInput({
searchString,
setSearchString,
setHighlightedItem,
autoFocus,
onBlur,
onFocus,
onClear,
placeholder,
showIcon = true,
showClear = true
}: SearchInputProps) {
const ref = useRef<HTMLInputElement>(null)
let manualFocus = true
const setFocus = () => {
manualFocus = false
ref?.current && ref.current.focus()
manualFocus = true
}
const handleOnFocus = (event: FocusEvent<HTMLInputElement, Element>) => {
manualFocus && onFocus(event)
}
return (
<StyledSearchInput>
<SearchIcon showIcon={showIcon} />
<input
ref={ref}
spellCheck={false}
value={searchString}
onChange={setSearchString}
onBlur={onBlur}
onFocus={handleOnFocus}
placeholder={placeholder}
autoFocus={autoFocus}
onKeyDown={(event) => setHighlightedItem({ event })}
/>
<ClearIcon
showClear={showClear}
setSearchString={setSearchString}
searchString={searchString}
onClear={onClear}
setFocus={setFocus}
/>
</StyledSearchInput>
)
}
Example #8
Source File: ConstantVariableEditor.tsx From grafana-chinese with Apache License 2.0 | 5 votes |
onBlur = (event: FocusEvent<HTMLInputElement>) => {
this.props.onPropChange({
propName: 'query',
propValue: event.target.value,
updateOptions: true,
});
};
Example #9
Source File: NumberInput.tsx From ble with Apache License 2.0 | 5 votes |
NumberInput: FunctionComponent<Props> = ({ value, onChange, onBlur, min, max, step, ...props }) => {
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'set': {
const safeValue = !isValid(action.valueAsNumber, { min, max, step }) ? state.latestValidValue : action.valueAsNumber;
return {
...state,
innerValue: action.value,
latestValidValue: safeValue,
};
}
case 'resetLatestSafe':
return {
...state,
innerValue: state.latestValidValue.toString(),
};
default:
throw new Error('Invalid action type');
}
}
const [{ innerValue, latestValidValue }, dispatch] = useReducer(reducer, {
innerValue: value.toString(),
latestValidValue: value,
});
useEffect(() => {
dispatch({
type: 'set',
value: value.toString(),
valueAsNumber: value,
});
}, [value]);
// state updates are asynchronous
// so we do our onChange here
useEffect(() => {
if (onChange !== undefined && latestValidValue !== value) {
onChange(latestValidValue);
}
}, [latestValidValue]);
function onInnerChange(ev: ChangeEvent<HTMLInputElement>): void {
dispatch({
type: 'set',
value: ev.target.value,
valueAsNumber: ev.target.valueAsNumber,
});
}
// put the latest correct value when losing focus
function onInnerBlur(ev: FocusEvent<HTMLInputElement>): void {
dispatch({
type: 'resetLatestSafe',
});
if (onBlur !== undefined) {
onBlur(ev);
}
}
return (
<input {...props} type="number" value={innerValue} onChange={onInnerChange} onBlur={onInnerBlur}/>
);
}
Example #10
Source File: CustomVariableEditor.tsx From grafana-chinese with Apache License 2.0 | 5 votes |
onBlur = (event: FocusEvent<HTMLInputElement>) => {
this.props.onPropChange({
propName: 'query',
propValue: event.target.value,
updateOptions: true,
});
};
Example #11
Source File: InfiniteRange.tsx From ble with Apache License 2.0 | 5 votes |
NumberInput: FunctionComponent<Props> = ({ value, onChange, onBlur, onPointerUp, min = 0, step, ...props }) => {
const maxClamp = Math.max(min, props.maxClamp || 100);
const [max, setMax] = useState(maxClamp);
function clampedSetMax(max_: number) {
setMax(Math.max(max_, maxClamp));
}
function onInnerChange(ev: ChangeEvent<HTMLInputElement>) {
if (onChange) {
onChange(ev.target.valueAsNumber);
}
}
function onInnerBlur(ev: FocusEvent<HTMLInputElement>) {
clampedSetMax(ev.target.valueAsNumber * 2);
if (onBlur) {
onBlur(ev);
}
}
function onInnerPointerUp(ev: PointerEvent<HTMLInputElement>) {
// we keep the value for our setTimeout
const val = (ev.target as HTMLInputElement).valueAsNumber;
// we wait for the next tick to let onChange happen first
window.setTimeout(() => {
clampedSetMax(val * 2);
}, 0);
if (onPointerUp) {
onPointerUp(ev);
}
}
return (
<input
{...props}
type="range"
value={value}
min={min}
max={max}
onChange={onInnerChange}
onBlur={onInnerBlur}
onPointerUpCapture={onInnerPointerUp}
/>
);
}
Example #12
Source File: DestinationParam.tsx From ble with Apache License 2.0 | 5 votes |
DestinationParam: FunctionComponent<Props> = ({ params }) => {
const [destination, setDestination] = useState(params.destination);
useEffect(() => {
setDestination(params.destination);
}, [params.destination]);
const onChange = (ev: ChangeEvent<HTMLInputElement>): void => {
setDestination(ev.target.value);
// we must cancel the potential previous invalid state
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/setCustomValidity
ev.target.setCustomValidity('');
const valid = ev.target.checkValidity();
if (valid) {
const value = ev.target.value === '' ? undefined : ev.target.value;
params.setDestination(value);
}
};
const onBlur = (ev: FocusEvent<HTMLInputElement>): void => {
const valid = ev.target.checkValidity();
if (!valid) {
ev.target.setCustomValidity('Must be a level URL or a level ID');
ev.target.reportValidity();
}
};
return (
<label title="The level where this door leads. Leave empty to go to the menu.">
<FontAwesomeIcon icon={faLink}/>
 
destination level:
 
<input
type="text"
value={destination || ''}
onChange={onChange} placeholder="https://bombhopper.io/?level=b3cd72ad-e47c-4aac-a720-3ea871d0330c"
pattern={doorDestinationRegex.source}
onBlur={onBlur}
/>
</label>
);
}
Example #13
Source File: layouts.ts From geist-ui with MIT License | 5 votes |
isRefTarget = (
eventOrRef:
| MouseEvent<HTMLElement>
| FocusEvent<HTMLElement>
| MutableRefObject<HTMLElement | null>,
): eventOrRef is MutableRefObject<HTMLElement | null> => {
return typeof (eventOrRef as any)?.target === 'undefined'
}
Example #14
Source File: layouts.ts From geist-ui with MIT License | 5 votes |
getEventRect = (
event?: MouseEvent<HTMLElement> | FocusEvent<HTMLElement>,
getContainer?: () => HTMLElement | null,
) => {
const rect = (event?.target as HTMLElement)?.getBoundingClientRect()
if (!rect) return defaultRect
return getRectFromDOMWithContainer(rect, getContainer)
}
Example #15
Source File: useFocusWithin.ts From use-platform with MIT License | 5 votes |
export function useFocusWithin<T extends HTMLElement>(
props: UseFocusWithinProps<T>,
): UseFocusWithinResult<T> {
const propsRef = useRef(props)
const stateRef = useRef({ within: false })
propsRef.current = props
const onFocus = useCallback((event: FocusEvent<T>) => {
const { onFocusWithin, onFocusWithinChange } = propsRef.current
const { current: state } = stateRef
if (!state.within) {
state.within = true
onFocusWithin?.(event)
onFocusWithinChange?.(true)
}
}, [])
const onBlur = useCallback((event: FocusEvent<T>) => {
const { onBlurWithin, onFocusWithinChange } = propsRef.current
const { current: state } = stateRef
if (state.within && !event.currentTarget.contains(event.relatedTarget as HTMLElement | null)) {
state.within = false
onBlurWithin?.(event)
onFocusWithinChange?.(false)
}
}, [])
const focusWithinProps: HTMLAttributes<T> = {}
if (!props.disabled && (props.onFocusWithin || props.onBlurWithin || props.onFocusWithinChange)) {
focusWithinProps.onFocus = onFocus
focusWithinProps.onBlur = onBlur
}
return {
focusWithinProps,
}
}
Example #16
Source File: useCalendar.ts From use-platform with MIT License | 4 votes |
export function useCalendar(props: UseCalendarProps, state: CalendarState): UseCalendarResult {
const { readOnly, disabled } = props
const { mode, focusedDate, focusCalendar, focusDate, moveDate } = state
const { isRTL } = useLocale()
const onFocus = () => {
focusCalendar(true)
}
const onBlur = (event: FocusEvent) => {
const { relatedTarget, currentTarget } = event
if (!currentTarget.contains(relatedTarget as HTMLElement)) {
focusCalendar(false)
}
}
const onKeyDown = (event: KeyboardEvent<HTMLElement>) => {
let action: CalendarNavigationAction | null = null
switch (event.key) {
case 'ArrowLeft':
action = isRTL ? CalendarNavigationAction.NextCell : CalendarNavigationAction.PrevCell
break
case 'ArrowRight':
action = isRTL ? CalendarNavigationAction.PrevCell : CalendarNavigationAction.NextCell
break
case 'ArrowUp':
action = CalendarNavigationAction.UpperCell
break
case 'ArrowDown':
action = CalendarNavigationAction.LowerCell
break
case 'Home':
action = event.shiftKey
? CalendarNavigationAction.FirstCell
: CalendarNavigationAction.StartCell
break
case 'End':
action = event.shiftKey
? CalendarNavigationAction.LastCell
: CalendarNavigationAction.EndCell
break
case 'PageUp':
action = event.shiftKey
? CalendarNavigationAction.PrevExtraView
: CalendarNavigationAction.PrevView
break
case 'PageDown':
action = event.shiftKey
? CalendarNavigationAction.NextExtraView
: CalendarNavigationAction.NextView
break
}
if (action !== null) {
event.preventDefault()
focusDate(moveDate(focusedDate, action))
}
}
const gridProps: HTMLAttributes<HTMLElement> = {
role: 'grid',
'aria-readonly': readOnly,
'aria-disabled': disabled,
'aria-multiselectable': mode === 'multiple' || mode === 'range',
onFocus,
onBlur,
onKeyDown,
}
return {
gridProps,
}
}
Example #17
Source File: modal-entry.tsx From keycaplendar with MIT License | 4 votes |
ModalEntry = ({
keyset: propsKeyset,
loading,
onClose,
onSubmit,
open,
user,
}: ModalEntryProps) => {
const device = useAppSelector(selectDevice);
const allDesigners = useAppSelector(selectAllDesigners);
const allProfiles = useAppSelector(selectAllProfiles);
const allVendors = useAppSelector(selectAllVendors);
const allVendorRegions = useAppSelector(selectAllVendorRegions);
const [keyset, updateKeyset] = useImmer<KeysetState>(
partialSet({ alias: nanoid(10) })
);
const keyedKeysetUpdate =
<K extends keyof KeysetState>(key: K, payload: KeysetState[K]) =>
(draft: KeysetState) => {
draft[key] = payload;
};
const [salesImageLoaded, setSalesImageLoaded] = useState(false);
const [focused, setFocused] = useState("");
useEffect(() => {
if (!user.isEditor && user.isDesigner) {
updateKeyset(keyedKeysetUpdate("designer", [user.nickname]));
}
if (!open) {
updateKeyset(partialSet({ alias: nanoid(10) }));
setSalesImageLoaded(false);
setFocused("");
}
}, [open]);
useEffect(() => {
if (propsKeyset) {
updateKeyset(
produce(propsKeyset, (draft) => {
draft.alias ??= nanoid(10);
draft.gbMonth ??= false;
draft.notes ??= "";
draft.sales ??= {
img: "",
thirdParty: false,
};
draft.sales.img ??= "";
draft.sales.thirdParty ??= false;
draft.vendors ??= [];
draft.vendors.forEach((vendor) => {
vendor.id ??= nanoid();
});
if (draft.gbMonth && draft.gbLaunch.length === 10) {
draft.gbLaunch = draft.gbLaunch.slice(0, 7);
}
})
);
}
}, [propsKeyset, open]);
const setImage = (image: Blob | File) =>
updateKeyset(keyedKeysetUpdate("image", image));
const handleFocus = (e: FocusEvent<HTMLInputElement>) =>
setFocused(e.target.name);
const handleBlur = () => setFocused("");
const toggleDate = () =>
updateKeyset((keyset) => {
keyset.gbMonth = !keyset.gbMonth;
});
const selectValue = (prop: string, value: string) => {
if (hasKey(keyset, prop)) {
updateKeyset(keyedKeysetUpdate(prop, value));
}
setFocused("");
};
const selectValueAppend = (prop: string, value: string) =>
updateKeyset((draft) => {
if (hasKey(draft, prop)) {
const { [prop]: original } = draft;
if (original) {
if (is<string[]>(original)) {
original[original.length - 1] = value;
setFocused("");
} else if (is<string>(original)) {
const array = original.split(", ");
array[array.length - 1] = value;
draft[prop as KeysMatching<KeysetState, string>] = array.join(", ");
setFocused("");
}
}
}
});
const selectVendor = (prop: string, value: string) => {
const property = prop.replace(/\d/g, "");
const index = parseInt(prop.replace(/\D/g, ""));
updateKeyset((draft) => {
const {
vendors: { [index]: vendor },
} = draft;
if (hasKey(vendor, property)) {
vendor[property] = value;
setFocused("");
}
});
};
const selectVendorAppend = (prop: string, value: string) => {
const property = prop.replace(/\d/g, "");
const index = parseInt(prop.replace(/\D/g, ""));
updateKeyset((draft) => {
const {
vendors: { [index]: vendor },
} = draft;
if (hasKey(vendor, property)) {
const { [property]: original } = vendor;
if (typeof original !== "undefined") {
const array = original.split(", ");
array[array.length - 1] = value;
vendor[property] = array.join(", ");
setFocused("");
}
}
});
};
const handleChange = ({
target: { checked, name, value },
}: ChangeEvent<HTMLInputElement>) => {
if (name === "designer") {
updateKeyset(keyedKeysetUpdate(name, value.split(", ")));
} else if (name === "shipped") {
updateKeyset(keyedKeysetUpdate(name, checked));
} else if (hasKey(keyset, name)) {
updateKeyset(keyedKeysetUpdate(name, value));
}
};
const handleSalesImage = ({
target: { checked, name, value },
}: ChangeEvent<HTMLInputElement>) => {
if (hasKey(keyset.sales, name)) {
if (name === "thirdParty") {
updateKeyset((keyset) => {
keyset.sales[name] = checked;
});
} else {
updateKeyset((keyset) => {
keyset.sales[name] = value;
});
}
}
};
const handleNamedChange =
<Key extends keyof KeysetState>(name: Key) =>
(value: KeysetState[Key]) =>
updateKeyset(keyedKeysetUpdate(name, value));
const handleChangeVendor = ({
target: { name, value },
}: ChangeEvent<HTMLInputElement>) => {
const property = name.replace(/\d/g, "");
const index = parseInt(name.replace(/\D/g, ""));
updateKeyset((draft) => {
const {
vendors: { [index]: vendor },
} = draft;
if (hasKey(vendor, property)) {
vendor[property] = value;
}
});
};
const handleNamedChangeVendor =
(name: keyof VendorType, index: number) => (value: string) =>
updateKeyset((draft) => {
const {
vendors: { [index]: vendor },
} = draft;
if (hasKey(vendor, name)) {
vendor[name] = value;
}
});
const handleChangeVendorEndDate = (e: ChangeEvent<HTMLInputElement>) => {
const index = parseInt(e.target.name.replace(/\D/g, ""));
updateKeyset((draft) => {
const {
vendors: { [index]: vendor },
} = draft;
if (e.target.checked) {
vendor.endDate = "";
} else {
delete vendor.endDate;
}
});
};
const addVendor = () => {
const emptyVendor = {
id: nanoid(),
name: "",
region: "",
storeLink: "",
};
updateKeyset((draft) => {
draft.vendors.push(emptyVendor);
});
};
const removeVendor = (index: number) =>
updateKeyset((draft) => {
draft.vendors.splice(index, 1);
});
const handleDragVendor = (result: DropResult) => {
if (!result.destination) return;
updateKeyset((draft) => {
arrayMove(
draft.vendors,
result.source.index,
result.destination?.index || 0
);
});
};
const result = useMemo(
() =>
gbMonthCheck(
SetSchema.extend({
id: z.string().min(propsKeyset ? 1 : 0),
image: z.union([z.string().url(), z.instanceof(Blob)]),
})
).safeParse(keyset),
[keyset]
);
const useDrawer = device !== "mobile";
const dateCard = keyset.gbMonth ? (
<Card className="date-container" outlined>
<Typography className="date-title" tag="h3" use="caption">
Month
</Typography>
<div className="date-form">
<DatePicker
allowQuarter
autoComplete="off"
icon={iconObject(<CalendarToday />)}
label="GB month"
month
name="gbLaunch"
onChange={handleNamedChange("gbLaunch")}
outlined
showNowButton
value={keyset.gbLaunch}
/>
</div>
<CardActions>
<CardActionButtons>
<CardActionButton label="Date" onClick={toggleDate} type="button" />
</CardActionButtons>
</CardActions>
</Card>
) : (
<Card className="date-container" outlined>
<Typography className="date-title" tag="h3" use="caption">
Date
</Typography>
<div className="date-form">
<DatePicker
allowQuarter
autoComplete="off"
icon={iconObject(<CalendarToday />)}
label="GB launch"
name="gbLaunch"
onChange={handleNamedChange("gbLaunch")}
outlined
showNowButton
value={keyset.gbLaunch}
/>
<DatePicker
autoComplete="off"
fallbackValue={keyset.gbLaunch}
icon={iconObject(<CalendarToday />)}
label="GB end"
name="gbEnd"
onChange={handleNamedChange("gbEnd")}
outlined
showNowButton
value={keyset.gbEnd}
/>
</div>
<CardActions>
<CardActionButtons>
<CardActionButton label="Month" onClick={toggleDate} type="button" />
</CardActionButtons>
</CardActions>
</Card>
);
return (
<BoolWrapper
condition={useDrawer}
falseWrapper={(children) => (
<FullScreenDialog className="entry-modal" {...{ onClose, open }}>
{children}
</FullScreenDialog>
)}
trueWrapper={(children) => (
<Drawer
className="drawer-right entry-modal"
modal
{...{ onClose, open }}
>
{children}
</Drawer>
)}
>
<BoolWrapper
condition={useDrawer}
falseWrapper={(children) => (
<FullScreenDialogAppBar>
<TopAppBarRow>{children}</TopAppBarRow>
<LinearProgress
closed={!loading}
progress={typeof loading === "number" ? loading : undefined}
/>
</FullScreenDialogAppBar>
)}
trueWrapper={(children) => (
<DrawerHeader>
{children}
<LinearProgress
closed={!loading}
progress={typeof loading === "number" ? loading : undefined}
/>
</DrawerHeader>
)}
>
<BoolWrapper
condition={useDrawer}
falseWrapper={(children) => (
<TopAppBarSection alignStart>
<TopAppBarNavigationIcon icon="close" onClick={onClose} />
<TopAppBarTitle>{children}</TopAppBarTitle>
</TopAppBarSection>
)}
trueWrapper={(children) => <DrawerTitle>{children}</DrawerTitle>}
>
{propsKeyset ? "Edit" : "Create"} Entry
</BoolWrapper>
<ConditionalWrapper
condition={!useDrawer}
wrapper={(children) => (
<TopAppBarSection alignEnd>{children}</TopAppBarSection>
)}
>
<Button
disabled={!result?.success || !!loading}
label="Save"
onClick={() => onSubmit(keyset)}
outlined={useDrawer}
type="button"
/>
</ConditionalWrapper>
</BoolWrapper>
<BoolWrapper
condition={useDrawer}
falseWrapper={(children) => (
<FullScreenDialogContent>{children}</FullScreenDialogContent>
)}
trueWrapper={(children) => <DrawerContent>{children}</DrawerContent>}
>
<div className="banner">
<div className="banner-text">Make sure to read the entry guide.</div>
<div className="banner-button">
<a
href="/guides?guideId=JLB4xxfx52NJmmnbvbzO"
rel="noopener noreferrer"
target="_blank"
>
<Button label="guide" />
</a>
</div>
</div>
<form className="form">
<div className="form-double">
<div className="select-container">
<MenuSurfaceAnchor>
<TextField
autoComplete="off"
label="Profile"
name="profile"
onBlur={handleBlur}
onChange={handleChange}
onFocus={handleFocus}
outlined
required
value={keyset.profile}
/>
<Autocomplete
array={allProfiles}
minChars={1}
open={focused === "profile"}
prop="profile"
query={keyset.profile}
select={selectValue}
/>
</MenuSurfaceAnchor>
</div>
<div className="field-container">
<TextField
autoComplete="off"
className="field"
label="Colorway"
name="colorway"
onBlur={handleBlur}
onChange={handleChange}
onFocus={handleFocus}
outlined
required
value={keyset.colorway}
/>
</div>
</div>
<MenuSurfaceAnchor>
<TextField
autoComplete="off"
disabled={user.isEditor === false && user.isDesigner}
helpText={{
children: (
<>
Separate multiple designers with{" "}
<code className="multiline">, </code>.
</>
),
persistent: true,
}}
label="Designer"
name="designer"
onBlur={handleBlur}
onChange={handleChange}
onFocus={handleFocus}
outlined
required
value={keyset.designer.join(", ")}
/>
<Autocomplete
array={allDesigners}
listSplit
minChars={2}
open={focused === "designer"}
prop="designer"
query={keyset.designer.join(", ")}
select={selectValueAppend}
/>
</MenuSurfaceAnchor>
<DatePicker
autoComplete="off"
icon={iconObject(<CalendarToday />)}
label="IC date"
name="icDate"
onChange={handleNamedChange("icDate")}
outlined
pickerProps={{ disableFuture: true }}
required
showNowButton
value={keyset.icDate}
/>
<TextField
autoComplete="off"
helpText={{
children: "Must be valid link",
persistent: false,
validationMsg: true,
}}
icon="link"
label="Details"
name="details"
onBlur={handleBlur}
onChange={handleChange}
onFocus={handleFocus}
outlined
pattern={validLink}
required
value={keyset.details}
/>
<TextField
autoComplete="off"
label="Notes"
name="notes"
onBlur={handleBlur}
onChange={handleChange}
onFocus={handleFocus}
outlined
rows={2}
textarea
value={keyset.notes}
/>
<ImageUpload
desktop={device === "desktop"}
image={keyset.image}
setImage={setImage}
/>
{dateCard}
<Checkbox
checked={keyset.shipped}
id="create-shipped"
label="Shipped"
name="shipped"
onChange={handleChange}
/>
<Typography className="subheader" tag="h3" use="caption">
Vendors
</Typography>
<DragDropContext onDragEnd={handleDragVendor}>
<Droppable droppableId="vendors-create">
{(provided) => (
<div
ref={provided.innerRef}
className="vendors-container"
{...provided.droppableProps}
>
{keyset.vendors.map((vendor, index) => {
const endDateField =
typeof vendor.endDate === "string" ? (
<DatePicker
autoComplete="off"
icon={iconObject(<CalendarToday />)}
label="End date"
name={`endDate${index}`}
onChange={handleNamedChangeVendor("endDate", index)}
outlined
required
showNowButton
value={vendor.endDate}
/>
) : null;
return (
<Draggable
key={vendor.id}
draggableId={vendor.id ?? index.toString()}
index={index}
>
{(provided, snapshot) => (
<Card
ref={provided.innerRef}
className={classNames("vendor-container", {
dragged: snapshot.isDragging,
})}
outlined
{...provided.draggableProps}
style={getVendorStyle(provided)}
>
<div className="title-container">
<Typography
className="vendor-title"
use="caption"
>
{`Vendor ${index + 1}`}
</Typography>
{withTooltip(
<IconButton
icon={iconObject(<Delete />)}
onClick={() => {
removeVendor(index);
}}
type="button"
/>,
"Delete"
)}
{withTooltip(
<Icon
className="drag-handle"
icon="drag_handle"
{...provided.dragHandleProps}
/>,
"Drag"
)}
</div>
<div className="vendor-form">
<MenuSurfaceAnchor>
<TextField
autoComplete="off"
icon={iconObject(<Store />)}
label="Name"
name={`name${index}`}
onBlur={handleBlur}
onChange={handleChangeVendor}
onFocus={handleFocus}
outlined
required
value={vendor.name}
/>
<Autocomplete
array={allVendors}
minChars={1}
open={focused === `name${index}`}
prop={`name${index}`}
query={vendor.name}
select={selectVendor}
/>
</MenuSurfaceAnchor>
<MenuSurfaceAnchor>
<TextField
autoComplete="off"
icon={iconObject(<Public />)}
label="Region"
name={`region${index}`}
onBlur={handleBlur}
onChange={handleChangeVendor}
onFocus={handleFocus}
outlined
required
value={vendor.region}
/>
<Autocomplete
array={allVendorRegions}
listSplit
minChars={1}
open={focused === `region${index}`}
prop={`region${index}`}
query={vendor.region}
select={selectVendorAppend}
/>
</MenuSurfaceAnchor>
<TextField
autoComplete="off"
helpText={{
children: "Must be valid link",
persistent: false,
validationMsg: true,
}}
icon="link"
label="Store link"
name={`storeLink${index}`}
onBlur={handleBlur}
onChange={handleChangeVendor}
onFocus={handleFocus}
outlined
pattern={validLink}
value={vendor.storeLink}
/>
<Checkbox
checked={
!!vendor.endDate || vendor.endDate === ""
}
className="end-date-field"
id={`editEndDate${index}`}
label="Different end date"
name={`endDate${index}`}
onChange={handleChangeVendorEndDate}
/>
{endDateField}
</div>
</Card>
)}
</Draggable>
);
})}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
<div className="add-button">
<Button
label="Add vendor"
onClick={addVendor}
outlined
type="button"
/>
</div>
<Card className="sales-container" outlined>
<Typography className="sales-title" tag="h3" use="caption">
Sales
</Typography>
<div
className={classNames("sales-image", {
loaded: salesImageLoaded,
})}
>
<div className="sales-image-icon">
<Icon icon={iconObject(<AddPhotoAlternate />)} />
</div>
<img
alt=""
onError={() => {
setSalesImageLoaded(false);
}}
onLoad={() => {
setSalesImageLoaded(true);
}}
src={keyset.sales.img}
/>
</div>
<div className="sales-field">
<TextField
autoComplete="off"
helpText={{
children: "Must be direct link to image",
persistent: true,
validationMsg: true,
}}
icon="link"
label="URL"
name="img"
onBlur={handleBlur}
onChange={handleSalesImage}
onFocus={handleFocus}
outlined
pattern={validLink}
value={keyset.sales.img}
/>
<Checkbox
checked={keyset.sales.thirdParty}
className="sales-checkbox"
label="Third party graph"
name="thirdParty"
onChange={handleSalesImage}
/>
</div>
</Card>
</form>
</BoolWrapper>
</BoolWrapper>
);
}
Example #18
Source File: useAutocomplete.ts From kodiak-ui with MIT License | 4 votes |
export function useAutocomplete({
isOpen: isOpenProp,
isClearable = true,
isDisabled = false,
isMulti = false,
value: valueProp,
inputValue: inputValueProp,
componentName = 'useAutocomplete',
defaultValue = null,
openOnFocus = true,
pageSize = 5,
blurOnSelect = false,
clearOnBlur = false,
clearOnEscape = false,
closeOnSelect = true,
options,
onCloseChange,
onOpenChange,
onValueChange,
onInputValueChange,
onHighlightedIndexChange,
getOptionSelected = (option, value) => option === value,
getOptionDisabled,
}: UseAutocompleteProps) {
const inputRef = useRef<HTMLInputElement>(null)
const listboxRef = useRef<HTMLElement>(null)
const highlightedIndexRef = useRef<number>(-1)
const uid = useId()
const id = `kodiak-autocomplete-${uid}`
const [isOpen, setIsOpen] = useControlled<boolean>({
controlled: isOpenProp,
default: false,
name: componentName,
state: 'open',
})
const [value, setValue] = useControlled<string | string[]>({
controlled: valueProp,
default: defaultValue,
name: componentName,
state: 'value',
})
const [inputValue, setInputValue] = useControlled<string>({
controlled: inputValueProp,
default: '',
name: componentName,
state: 'inputValue',
})
const [isFocused, setIsFocused] = useState(false)
const [isPristine, setIsPristine] = useState(false)
const filteredOptions = useFilterOptions({
isOpen,
options,
inputValue:
!isMulti && value !== null && value === inputValue && isPristine
? ''
: inputValue,
})
const setHighlightedIndex = useHighlightIndex(
{
id,
isOpen,
options: filteredOptions,
onHighlightedIndexChange,
},
inputRef,
listboxRef,
highlightedIndexRef,
)
const isAvailable = isOpen && filteredOptions?.length > 0
const isDirty =
inputValue?.length > 0 || isMulti ? value?.length > 0 : value !== null
const hasValue = isMulti ? value?.length > 0 : value
useEffect(
function handleScrollSelectedOptionIntoView() {
const valueArray = isMulti ? value : [value]
if (!isOpen || !listboxRef?.current || valueArray?.length === 0) {
return
}
const valueItem = valueArray?.[0]
const valueIndex = filteredOptions?.findIndex(option =>
getOptionSelected(option, valueItem),
)
if (valueIndex !== -1) {
setHighlightedIndex({ index: valueIndex })
}
},
[
filteredOptions,
getOptionSelected,
isMulti,
isOpen,
setHighlightedIndex,
value,
],
)
const handleResetInputValue = useCallback(
(event: InteractionEvent, newValue?: string) => {
let newInputValue: string
if (isMulti || newValue === null) {
newInputValue = ''
} else {
newInputValue = newValue
}
setInputValue(newInputValue)
onInputValueChange?.(event, newInputValue)
},
[isMulti, onInputValueChange, setInputValue],
)
const handleOnOpen = useCallback(
event => {
if (isOpen) {
return
}
setIsOpen(true)
setIsPristine(true)
onOpenChange?.(event)
},
[isOpen, onOpenChange, setIsOpen],
)
const handleOnClose = useCallback(
event => {
if (!isOpen) {
return
}
setIsOpen(false)
onCloseChange?.(event)
},
[isOpen, onCloseChange, setIsOpen],
)
const handleToggle = useCallback(
event => {
if (isOpen) {
handleOnClose(event)
} else {
handleOnOpen(event)
}
},
[handleOnClose, handleOnOpen, isOpen],
)
const handleSetValue = useCallback(
(event, newValue) => {
if (value === newValue) {
return
}
onValueChange?.(event, newValue)
setValue(newValue)
},
[onValueChange, setValue, value],
)
const handleSetNewValue = useCallback(
(event: InteractionEvent, option: string) => {
setHighlightedIndex({ diff: 'reset' })
let newValue: string | string[] = option
if (isMulti) {
newValue = Array.isArray(value) ? value.slice() : []
const index = newValue?.findIndex(valueItem =>
getOptionSelected(valueItem, option),
)
if (index === -1) {
if (option) {
newValue.push(option as string)
}
} else {
newValue.splice(index, 1)
}
}
handleSetValue(event, newValue)
handleResetInputValue(event, option as string)
if (closeOnSelect) {
handleOnClose(event)
}
if (blurOnSelect) {
inputRef?.current?.blur()
}
},
[
blurOnSelect,
closeOnSelect,
getOptionSelected,
handleOnClose,
handleResetInputValue,
handleSetValue,
isMulti,
setHighlightedIndex,
value,
],
)
const handleOnClear = useCallback(
(event: InteractionEvent) => {
if (isClearable) {
setInputValue('')
onInputValueChange?.(event, '')
handleSetValue(event, isMulti ? [] : null)
}
},
[handleSetValue, isClearable, isMulti, onInputValueChange, setInputValue],
)
const handleOnKeyDown = useCallback(
(event: KeyboardEvent) => {
switch (event.key) {
case 'PageUp':
event.preventDefault()
setHighlightedIndex({ diff: -pageSize, direction: 'previous' })
handleOnOpen(event)
break
case 'PageDown':
event.preventDefault()
setHighlightedIndex({ diff: pageSize, direction: 'next' })
handleOnOpen(event)
break
case 'ArrowDown':
event.preventDefault()
setHighlightedIndex({ diff: 1, direction: 'next' })
handleOnOpen(event)
break
case 'ArrowUp':
event.preventDefault()
setHighlightedIndex({ diff: -1, direction: 'previous' })
handleOnOpen(event)
break
case 'Escape':
if (isOpen) {
event.preventDefault()
event.stopPropagation()
if (clearOnEscape && (inputValue || hasValue)) {
handleOnClear(event)
}
handleOnClose(event)
}
break
case 'Enter':
if (event.which === 229) {
break
}
if (highlightedIndexRef?.current !== -1 && isOpen) {
event.preventDefault()
const option = filteredOptions?.[highlightedIndexRef?.current]
handleSetNewValue(event, option)
}
break
case 'Backspace':
if (isMulti && inputValue === '' && hasValue) {
const index = value?.length - 1
const newValue: string[] = (value as string[])?.slice()
newValue.splice(index, 1)
handleSetValue(event, newValue)
}
break
default:
}
},
[
clearOnEscape,
filteredOptions,
handleOnClear,
handleOnClose,
handleOnOpen,
handleSetNewValue,
handleSetValue,
hasValue,
inputValue,
isMulti,
isOpen,
pageSize,
setHighlightedIndex,
value,
],
)
function handleOnClick() {
// Always automatically focus on input when opening
inputRef?.current?.focus()
}
const handleOptionOnClick = useCallback(
(event: MouseEvent) => {
const index = Number(
event.currentTarget.getAttribute('data-option-index'),
)
handleSetNewValue(event, filteredOptions[index])
},
[filteredOptions, handleSetNewValue],
)
const handleOnFocus = useCallback(
(event: FocusEvent) => {
setIsFocused(true)
if (openOnFocus) {
handleOnOpen(event)
}
},
[handleOnOpen, openOnFocus],
)
const handleOnBlur = useCallback(
(event: FocusEvent) => {
setIsFocused(false)
setHighlightedIndex({ diff: 'reset' })
if (clearOnBlur) {
handleResetInputValue(event, null)
}
handleOnClose(event)
},
[clearOnBlur, handleOnClose, handleResetInputValue, setHighlightedIndex],
)
const handleOnMouseDown = useCallback(
(event: MouseEvent) => {
if (event?.currentTarget?.getAttribute('id') !== id) {
event.preventDefault()
}
},
[id],
)
const handleInputOnMouseDown = useCallback(
(event: MouseEvent) => {
if (inputValue === '' || !isOpen) {
event.stopPropagation()
handleToggle(event)
}
},
[handleToggle, inputValue, isOpen],
)
function handleListboxOnMouseDown(event) {
event.preventDefault()
}
const handleOptionOnMouseOver = useCallback(
(event: MouseEvent) => {
setHighlightedIndex({
index: Number(event?.currentTarget?.getAttribute('data-option-index')),
})
},
[setHighlightedIndex],
)
const handleInputOnChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
const newValue = event?.target?.value
if (inputValue !== newValue) {
setInputValue(newValue)
setIsPristine(false)
onInputValueChange?.(event, newValue)
}
if (newValue === '') {
if (isClearable && !isMulti) {
handleSetValue(event, null)
}
} else {
handleOnOpen(event)
}
},
[
handleOnOpen,
handleSetValue,
inputValue,
isClearable,
isMulti,
onInputValueChange,
setInputValue,
],
)
const handleTagOnDismiss = useCallback(
(index: number) => event => {
const newValue = value?.slice() as string[]
newValue.splice(index, 1)
handleSetValue(event, newValue)
},
[handleSetValue, value],
)
const getRootProps = useCallback(
(): AutocompleteRootProps => ({
role: 'combobox',
onClick: handleOnClick,
onKeyDown: handleOnKeyDown,
onMouseDown: handleOnMouseDown,
'aria-owns': isAvailable ? `${id}-listbox` : null,
'aria-expanded': isAvailable,
}),
[handleOnKeyDown, handleOnMouseDown, id, isAvailable],
)
const getLabelProps = useCallback(
(): AutocompleteLabelProps => ({
id: `${id}-label`,
htmlFor: id,
}),
[id],
)
const getInputProps = useCallback(
(): AutocompleteInputProps => ({
id,
ref: inputRef,
value: inputValue,
autoComplete: 'none',
autoCapitalize: 'none',
spellCheck: false,
disabled: isDisabled,
onFocus: handleOnFocus,
onBlur: handleOnBlur,
onChange: handleInputOnChange,
onMouseDown: handleInputOnMouseDown,
'aria-controls': isAvailable ? `${id}-listbox` : null,
'aria-autocomplete': 'list' as 'list' | 'both',
'aria-activedescendant': isOpen ? '' : null,
}),
[
id,
inputValue,
isDisabled,
handleOnFocus,
handleOnBlur,
handleInputOnChange,
handleInputOnMouseDown,
isAvailable,
isOpen,
],
)
const getListboxProps = useCallback(
(): AutocompleteListboxProps => ({
id: `${id}-listbox`,
ref: listboxRef as any,
role: 'listbox',
onMouseDown: handleListboxOnMouseDown,
'aria-labelledby': `${id}-label`,
}),
[id],
)
const getOptionProps = useCallback(
({ index, option }): AutocompleteOptionProps => {
const valueArray = isMulti ? value : [value]
const selected =
valueArray?.length > 0 &&
!!(valueArray as string[])?.find(valueItem =>
getOptionSelected<string>(option, valueItem),
)
const disabled = getOptionDisabled?.(option)
return {
key: index,
id: `${id}-option-${index}`,
role: 'option',
tabIndex: -1,
onClick: handleOptionOnClick,
onMouseOver: handleOptionOnMouseOver,
'data-option-index': index,
'aria-disabled': disabled,
'aria-selected': selected,
...(selected ? { 'data-option-selected': true } : null),
}
},
[
getOptionDisabled,
getOptionSelected,
handleOptionOnClick,
handleOptionOnMouseOver,
id,
isMulti,
value,
],
)
const getClearButtonProps = useCallback(
(): AutocompleteInputButtonProps => ({
tabIndex: -1,
onClick: handleOnClear,
}),
[handleOnClear],
)
const getPopoverButtonProps = useCallback(
(): AutocompleteInputButtonProps => ({
tabIndex: -1,
disabled: isDisabled,
onClick: handleToggle,
}),
[handleToggle, isDisabled],
)
const getTagProps = useCallback(
({ index }: { index: number }): AutocompleteTagProps => ({
onDismiss: handleTagOnDismiss(index),
'data-tag-index': index,
}),
[handleTagOnDismiss],
)
return {
isOpen,
isAvailable,
isDirty,
isFocused,
value,
options: filteredOptions,
getRootProps,
getLabelProps,
getInputProps,
getListboxProps,
getOptionProps,
getClearButtonProps,
getPopoverButtonProps,
getTagProps,
}
}
Example #19
Source File: Input.tsx From oxen-website with GNU General Public License v3.0 | 4 votes |
export function Input(props: InputProps) {
const {
className,
inputClassName,
type = 'text',
center = false,
readonly = false,
size = 'medium',
border = 'secondary',
style,
prefix,
duration = true,
suffix,
required,
disabled,
min,
max,
step,
placeholder = '',
inputMode = 'text',
fitHeight = false,
onKeyDown,
onMouseUp,
} = props;
// Focus
const inputRef = useRef<HTMLInputElement>(null);
const setInputFocus = () => {
if (typeof inputRef !== 'string') {
inputRef?.current?.focus();
}
};
// Value
const [value, setValue] = useState('' as string | number);
const [hasFocus, setHasFocus] = useState(false);
// Styles
const fontSize =
size !== 'medium' && size === 'large' ? 'text-lg' : 'text-sm';
// Functions
const handleOnChange = (event: ChangeEvent<HTMLInputElement>) => {
const element = event?.target as HTMLInputElement;
if (element.value === undefined) {
return;
}
// Emails don't support selectionStart
if (type !== 'email') {
const caret = element.selectionStart;
window.requestAnimationFrame(() => {
element.selectionStart = caret;
element.selectionEnd = caret;
});
}
if (props.onValueChange) {
props.onValueChange(element.value);
}
setValue(element.value);
};
const handleOnBlur = (event: FocusEvent<HTMLInputElement>) => {
setHasFocus(false);
if (props.onBlur) {
props.onBlur(event);
}
};
const handleOnFocus = (event: FocusEvent<HTMLInputElement>) => {
if (!readonly) {
setHasFocus(true);
}
if (props.onFocus) {
props.onFocus(event);
}
};
// // Effects
// useEffect(() => {
// if (autofocus) {
// setInputFocus();
// }
// }, []);
return (
<div
style={style ?? {}}
className={classNames(
'flex',
'items-center',
'appearance-none',
'rounded-lg',
'w-full',
// 'bg-white',
'text-gray-700',
'leading-tight',
'focus:outline-black',
border !== 'none' && 'border-2',
size === 'small' ? 'px-2' : 'px-4',
duration && 'duration-300',
disabled && 'opacity-50 cursor-not-allowed',
border === 'primary' && 'border-primary',
border === 'secondary' && hasFocus
? `border-primary`
: 'border-secondary',
className,
)}
onClick={setInputFocus}
>
{prefix && (
<span
className={classNames(`text-black`, 'flex', 'items-center', 'pr-4')}
>
{prefix}
</span>
)}
<input
className={classNames(
'bg-transparent',
'outline-none',
'flex-1',
'w-0',
size === 'large' && 'py-2',
disabled && 'cursor-not-allowed',
center && 'text-center',
fontSize,
inputClassName,
)}
readOnly={readonly}
type={type}
ref={inputRef}
spellCheck={false}
disabled={disabled}
required={required}
placeholder={placeholder}
value={props.value ?? value}
step={step}
min={min}
max={max}
onChange={handleOnChange}
onFocus={handleOnFocus}
onBlur={handleOnBlur}
inputMode={inputMode}
onKeyDown={onKeyDown}
onMouseUp={onMouseUp}
></input>
{type === 'number' && <div className="h-full bg-green-200"></div>}
{suffix && (
<span
className={classNames(`text-primary`, 'flex', 'items-center', 'pl-4')}
>
{suffix}
</span>
)}
</div>
);
}
Example #20
Source File: VerificationCodeInput.tsx From design-system with MIT License | 4 votes |
VerificationCodeInput = (props: VerificationCodeInputProps) => {
const {
type = 'number',
fields = 4,
placeholder = '_',
autoFocus = true,
onComplete,
onFocus,
onBlur,
className,
value,
...rest
} = props;
const initialValues = useMemo(() => {
if (props.value && props.value.length) {
return props.value.split('');
}
return Array(fields).fill('');
}, []);
const initialRefs = useMemo(() => {
return [...Array(fields)].map(() => {
return React.createRef<HTMLInputElement>();
});
}, []);
const [values, setValues] = useState<string[]>(initialValues);
const [refs] = useState<Refs>(initialRefs);
useEffect(() => {
if (refs[0] && refs[0].current && autoFocus) {
refs[0].current.focus({ preventScroll: true });
}
}, []);
useEffect(() => {
const completeValue = values.join('');
if (onComplete && completeValue.length === fields) {
onComplete(completeValue);
}
}, [values]);
const onChangeHandler = (e: ChangeEvent<HTMLInputElement>) => {
const index = parseInt(e.target.dataset.id as string, 10);
const fieldValue = e.target.value;
let nextRef;
const newValues = [...values];
if (!fieldValue) {
return;
}
if (fieldValue.length > 1) {
let nextIndex = fieldValue.length + index - 1;
if (nextIndex >= fields) {
nextIndex = fields - 1;
}
nextRef = refs[nextIndex];
const split = fieldValue.split('');
split.forEach((item: string, i: number) => {
const cursor: number = index + i;
if (cursor < fields) {
newValues[cursor] = item;
}
});
setValues(newValues);
} else {
nextRef = refs[index + 1];
newValues[index] = fieldValue;
setValues(newValues);
}
if (nextRef && nextRef.current) {
nextRef.current.focus({ preventScroll: true });
nextRef.current.select();
}
};
const onFocusHandler = (e: FocusEvent<HTMLInputElement>) => {
e.target.select();
e.target.placeholder = '';
if (onFocus) {
onFocus(e);
}
};
const onBlurHandler = (e: FocusEvent<HTMLInputElement>) => {
e.target.placeholder = placeholder;
if (onBlur) {
onBlur(e);
}
};
const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
const index = parseInt(e.currentTarget.dataset.id as string, 10);
const prevIndex = index - 1;
const nextIndex = index + 1;
const prev = refs[prevIndex];
const nextRef = refs[nextIndex];
switch (e.key) {
case KEY_CODE.backspace: {
e.preventDefault();
const vals = [...values];
if (values[index]) {
vals[index] = '';
setValues(vals);
} else if (prev && prev.current) {
vals[prevIndex] = '';
prev.current.focus({ preventScroll: true });
setValues(vals);
}
break;
}
case KEY_CODE.left: {
e.preventDefault();
if (prev && prev.current) {
prev.current.focus({ preventScroll: true });
}
break;
}
case KEY_CODE.right: {
e.preventDefault();
if (nextRef && nextRef.current) {
nextRef.current.focus({ preventScroll: true });
}
break;
}
case KEY_CODE.up:
case KEY_CODE.down:
case KEY_CODE.e:
case KEY_CODE.E: {
if (type === 'number') {
e.preventDefault();
}
break;
}
default:
break;
}
};
const wrapperClassNames = (i: number) =>
classNames(
{
'VerificationCodeInput-Input': true,
'ml-4': i > 0,
},
className
);
return (
<div data-test="DesignSystem-VerificationCodeInput" className="VerificationCodeInput">
{values.map((val: string, index: number) => (
<Input
key={index}
className={wrapperClassNames(index)}
size="large"
minWidth="40px"
value={val}
placeholder={placeholder}
onChange={onChangeHandler}
onKeyDown={onKeyDown}
onFocus={onFocusHandler}
onBlur={onBlurHandler}
data-id={index}
ref={refs[index]}
{...rest}
/>
))}
</div>
);
}
Example #21
Source File: MenuBar.tsx From ble with Apache License 2.0 | 4 votes |
MenuBar: FunctionComponent = () => {
const { level, undoManager } = useStore();
const dispatch = useDispatch();
function on2StarsChange(value: number): void {
level.set2StarsTime(value * 1000);
}
function on3StarsChange(value: number): void {
level.set3StarsTime(value * 1000);
}
function onNameChange(ev: ChangeEvent<HTMLInputElement>): void {
level.setName(ev.target.value);
}
function onNameFocus(): void {
undoManager.startGroup();
}
function onNameBlur(ev: FocusEvent<HTMLInputElement>): void {
undoManager.stopGroup();
ev.target.reportValidity();
}
function onStarFocus(): void {
undoManager.startGroup();
}
function onStarBlur(): void {
undoManager.stopGroup();
}
function onUndo(): void {
dispatch({
type: 'undo',
});
}
function onRedo(): void {
dispatch({
type: 'redo',
});
}
return (
<Bar>
<Line>
<LevelNameInput
type="text"
value={level.name}
onChange={onNameChange}
onFocus={onNameFocus}
onBlur={onNameBlur}
placeholder="Level name"
required
/>
<Table>
<TableRow
title={`Finish in ${level.timings[0] / 1000}s or less to get 2 stars`}
>
<Text>★★</Text>
<StarInput
min={0}
step={0.001}
value={level.timings[0] / 1000}
onChange={on2StarsChange}
onFocus={onStarFocus}
onBlur={onStarBlur}
required
/>
<Text>secs</Text>
</TableRow>
<TableRow
title={`Finish in ${level.timings[1] / 1000}s or less to get 3 stars`}
>
<Text>★★★</Text>
<StarInput
min={0}
step={0.001}
value={level.timings[1] / 1000}
onChange={on3StarsChange}
onFocus={onStarFocus}
onBlur={onStarBlur}
required
/>
<Text>secs</Text>
</TableRow>
</Table>
</Line>
<Line>
<Button onClick={onUndo} disabled={!undoManager.canUndo}>
<FontAwesomeIcon icon={faUndo}/>
 
Undo
</Button>
<Button onClick={onRedo} disabled={!undoManager.canRedo}>
<FontAwesomeIcon icon={faRedo}/>
 
Redo
</Button>
<LoadSave/>
<HomeButton/>
</Line>
</Bar>
);
}
Example #22
Source File: search-items.tsx From geist-ui with MIT License | 4 votes |
SearchItems = React.forwardRef<
SearchItemsRef,
React.PropsWithChildren<SearchItemsProps>
>(
(
{ data, onSelect, preventHoverHighlightSync },
outRef: React.Ref<SearchItemsRef | null>,
) => {
const theme = useTheme()
const { rect, setRect } = useRect()
const ref = useRef<HTMLUListElement | null>(null)
const [displayHighlight, setDisplayHighlight] = useState<boolean>(false)
useImperativeHandle(outRef, () =>
Object.assign(ref.current, {
closeHighlight: () => setDisplayHighlight(false),
}),
)
const hoverHandler = (event: MouseEvent<HTMLButtonElement>) => {
if (preventHoverHighlightSync) return
if (!isSearchItem(event.target as HTMLButtonElement)) return
;(event.target as HTMLButtonElement).focus()
}
const focusHandler = (event: FocusEvent<HTMLButtonElement>) => {
if (!isSearchItem(event.target as HTMLButtonElement)) return
setRect(event, () => ref.current)
setDisplayHighlight(true)
}
const blurHandler = () => {
setDisplayHighlight(false)
}
const grouppedResults = useMemo(() => groupResults(data), [data])
return (
<ul className="results" role="listbox" ref={ref}>
<Highlight
className="results-hover"
rect={rect}
visible={displayHighlight}
activeOpacity={0.5}
/>
{grouppedResults.map((group) => (
<li role="presentation" key={group.title}>
<div className="group-title">{group.title}</div>
<ul role="group">
{group.items.map(item => (
<SearchItem
onSelect={onSelect}
onMouseOver={hoverHandler}
onFocus={focusHandler}
onBlur={blurHandler}
data={item}
key={item.url}
/>
))}
</ul>
</li>
))}
<style jsx>{`
.results {
width: 100%;
max-height: 300px;
overflow-y: auto;
position: relative;
scroll-behavior: smooth;
margin-bottom: 0.5rem;
}
.results :global(li:before) {
content: none;
}
.group-title {
color: ${theme.palette.accents_5};
font-size: 0.75rem;
text-align: start;
margin: 0.25rem 0;
}
.results:global(div.highlight.results-hover) {
border-radius: 8px;
}
`}</style>
</ul>
)
},
)
Example #23
Source File: ValidatorReport.tsx From community-repo with GNU General Public License v3.0 | 4 votes |
ValidatorReport = () => {
const dateFormat = 'yyyy-MM-DD';
const [backendUrl, setBackendUrl] = useState(process.env.REACT_APP_BACKEND_URL || "http://localhost:3500");
const [activeValidators, setActiveValidators] = useState([]);
const [lastBlock, setLastBlock] = useState(0);
const [stash, setStash] = useState('5EhDdcWm4TdqKp1ew1PqtSpoAELmjbZZLm5E34aFoVYkXdRW');
const [dateFrom, setDateFrom] = useState(moment().subtract(14, 'd').format(dateFormat));
const [dateTo, setDateTo] = useState(moment().format(dateFormat));
const [startBlock, setStartBlock] = useState('' as unknown as number);
const [endBlock, setEndBlock] = useState('' as unknown as number);
const [isLoading, setIsLoading] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const [error, setError] = useState(undefined);
const [currentPage, setCurrentPage] = useState(1);
const [filterTab, setFilterTab] = useState(0 as number);
const [columns] = useState(
[
{ field: 'id', headerName: 'Era', width: 150, sortable: true },
{ field: 'stakeTotal', headerName: 'Total Stake', width: 150, sortable: true },
{ field: 'stakeOwn', headerName: 'Own Stake', width: 150, sortable: true },
{ field: 'points', headerName: 'Points', width: 150, sortable: true },
{ field: 'rewards', headerName: 'Rewards', width: 150, sortable: true },
{ field: 'commission', headerName: 'Commission', width: 150, sortable: true, valueFormatter: (params: ValueFormatterParams) => {
if (isNaN(params.value as unknown as number)) {
return `${params.value}%`
}
return `${Number(params.value).toFixed(0)}%`
}},
{ field: 'blocksCount', headerName: 'Blocks Produced', width: 150, sortable: true },
]
);
const [report, setReport] = useState({
pageSize: 0,
totalCount: 0,
totalBlocks: 0,
startEra: -1,
endEra: -1,
startBlock: -1,
endBlock: -1,
startTime: -1,
endTime: -1,
report: [] as unknown as Report[]
} as unknown as Reports );
const isDateRange = filterTab === 0;
const isBlockRange = filterTab === 1;
useEffect(() => {
updateChainState()
const interval = setInterval(() => { updateChainState() }, 10000);
return () => clearInterval(interval);
}, []);
const updateChainState = () => {
getChainState().then((chainState) => {
setLastBlock(chainState.finalizedBlockHeight)
setActiveValidators(chainState.validators.validators)
})
}
const handlePageChange = (params: PageChangeParams) => {
if (report.totalCount > 0) {
loadReport(params.page)
}
}
const loadReport = (page: number) => {
setCurrentPage(page)
setIsLoading(true)
const blockParam = isBlockRange && startBlock && endBlock ? `&start_block=${startBlock}&end_block=${endBlock}` : ''
const dateParam = isDateRange && dateFrom && dateTo ? `&start_time=${moment(dateFrom, dateFormat).format(dateFormat)}&end_time=${moment(dateTo, dateFormat).format(dateFormat)}` : ''
const apiUrl = `${backendUrl}/validator-report?addr=${stash}&page=${page}${blockParam}${dateParam}`
axios.get(apiUrl).then((response) => {
if (response.data.report !== undefined) {
setReport(response.data);
}
setIsLoading(false)
setError(undefined)
}).catch((err) => {
setIsLoading(false)
setError(err)
})
}
const stopLoadingReport = () => {
setIsLoading(false)
}
const canLoadReport = () => stash && ((isBlockRange && startBlock && endBlock) || (isDateRange && dateFrom && dateTo))
const startOrStopLoading = () => isLoading ? stopLoadingReport() : loadReport(1)
const updateStartBlock = (e: { target: { value: unknown; }; }) => setStartBlock((e.target.value as unknown as number));
const updateEndBlock = (e: { target: { value: unknown; }; }) => setEndBlock((e.target.value as unknown as number));
const updateDateFrom = (e: { target: { value: unknown; }; }) => setDateFrom((e.target.value as unknown as string))
const updateDateTo = (e: { target: { value: unknown; }; }) => setDateTo((e.target.value as unknown as string));
const setCurrentPeriodStartBlock = () => {
const blocksToEndOfDay = moment().endOf('d').diff(moment(), "seconds") / 6
const twoWeeksBlocks = (600 * 24 * 14);
return setStartBlock(lastBlock - twoWeeksBlocks - Number(blocksToEndOfDay.toFixed(0)))
}
const setCurrentPeriodEndBlock = () => setEndBlock(lastBlock)
const getButtonTitle = (isLoading: boolean) => {
if (isLoading) {
return (<div style={{ display: 'flex', alignItems: 'center' }}>Stop loading <CircularProgress style={ { color: '#fff', height: 20, width: 20, marginLeft: 12 } } /></div>)
}
if (isBlockRange) {
return startBlock && endBlock ? `Load data between blocks ${startBlock} - ${endBlock}` : 'Load data between blocks'
}
if (isDateRange) {
return dateFrom && dateTo ? `Load data between dates ${dateFrom} - ${dateTo}` : 'Load data between dates'
}
return 'Choose dates or blocks range'
}
const updateStash = (event: ChangeEvent<{}>, value: string | null, reason: AutocompleteChangeReason, details?: AutocompleteChangeDetails<string> | undefined) => {
setStash(value || '')
}
const updateStashOnBlur = (event: FocusEvent<HTMLDivElement> & { target: HTMLInputElement}) => {
setStash((prev) => prev !== event.target.value ? event.target.value : prev)
}
const classes = useStyles();
return (
<div className={classes.root}>
<Container maxWidth="lg">
<Grid container spacing={2}>
<Grid item lg={12}>
<div style={{ display: 'flex', justifyContent: 'flex-start' }}>
<h1>Validator Report</h1>
<Button style={{ display: 'none', fontSize: 12, alignSelf: 'center'}} onClick={() => setIsModalOpen(true)}><Edit style={{ fontSize: 12, alignSelf: 'center'}} /> Backend</Button>
</div>
<Dialog style={{ minWidth: 275 }} onClose={() => setIsModalOpen(false)} aria-labelledby="simple-dialog-title" open={isModalOpen}>
<DialogTitle id="simple-dialog-title">Change Backend URL</DialogTitle>
<FormControl style={ { margin: 12 }}>
<Select
labelId="backend-url-label"
id="backend-url"
value={backendUrl}
onChange={(e) => setBackendUrl(e.target.value as unknown as string)}
>
<MenuItem value={'https://validators.joystreamstats.live'}>validators.joystreamstats.live</MenuItem>
<MenuItem value={'https://joystream-api.herokuapp.com'}>joystream-api.herokuapp.com</MenuItem>
<MenuItem value={'http://localhost:3500'}>localhost:3500</MenuItem>
</Select>
</FormControl>
</Dialog>
</Grid>
<Grid item xs={12} lg={12}>
<Autocomplete
freeSolo
style={{ width: '100%' }}
options={activeValidators}
onChange={updateStash}
onBlur={updateStashOnBlur}
value={stash}
renderInput={(params) => <TextField {...params} label="Validator stash address" variant="filled" />} />
</Grid>
<Grid item xs={12} lg={12}>
<Tabs indicatorColor='primary' value={filterTab} onChange={(e: unknown, newValue: number) => setFilterTab(newValue)} aria-label="simple tabs example">
<Tab label="Search by date" />
<Tab label="Search by blocks" />
</Tabs>
</Grid>
<Grid hidden={!isDateRange} item xs={6} lg={3}>
<TextField fullWidth type="date" onChange={updateDateFrom} id="block-start" InputLabelProps={{ shrink: true }} label="Date From" value={dateFrom} variant="filled" />
</Grid>
<Grid hidden={!isDateRange} item xs={6} lg={3}>
<BootstrapButton size='large' style={{ height: 56 }} fullWidth onClick={() => setDateFrom(moment().subtract(2, 'w').format('yyyy-MM-DD'))}>2 weeks from today</BootstrapButton>
</Grid>
<Grid hidden={!isDateRange} item xs={6} lg={3}>
<TextField fullWidth type="date" onChange={updateDateTo} id="block-end" InputLabelProps={{ shrink: true }} label="Date To" value={dateTo} variant="filled" />
</Grid>
<Grid hidden={!isDateRange} item xs={6} lg={3}>
<BootstrapButton size='large' style={{ height: 56 }} fullWidth onClick={() => setDateTo(moment().format('yyyy-MM-DD'))}>Today</BootstrapButton>
</Grid>
<Grid hidden={!isBlockRange} item xs={6} lg={3}>
<TextField fullWidth type="number" onChange={updateStartBlock} id="block-start" label="Start Block" value={startBlock} variant="filled" />
</Grid>
<Grid hidden={!isBlockRange} item xs={6} lg={3}>
<BootstrapButton size='large' style={{ height: 56 }} fullWidth disabled={!lastBlock} onClick={setCurrentPeriodStartBlock}>{lastBlock ? `2 weeks before latest (${lastBlock - (600 * 24 * 14)})` : '2 weeks from latest'}</BootstrapButton>
</Grid>
<Grid hidden={!isBlockRange} item xs={6} lg={3}>
<TextField fullWidth type="number" onChange={updateEndBlock} id="block-end" label="End Block" value={endBlock} variant="filled" />
</Grid>
<Grid hidden={!isBlockRange} item xs={6} lg={3}>
<BootstrapButton size='large' style={{ height: 56 }} fullWidth disabled={!lastBlock} onClick={setCurrentPeriodEndBlock}>{lastBlock ? `Pick latest block (${lastBlock})` : 'Use latest block'}</BootstrapButton>
</Grid>
<Grid item xs={12} lg={12}>
<BootstrapButton size='large' style={{ height: 56 }} fullWidth disabled={!canLoadReport()} onClick={startOrStopLoading}>{getButtonTitle(isLoading)}</BootstrapButton>
<Alert style={ error !== undefined ? { marginTop: 12 } : { display: 'none'} } onClose={() => setError(undefined)} severity="error">Error loading validator report, please try again.</Alert>
</Grid>
<Grid item xs={12} lg={12}>
<ValidatorReportCard stash={stash} report={report} />
</Grid>
<Grid item xs={12} lg={12}>
<div style={{ height: 400 }}>
<Backdrop className={classes.backdrop} open={isLoading}>
<CircularProgress color="inherit" />
</Backdrop>
<DataGrid
rows={report.report}
columns={columns as unknown as ColDef[]}
rowCount={report.totalCount}
pagination
paginationMode="server"
onPageChange={handlePageChange}
pageSize={report.pageSize}
rowsPerPageOptions={[]}
disableSelectionOnClick
page={currentPage}
/>
</div>
</Grid>
</Grid>
</Container>
</div>
)
}
Example #24
Source File: user-row.tsx From keycaplendar with MIT License | 4 votes |
UserRow = ({
delete: deleteFn,
getUsers,
user: propsUser,
}: UserRowProps) => {
const currentUser = useAppSelector(selectUser);
const allDesigners = useAppSelector(selectAllDesigners);
const [user, updateUser] = useImmer<UserType>(propsUser);
const [edited, setEdited] = useState(false);
const [loading, setLoading] = useState(false);
const [focused, setFocused] = useState("");
const keyedUpdate =
<T extends UserType, K extends keyof T>(key: K, payload: T[K]) =>
(draft: T) => {
draft[key] = payload;
};
useEffect(() => {
if (propsUser !== user) {
updateUser(user);
setEdited(false);
}
}, [propsUser]);
const handleFocus = (e: FocusEvent<HTMLInputElement>) =>
setFocused(e.target.name);
const handleBlur = () => setFocused("");
const handleCheckboxChange = ({
target: { checked, name },
}: ChangeEvent<HTMLInputElement>) => {
if (arrayIncludes(roles, name)) {
updateUser(keyedUpdate(name, checked));
setEdited(true);
}
};
const handleChange = ({
target: { name, value },
}: ChangeEvent<HTMLInputElement>) => {
if (hasKey(user, name)) {
updateUser(keyedUpdate(name, value));
setEdited(true);
}
};
const selectValue = <Key extends keyof UserType>(
prop: Key,
value: UserType[Key]
) => {
updateUser(keyedUpdate(prop, value));
setEdited(true);
};
const setRoles = () => {
setLoading(true);
const setRolesFn = firebase.functions().httpsCallable("setRoles");
setRolesFn({
admin: user.admin,
designer: user.designer,
editor: user.editor,
email: user.email,
nickname: user.nickname,
}).then((result) => {
setLoading(false);
if (
result.data.designer === user.designer &&
result.data.editor === user.editor &&
result.data.admin === user.admin
) {
queue.notify({ title: "Successfully edited user permissions." });
getUsers();
} else if (result.data.error) {
queue.notify({
title: `Failed to edit user permissions: ${result.data.error}`,
});
} else {
queue.notify({ title: "Failed to edit user permissions." });
}
});
};
const saveButton = loading ? (
<CircularProgress />
) : (
<IconButton
disabled={!edited}
icon={iconObject(<Save />)}
onClick={() => {
if (edited) {
setRoles();
}
}}
/>
);
const deleteButton =
user.email === currentUser.email ||
user.email === "[email protected]" ? null : (
<IconButton
icon={iconObject(<Delete />)}
onClick={() => deleteFn(user)}
/>
);
return (
<DataTableRow>
<DataTableCell>
<Avatar name={user.displayName} size="large" src={user.photoURL} />
</DataTableCell>
<DataTableCell>{user.displayName}</DataTableCell>
<DataTableCell>{truncate(user.email, 20)}</DataTableCell>
<DataTableCell>
{DateTime.fromISO(user.dateCreated).toFormat(
`HH:mm d'${ordinal(DateTime.fromISO(user.dateCreated).day)}' MMM yyyy`
)}
</DataTableCell>
<DataTableCell>
{DateTime.fromISO(user.lastSignIn).toFormat(
`HH:mm d'${ordinal(DateTime.fromISO(user.lastSignIn).day)}' MMM yyyy`
)}
</DataTableCell>
<DataTableCell>
{DateTime.fromISO(user.lastActive).toFormat(
`HH:mm d'${ordinal(DateTime.fromISO(user.lastActive).day)}' MMM yyyy`
)}
</DataTableCell>
<DataTableCell>
<MenuSurfaceAnchor>
<TextField
className="nickname"
name="nickname"
onBlur={handleBlur}
onChange={handleChange}
onFocus={handleFocus}
outlined
value={user.nickname}
/>
<Autocomplete
array={allDesigners}
minChars={2}
open={focused === "nickname"}
prop="nickname"
query={user.nickname}
select={(prop, item) =>
hasKey(user, prop) && selectValue(prop, item)
}
/>
</MenuSurfaceAnchor>
</DataTableCell>
<DataTableCell hasFormControl>
<Checkbox
checked={user.designer}
name="designer"
onChange={handleCheckboxChange}
/>
</DataTableCell>
<DataTableCell hasFormControl>
<Checkbox
checked={user.editor}
disabled={
user.email === currentUser.email ||
user.email === "[email protected]"
}
name="editor"
onChange={handleCheckboxChange}
/>
</DataTableCell>
<DataTableCell hasFormControl>
<Checkbox
checked={user.admin}
disabled={
user.email === currentUser.email ||
user.email === "[email protected]"
}
name="admin"
onChange={handleCheckboxChange}
/>
</DataTableCell>
<DataTableCell hasFormControl>{saveButton}</DataTableCell>
<DataTableCell hasFormControl>{deleteButton}</DataTableCell>
</DataTableRow>
);
}
Example #25
Source File: user-card.tsx From keycaplendar with MIT License | 4 votes |
UserCard = ({
delete: deleteFn,
getUsers,
user: propsUser,
}: UserCardProps) => {
const device = useAppSelector(selectDevice);
const currentUser = useAppSelector(selectUser);
const allDesigners = useAppSelector(selectAllDesigners);
const [user, updateUser] = useImmer<UserType>(propsUser);
const [edited, setEdited] = useState(false);
const [loading, setLoading] = useState(false);
const [focused, setFocused] = useState("");
const keyedUpdate =
<T extends UserType, K extends keyof T>(key: K, payload: T[K]) =>
(draft: T) => {
draft[key] = payload;
};
useEffect(() => {
if (propsUser !== user) {
updateUser(user);
setEdited(false);
}
}, [propsUser]);
const handleFocus = (e: FocusEvent<HTMLInputElement>) =>
setFocused(e.target.name);
const handleBlur = () => setFocused("");
const handleChange = ({
target: { name, value },
}: ChangeEvent<HTMLInputElement>) => {
if (hasKey(user, name)) {
updateUser(keyedUpdate(name, value));
setEdited(true);
}
};
const selectValue = <Key extends keyof UserType>(
prop: Key,
value: UserType[Key]
) => {
updateUser(keyedUpdate(prop, value));
setEdited(true);
};
const toggleRole = (role: typeof roles[number]) => {
if (roles.includes(role)) {
updateUser((user) => {
user[role] = !user[role];
});
setEdited(true);
}
};
const setRoles = () => {
setLoading(true);
const setRolesFn = firebase.functions().httpsCallable("setRoles");
setRolesFn({
admin: user.admin,
designer: user.designer,
editor: user.editor,
email: user.email,
nickname: user.nickname,
}).then((result) => {
setLoading(false);
if (
result.data.designer === user.designer &&
result.data.editor === user.editor &&
result.data.admin === user.admin
) {
queue.notify({ title: "Successfully edited user permissions." });
getUsers();
} else if (result.data.error) {
queue.notify({
title: `Failed to edit user permissions: ${result.data.error}`,
});
} else {
queue.notify({ title: "Failed to edit user permissions." });
}
});
};
const saveButton = loading ? (
<CircularProgress />
) : (
<CardActionIcon
disabled={!edited}
icon={iconObject(<Save />)}
onClick={() => {
if (edited) {
setRoles();
}
}}
/>
);
const deleteButton =
user.email === currentUser.email ||
user.email === "[email protected]" ? null : (
<IconButton
icon={iconObject(<Delete />)}
onClick={() => deleteFn(user)}
/>
);
return (
<Card className="user">
<List nonInteractive>
<ListItem className="three-line" ripple={false}>
<Avatar
className="mdc-list-item__graphic"
size="xlarge"
src={user.photoURL}
/>
<ListItemText>
<div className="overline">{user.nickname}</div>
<ListItemPrimaryText>{user.displayName}</ListItemPrimaryText>
<ListItemSecondaryText>{user.email}</ListItemSecondaryText>
</ListItemText>
<ListItemMeta>{deleteButton}</ListItemMeta>
</ListItem>
</List>
<CollapsibleList
handle={<SimpleListItem metaIcon="expand_more" text="Account dates" />}
>
<List nonInteractive twoLine>
<ListItem ripple={false}>
<ListItemText>
<ListItemPrimaryText>Date created</ListItemPrimaryText>
<ListItemSecondaryText>
{DateTime.fromISO(user.dateCreated).toFormat(
`HH:mm d'${ordinal(
DateTime.fromISO(user.dateCreated).day
)}' MMM yyyy`
)}
</ListItemSecondaryText>
</ListItemText>
</ListItem>
<ListItem ripple={false}>
<ListItemText>
<ListItemPrimaryText>Last signed in</ListItemPrimaryText>
<ListItemSecondaryText>
{DateTime.fromISO(user.lastSignIn).toFormat(
`HH:mm d'${ordinal(
DateTime.fromISO(user.lastSignIn).day
)}' MMM yyyy`
)}
</ListItemSecondaryText>
</ListItemText>
</ListItem>
<ListItem ripple={false}>
<ListItemText>
<ListItemPrimaryText>Last active</ListItemPrimaryText>
<ListItemSecondaryText>
{DateTime.fromISO(user.lastActive).toFormat(
`HH:mm d'${ordinal(
DateTime.fromISO(user.lastActive).day
)}' MMM yyyy`
)}
</ListItemSecondaryText>
</ListItemText>
</ListItem>
</List>
</CollapsibleList>
<div className="text-field-container">
<MenuSurfaceAnchor>
<TextField
className="nickname"
label="Nickname"
name="nickname"
onBlur={handleBlur}
onChange={handleChange}
onFocus={handleFocus}
outlined
value={user.nickname}
/>
<Autocomplete
array={allDesigners}
minChars={2}
open={focused === "nickname"}
prop="nickname"
query={user.nickname}
select={(prop, item) =>
hasKey(user, prop) && selectValue(prop, item)
}
/>
</MenuSurfaceAnchor>
</div>
<CardActions>
<CardActionButtons>
<SegmentedButton toggle>
{roles.map((role) => (
<SegmentedButtonSegment
key={role}
disabled={
(user.email === currentUser.email ||
user.email === "[email protected]") &&
role !== "designer"
}
icon={
device === "desktop" && hasKey(userRoleIcons, role)
? userRoleIcons[role]
: null
}
label={role}
onClick={() => {
toggleRole(role);
}}
selected={hasKey(user, role) && !!user[role]}
/>
))}
</SegmentedButton>
</CardActionButtons>
<CardActionIcons>{saveButton}</CardActionIcons>
</CardActions>
</Card>
);
}