Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finish building Headless Input and a demo of Headful Input #23

Merged
merged 15 commits into from
Jun 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 51 additions & 10 deletions packages/date-picker/date-picker-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,30 @@ import { styled } from '@stitches/react';
import { parseDate } from './processor.js';
import { dateOptions } from '../support/date.js';

const AmsDatePickerInputContainer = styled('input', {});
const AmsDatePickerInputContainer = styled('input', {
outline: 'none',
border: 'none',
backgroundColor: 'transparent',
});

/**
* [Ams] The headless date picker component.
* @param {string} className
* @param {string} id
* @param {object} style
* @param {any} value
* @param {any} baseDate
* @param {function} onChange - Callback function to be called when the date is changed (only when finalized).
* @param {function} onError - Callback function when the error is occurring in user's input (neither functionality error nor development error).
* @param {function} onKeyPress
* @param {function} onFocus
* @param {function} onBlur
* @param {object} dateOption - (TBD) The date option for formatting the date.
* @param {function} onShouldOpenSelector
* @param {function} onShouldCloseSelector
* @param {any} props
* @return {JSX.Element}
*/
export const AmsDatePickerInput = ({
className,
id,
Expand All @@ -16,13 +38,19 @@ export const AmsDatePickerInput = ({
onKeyPress,
onFocus,
onBlur,
dateOption = dateOptions,
onShouldOpenSelector,
onShouldCloseSelector,
...props
}) => {
const [inputValue, setInputValue] = useState(value);
const [inputValue, setInputValue] = useState('');

useEffect(() => {
setInputValue(value.toLocaleString('en-US', dateOptions));
if (value) {
setInputValue(value.toLocaleString('en-US', dateOption));
} else {
setInputValue('');
}
}, [value]);

// This function is a callback when the input is finished by user (on finalizing or on blurring).
Expand All @@ -34,36 +62,45 @@ export const AmsDatePickerInput = ({
onChange(parsedDate);
}
} catch (e) {
onError(e); // Return error.
if (onError) {
onError(e); // Return error.
} else {
console.error('AmsDatePicker:', e); // Log error.
}
}
};

// This function is used to handle the close action of the date selector.
const handleCloseDateSelector = () => {
// TODO.
// This function is used to handle the close action of the date selector or the blur action of input.
const handleEscape = () => {
// TODO: Blur the input when needed.
// TODO: Call back the onShouldCloseSelector callback with some conditions.
};

// This function should be called to determine if we should finish the input on blur.
const isValidOnBlur = () => {
return (
inputValue.length > 0
&& !inputValue.match(/^\d{1,2}\/\d{1,2}\/\d{4}, \d{1,2}:\d{2}(?::\d{2})? (?:AM|PM)?$/)
&& inputValue.match(/^\d{1,2}\/\d{1,2}\/\d{4},? \d{1,2}:\d{2}(?::\d{2})? ?(?:AM|PM)?$/)
);
};

// TODO: Make the input element style-less.
// This input element is style-less.
return (
<AmsDatePickerInputContainer
className={`ams-date-picker-input ${className ?? ''}`}
id={id ?? 'ams-date-picker-input'}
css={style}
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value);
}}
onKeyPress={(e) => {
if (e.key === 'Enter') {
onInputFinish(inputValue);
}
if (e.key === 'Escape') {
handleEscape();
}
if (onKeyPress) {
onKeyPress(e);
}
Expand All @@ -75,14 +112,18 @@ export const AmsDatePickerInput = ({
}
}}
onBlur={(e) => {
// TODO: Determine if we should close the data selector.
// Determine if we should close the data selector.
if (isValidOnBlur()) {
onInputFinish(inputValue);
}
if (onBlur) {
onBlur(e);
}
}}
{
...props
// TODO: Handle potential conflicts with props.
}
/>
);
};
94 changes: 56 additions & 38 deletions packages/date-picker/date-picker.js
Original file line number Diff line number Diff line change
@@ -1,83 +1,101 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { AmsDatePickerInput } from './date-picker-input';
import { Layout } from './layout-support.js';
import { AmsDesign } from '../support/standards.js';

/**
* [Ams] (WIP) Magic Date Picker Component.
*
* @param {string|undefined} className
* @param {string|undefined} id
* @param {string} label
* @param {Date|undefined} value
* @param {function} onChange
* @param {string|null} hint
* @param {string|null} error
* @param {Date|undefined} baseDate
* @param {object} layoutStyle
* @param {object} style
* @param {any} props
* @return {JSX.Element}
*/
export const AmsDatePicker = ({
className,
id = 'ams-date-picker',
label,
value,
onChange,
hint,
error,
baseDate,
layoutStyle,
style,
...props
}) => {
// Date picker value state.
const [valueState, setValueState] = useState(null);
const [valueState, setValueState] = useState(value);

// Date picker open state.
const [isOpen, setIsOpen] = useState(false);

// Date picker hint state.
const [hintState, setHintState] = useState(null);

// Date picker input field error state.
const [errorState, setErrorState] = useState(null);

// Date picker anchor element.
const boxRef = useRef(null);

const handlePopoverClose = () => {
setIsOpen(false);
};

// Update hint state when hint prop changes.
useEffect(() => {
if (hint) {
setHintState(hint);
}
}, [hint]);

// Update error state when error prop changes.
useEffect(() => {
if (error) {
setErrorState(error);
}
}, [error]);

// Process the datepicker value into input value.
useEffect(() => {
if (valueState) {
setHintState(null);
setErrorState(null);
if (onChange) {
// Call the onChange callback with Date object.
// It will always be called nevertheless it is inputted by typing or selecting.
onChange(valueState);
} else {
console.warn('ams:', 'onChange callback is not defined.');
console.log('ams:', 'valueState:', valueState);
}
}
}, [valueState]);

return (
<div
style={{
<Layout
id={id}
css={{
width: '100%',
height: '42px',
display: 'flex',
flexDirection: 'column',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
borderRadius: '$md',
border: '0.5px solid $gray5',
backgroundColor: '$gray1',
transition: AmsDesign.transition.cubic,
'&:hover': {
backgroundColor: '$white',
border: '0.5px solid $gray7',
},
'&:focus-within': {
backgroundColor: '$white',
boxShadow: '$md',
},
overflow: 'hidden',
...layoutStyle,
}}
>
{/* TODO */}
</div>
{/* TODO: Replenish necessary APIs */}
<AmsDatePickerInput
className={className}
id={`${id}-input`}
value={valueState}
baseDate={baseDate}
onChange={(value) => {
setValueState(value);
}}
style={{
width: '100%',
height: '100%',
padding: '10px 14px',
fontSize: '$lg',
fontWeight: '400',
...style,
}}
{...props}
/>
</Layout>
);
};
4 changes: 2 additions & 2 deletions packages/support/date.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export const dateOptions = {
year: 'numeric',
month: 'numeric',
day: 'numeric',
month: '2-digit',
day: '2-digit',
weekday: undefined,
hour: 'numeric',
minute: 'numeric',
Expand Down
8 changes: 4 additions & 4 deletions packages/support/stitches.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ export const {
black: '#000',
},
shadows: {
'sm': '0 5px 6px 0 rgba(0, 0, 0, 0.03)',
'md': '0 6px 8px 0 rgba(0, 0, 0, 0.03)',
'lg': '0px 10px 16px rgba(0, 0, 0, 0.03)',
'sm': '0 4px 10px 0 rgba(0, 0, 0, 0.03)',
'md': '0 6px 14px 0 rgba(0, 0, 0, 0.03)',
'lg': '0px 10px 20px rgba(0, 0, 0, 0.03)',
},
fontSizes: {
'xxs': '11px',
Expand Down Expand Up @@ -68,7 +68,7 @@ export const {
},
});

export const amsDarkTheme = createTheme('ams-dark-theme', {
export const amsDarkTheme = createTheme('dark', {
colors: {
...grayDark,
...blueDark,
Expand Down
6 changes: 4 additions & 2 deletions packages/user-manual/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { styled } from '@stitches/react';
import * as Dialog from '@radix-ui/react-dialog';
import { IconQuestionMark } from '@tabler/icons';
import { IconHelp, IconQuestionMark } from '@tabler/icons';

import { AmsDesign } from '../support/standards.js';

Expand Down Expand Up @@ -71,7 +71,9 @@ export const AmsUserManual = ({ style, children }) => {
css={style}
>
{children ?? (
<IconQuestionMark size={24} />
<IconHelp size={17} strokeWidth={2.1} style={{
marginBottom: -2,
}}/>
)}
</UMTrigger>
<Dialog.Portal>
Expand Down
Loading