preact#Fragment TypeScript Examples
The following examples show how to use
preact#Fragment.
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: ConsentCheckboxLabel.tsx From adyen-web with MIT License | 6 votes |
export default function ConsentCheckboxLabel(props: ConsentCheckboxLabelProps) {
const { i18n } = useCoreContext();
const linkText = i18n.get('paymentConditions');
const translationString = i18n.get('afterPay.agreement');
const [textBeforeLink, textAfterLink] = translationString.split('%@');
if (textBeforeLink && textAfterLink) {
return (
<Fragment>
{textBeforeLink}
<a className="adyen-checkout__link" target="_blank" rel="noopener noreferrer" href={props.url}>
{linkText}
</a>
{textAfterLink}
</Fragment>
);
}
return <span className="adyen-checkout__checkbox__label">{i18n.get('privacyPolicy')}</span>;
}
Example #2
Source File: text.component.tsx From passwords-fountain with MIT License | 6 votes |
Text: TypedComponent<Props> = ({
children,
withMarkup,
}: Props): VNode<string> => {
useContext(LocalisationContext);
return withMarkup ? (
<span
className="sanitized-translation"
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(i18n._(children), {
ALLOWED_ATTR: ['rel', 'href', 'target'],
}),
}}
/>
) : (
<Fragment>{i18n._(children)}</Fragment>
);
}
Example #3
Source File: prompt.spec.tsx From passwords-fountain with MIT License | 6 votes |
describe('Prompt', () => {
it('should render correctly', () => {
const { asFragment } = render(
<Prompt
renderContent={(): string =>
'Are you sure you want to delete this file?'
}
renderControls={(): VNode => (
<Fragment>
<Button
onClick={(): void => {
// empty
}}
>
Cancel
</Button>
<Button
onClick={(): void => {
// empty
}}
>
Ok
</Button>
</Fragment>
)}
/>
);
expect(asFragment()).toMatchSnapshot();
});
});
Example #4
Source File: tr.tsx From gridjs with MIT License | 6 votes |
private getChildren(): ComponentChildren {
if (this.props.children) {
return this.props.children;
} else {
return (
<Fragment>
{this.props.row.cells.map((cell: Cell, i) => {
const column = this.getColumn(i);
if (column && column.hidden) return null;
return (
<TD
key={cell.id}
cell={cell}
row={this.props.row}
column={column}
/>
);
})}
</Fragment>
);
}
}
Example #5
Source File: pagination.tsx From gridjs with MIT License | 6 votes |
renderSummary() {
return (
<Fragment>
{this.props.summary && this.state.total > 0 && (
<div
role="status"
aria-live="polite"
className={classJoin(
className('summary'),
this.config.className.paginationSummary,
)}
title={this._(
'pagination.navigate',
this.state.page + 1,
this.pages,
)}
>
{this._('pagination.showing')}{' '}
<b>{this._(`${this.state.page * this.state.limit + 1}`)}</b>{' '}
{this._('pagination.to')}{' '}
<b>
{this._(
`${Math.min(
(this.state.page + 1) * this.state.limit,
this.state.total,
)}`,
)}
</b>{' '}
{this._('pagination.of')} <b>{this._(`${this.state.total}`)}</b>{' '}
{this._('pagination.results')}
</div>
)}
</Fragment>
);
}
Example #6
Source File: prompt.story.tsx From passwords-fountain with MIT License | 6 votes |
defaultView = (): VNode => (
<Prompt
renderContent={(): string =>
'Are you sure you want to delete this file?'
}
renderControls={(): VNode => (
<Fragment>
<Button
onClick={(): void => {
// empty
}}
>
Cancel
</Button>
<Button
onClick={(): void => {
// empty
}}
>
Ok
</Button>
</Fragment>
)}
/>
)
Example #7
Source File: plugin.ts From gridjs with MIT License | 6 votes |
render() {
if (this.props.pluginId) {
// render a single plugin
const plugin = this.config.plugin.get(this.props.pluginId);
if (!plugin) return null;
return h(
Fragment,
{},
h(plugin.component, {
plugin: plugin,
...plugin.props,
...this.props.props,
}),
);
} else if (this.props.position !== undefined) {
// render using a specific plugin position
return h(
Fragment,
{},
this.config.plugin
.list(this.props.position)
.map((p) =>
h(p.component, { plugin: p, ...p.props, ...this.props.props }),
),
);
}
return null;
}
Example #8
Source File: task-view.tsx From obsidian-dataview with MIT License | 6 votes |
/** JSX component which recursively renders grouped tasks. */
function TaskGrouping({ items, sourcePath }: { items: Grouping<SListItem>; sourcePath: string }) {
const isGrouping = items.length > 0 && Groupings.isGrouping(items);
return (
<Fragment>
{isGrouping &&
items.map(item => (
<Fragment>
<h4>
<Lit value={item.key} sourcePath={sourcePath} />
<span class="dataview small-text"> ({Groupings.count(item.rows)})</span>
</h4>
<div class="dataview result-group">
<TaskGrouping items={item.rows} sourcePath={sourcePath} />
</div>
</Fragment>
))}
{!isGrouping && <TaskList items={items as SListItem[]} />}
</Fragment>
);
}
Example #9
Source File: RedirectButton.tsx From adyen-web with MIT License | 6 votes |
function RedirectButton({ payButton, onSubmit, amount = null, name, ...props }) {
const { i18n } = useCoreContext();
const [status, setStatus] = useState('ready');
this.setStatus = newStatus => {
setStatus(newStatus);
};
const payButtonLabel = () => {
const isZeroAuth = amount && {}.hasOwnProperty.call(amount, 'value') && amount.value === 0;
if (isZeroAuth) return `${i18n.get('preauthorizeWith')} ${name}`;
return `${i18n.get('continueTo')} ${name}`;
};
return (
<Fragment>
{payButton({
...props,
status,
classNameModifiers: ['standalone'],
label: payButtonLabel(),
onClick: onSubmit
})}
</Fragment>
);
}
Example #10
Source File: ReadOnlyPersonalDetails.tsx From adyen-web with MIT License | 6 votes |
ReadOnlyPersonalDetails = ({ data }) => {
const { firstName, lastName, shopperEmail, telephoneNumber }: ReadOnlyPersonalDetailsProps = data;
return (
<Fieldset classNameModifiers={['personalDetails']} label="personalDetails" readonly>
{firstName && `${firstName} `}
{lastName && `${lastName} `}
{shopperEmail && (
<Fragment>
<br />
{shopperEmail}
</Fragment>
)}
{telephoneNumber && (
<Fragment>
<br />
{telephoneNumber}
</Fragment>
)}
</Fieldset>
);
}
Example #11
Source File: InstantPaymentMethods.tsx From adyen-web with MIT License | 6 votes |
function InstantPaymentMethods({ paymentMethods }: InstantPaymentMethodsProps) {
const { i18n } = useCoreContext();
return (
<Fragment>
<ul className="adyen-checkout__instant-payment-methods-list">
{paymentMethods.map(pm => (
<li key={pm.type}>{pm.render()}</li>
))}
</ul>
<ContentSeparator label={i18n.get('orPayWith')} />
</Fragment>
);
}
Example #12
Source File: list-view.tsx From obsidian-dataview with MIT License | 5 votes |
/** Pure view over list elements. */
export function ListView({ query, sourcePath }: { query: Query; sourcePath: string }) {
let context = useContext(DataviewContext);
let items = useIndexBackedState<ListViewState>(
context.container,
context.app,
context.settings,
context.index,
{ state: "loading" },
async () => {
let result = await asyncTryOrPropogate(() =>
executeList(query, context.index, sourcePath, context.settings)
);
if (!result.successful) return { state: "error", error: result.error, sourcePath };
let showId = (query.header as ListQuery).showId;
let showValue = !!(query.header as ListQuery).format;
let mode = showId && showValue ? "both" : showId ? "id" : "value";
return { state: "ready", items: result.value.data, mode: mode as ListMode };
}
);
if (items.state == "loading")
return (
<Fragment>
<ErrorPre>Loading...</ErrorPre>
</Fragment>
);
else if (items.state == "error")
return (
<Fragment>
{" "}
<ErrorPre>Dataview: {items.error}</ErrorPre>{" "}
</Fragment>
);
if (items.items.length == 0 && context.settings.warnOnEmptyResult)
return <ErrorMessage message="Dataview: No results to show for list query." />;
return <ListGrouping items={items.items} sourcePath={sourcePath} mode={items.mode} />;
}
Example #13
Source File: table-view.tsx From obsidian-dataview with MIT License | 5 votes |
/** Simple table over headings and corresponding values. */
export function TableGrouping({
headings,
values,
sourcePath,
}: {
headings: string[];
values: Literal[][];
sourcePath: string;
}) {
let settings = useContext(DataviewContext).settings;
return (
<Fragment>
<table class="dataview table-view-table">
<thead class="table-view-thead">
<tr class="table-view-tr-header">
{headings.map((heading, index) => (
<th class="table-view-th">
<Markdown sourcePath={sourcePath} content={heading} />
{index == 0 && <span class="dataview small-text"> ({values.length})</span>}
</th>
))}
</tr>
</thead>
<tbody class="table-view-tbody">
{values.map(row => (
<tr>
{row.map(element => (
<td>
<Lit value={element} sourcePath={sourcePath} />
</td>
))}
</tr>
))}
</tbody>
</table>
{settings.warnOnEmptyResult && values.length == 0 && (
<ErrorMessage message="Dataview: No results to show for table query." />
)}
</Fragment>
);
}
Example #14
Source File: table-view.tsx From obsidian-dataview with MIT License | 5 votes |
/** Pure view over list elements. */
export function TableView({ query, sourcePath }: { query: Query; sourcePath: string }) {
let context = useContext(DataviewContext);
let items = useIndexBackedState<TableViewState>(
context.container,
context.app,
context.settings,
context.index,
{ state: "loading" },
async () => {
let result = await asyncTryOrPropogate(() =>
executeTable(query, context.index, sourcePath, context.settings)
);
if (!result.successful) return { state: "error", error: result.error };
let showId = (query.header as TableQuery).showId;
if (showId) {
let dataWithNames: Literal[][] = [];
for (let entry of result.value.data) dataWithNames.push([entry.id].concat(entry.values));
let name =
result.value.idMeaning.type === "group"
? result.value.idMeaning.name
: context.settings.tableIdColumnName;
return { state: "ready", headings: [name].concat(result.value.names), values: dataWithNames };
}
// Do not append the ID field by default.
return { state: "ready", headings: result.value.names, values: result.value.data.map(v => v.values) };
}
);
if (items.state == "loading")
return (
<Fragment>
<ErrorPre>Loading...</ErrorPre>
</Fragment>
);
else if (items.state == "error")
return (
<Fragment>
{" "}
<ErrorPre>Dataview: {items.error}</ErrorPre>{" "}
</Fragment>
);
return <TableGrouping headings={items.headings} values={items.values} sourcePath={sourcePath} />;
}
Example #15
Source File: task-view.tsx From obsidian-dataview with MIT License | 5 votes |
/**
* Pure view over (potentially grouped) tasks and list items which allows for checking/unchecking tasks and manipulating
* the task view.
*/
export function TaskView({ query, sourcePath }: { query: Query; sourcePath: string }) {
let context = useContext(DataviewContext);
let items = useIndexBackedState<TaskViewState>(
context.container,
context.app,
context.settings,
context.index,
{ state: "loading" },
async () => {
let result = await asyncTryOrPropogate(() =>
executeTask(query, sourcePath, context.index, context.settings)
);
if (!result.successful) return { state: "error", error: result.error, sourcePath };
else return { state: "ready", items: result.value.tasks };
}
);
if (items.state == "loading")
return (
<Fragment>
<ErrorPre>Loading</ErrorPre>
</Fragment>
);
else if (items.state == "error")
return (
<Fragment>
<ErrorPre>Dataview: {items.error}</ErrorPre>
</Fragment>
);
return (
<div class="dataview dataview-container">
<TaskGrouping items={items.items} sourcePath={sourcePath} />
</div>
);
}
Example #16
Source File: IssuerList.tsx From adyen-web with MIT License | 5 votes |
function IssuerList({ items, placeholder = 'idealIssuer.selectField.placeholder', issuer, highlightedIds = [], ...props }: IssuerListProps) {
const { i18n } = useCoreContext();
const { handleChangeFor, triggerValidation, data, valid, errors, isValid } = useForm({
schema,
defaultData: { issuer },
rules: validationRules
});
const [status, setStatus] = useState('ready');
const [inputType, setInputType] = useState<IssuerListInputTypes>(IssuerListInputTypes.Dropdown);
this.setStatus = newStatus => {
setStatus(newStatus);
};
const handleInputChange = useCallback(
(type: IssuerListInputTypes) => (event: UIEvent) => {
setInputType(type);
handleChangeFor('issuer')(event);
},
[handleChangeFor]
);
useEffect(() => {
props.onChange({ data, valid, errors, isValid });
}, [data, valid, errors, isValid]);
this.showValidation = () => {
triggerValidation();
};
const { highlightedItems } = items.reduce(
(memo, item) => {
if (highlightedIds.includes(item.id)) memo.highlightedItems.push({ ...item });
return memo;
},
{ highlightedItems: [] }
);
return (
<div className="adyen-checkout__issuer-list">
{!!highlightedItems.length && (
<Fragment>
<IssuerButtonGroup
selectedIssuerId={inputType === IssuerListInputTypes.ButtonGroup ? data['issuer'] : null}
items={highlightedItems}
onChange={handleInputChange(IssuerListInputTypes.ButtonGroup)}
/>
<ContentSeparator />
</Fragment>
)}
<Field errorMessage={!!errors['issuer']} classNameModifiers={['issuer-list']}>
{renderFormField('select', {
items,
selected: inputType === IssuerListInputTypes.Dropdown ? data['issuer'] : null,
placeholder: i18n.get(placeholder),
name: 'issuer',
className: 'adyen-checkout__issuer-list__dropdown',
onChange: handleInputChange(IssuerListInputTypes.Dropdown)
})}
</Field>
{props.showPayButton &&
props.payButton({
status,
label: payButtonLabel({ issuer: data['issuer'], items: [...items, ...highlightedItems] }, i18n)
})}
</div>
);
}
Example #17
Source File: SelectButton.tsx From adyen-web with MIT License | 5 votes |
function SelectButton(props: SelectButtonProps) {
const { i18n } = useCoreContext();
const { active, readonly, showList } = props;
return (
<SelectButtonElement
aria-disabled={readonly}
aria-expanded={showList}
aria-haspopup="listbox"
className={cx({
'adyen-checkout__dropdown__button': true,
[styles['adyen-checkout__dropdown__button']]: true,
'adyen-checkout__dropdown__button--readonly': readonly,
'adyen-checkout__dropdown__button--active': showList,
[styles['adyen-checkout__dropdown__button--active']]: showList,
'adyen-checkout__dropdown__button--invalid': props.isInvalid,
'adyen-checkout__dropdown__button--valid': props.isValid
})}
filterable={props.filterable}
onClick={!readonly ? props.toggleList : null}
onKeyDown={!readonly ? props.onButtonKeyDown : null}
role={props.filterable ? 'button' : null}
tabIndex="0"
title={active.name || props.placeholder}
toggleButtonRef={props.toggleButtonRef}
type={!props.filterable ? 'button' : null}
aria-describedby={props.ariaDescribedBy}
id={props.id}
>
{!showList || !props.filterable ? (
<Fragment>
<span className="adyen-checkout__dropdown__button__text">{active.selectedOptionName || active.name || props.placeholder}</span>
{active.icon && <Img className="adyen-checkout__dropdown__button__icon" src={active.icon} alt={active.name} />}
</Fragment>
) : (
<input
aria-autocomplete="list"
aria-controls={props.selectListId}
aria-expanded={showList}
aria-owns={props.selectListId}
autoComplete="off"
className={cx('adyen-checkout__filter-input', [styles['adyen-checkout__filter-input']])}
onInput={props.onInput}
placeholder={i18n.get('select.filter.placeholder')}
ref={props.filterInputRef}
role="combobox"
type="text"
/>
)}
</SelectButtonElement>
);
}
Example #18
Source File: PaymentMethodList.tsx From adyen-web with MIT License | 5 votes |
render({ paymentMethods, instantPaymentMethods, activePaymentMethod, cachedPaymentMethods, isLoading }) {
const paymentMethodListClassnames = classNames({
[styles['adyen-checkout__payment-methods-list']]: true,
'adyen-checkout__payment-methods-list': true,
'adyen-checkout__payment-methods-list--loading': isLoading
});
return (
<Fragment>
{this.props.orderStatus && (
<OrderPaymentMethods order={this.props.order} orderStatus={this.props.orderStatus} onOrderCancel={this.props.onOrderCancel} />
)}
{!!instantPaymentMethods.length && <InstantPaymentMethods paymentMethods={instantPaymentMethods} />}
<ul className={paymentMethodListClassnames}>
{paymentMethods.map((paymentMethod, index, paymentMethodsCollection) => {
const isSelected = activePaymentMethod && activePaymentMethod._id === paymentMethod._id;
const isLoaded = paymentMethod._id in cachedPaymentMethods;
const isNextOneSelected =
activePaymentMethod &&
paymentMethodsCollection[index + 1] &&
activePaymentMethod._id === paymentMethodsCollection[index + 1]._id;
return (
<PaymentMethodItem
className={classNames({ 'adyen-checkout__payment-method--next-selected': isNextOneSelected })}
standalone={paymentMethods.length === 1}
paymentMethod={paymentMethod}
isSelected={isSelected}
isDisabling={isSelected && this.props.isDisabling}
isLoaded={isLoaded}
isLoading={isLoading}
onSelect={this.onSelect(paymentMethod)}
key={paymentMethod._id}
showRemovePaymentMethodButton={this.props.showRemovePaymentMethodButton}
onDisableStoredPaymentMethod={this.props.onDisableStoredPaymentMethod}
/>
);
})}
</ul>
</Fragment>
);
}
Example #19
Source File: Faq.tsx From help-widget with MIT License | 5 votes |
Faq = () => {
const service = useContext(ServiceContext);
const [questions, setQuestions] = useState<FaqModel[] | undefined>(undefined);
const [visible, setVisible] = useState(0);
const [statusText, setStatusText] = useState('');
const loaders = [
useTimeout(() => !questions && setStatusText('Loading...'), 500),
useTimeout(() => !questions && setStatusText('Still loading...'), 5000),
useTimeout(() => !questions && setStatusText('Backend still didn\'t return results...'), 10000)];
useEffect(() => {
service?.getFaq()
.then(setQuestions)
.catch(() => setStatusText('Failed to load, try again later.'))
.then(() => loaders.forEach((c) => c()));
}, [service]);
return (
<div>
{
!questions
? statusText
: <Fragment>
<p>
Here is a list of frequently asked questions.
You can also contact us <RouteLink href='/'> here</RouteLink>.
</p>
<ul className={style.root}>
{
questions.map((q, i) => (
<li key={i} className={clsx({ [style.visible]: i === visible })}>
<a href='javascript:;' onClick={() => setVisible(i)}>{q.question}</a>
<span>{q.answer}</span>
</li>))
}
</ul>
</Fragment>
}
</div>
);
}
Example #20
Source File: optionsPanelEntityFormExpanded.component.tsx From passwords-fountain with MIT License | 4 votes |
OptionsPanelEntityFormExpanded: TypedComponent<VariantProps> = ({ switchCurrentVariantName, }: VariantProps) => { const encryptedAdminKey = useSelector(selectAdminKey); const formRef = useRef(undefined as any); const firstInputRef = useRef(undefined as any); const addNewPassword = useAction(passwordListActions.addNewPassword); const editPassword = useAction(passwordListActions.editPassword); const fetchPasswords = useAction(passwordListActions.fetchPasswords); const editedEntity = useSelector(selectSelectedAndDecryptedEntity); const isInEditMode = useSelector(selectIsInEditMode); const actionLabel = isInEditMode ? 'optionsPanel.edit' : 'optionsPanel.add'; useEffect(() => { firstInputRef.current.base.focus(); }, [firstInputRef]); const useInputForm = (fieldName: string, defaultValue?: string) => useInputFormControl(formRef, formValidation, fieldName, defaultValue); const [labelInputState, labelInputProps] = useInputForm( 'label', editedEntity.label ); const [loginInputState, loginInputProps] = useInputForm( 'login', editedEntity.login ); const [passwordInputState, passwordInputProps] = useInputForm( 'password', editedEntity.password ); const [encryptionKeyInputState, encryptionKeyInputProps] = useInputForm( 'encryptionKey' ); const handleCancelClick = (): void => switchCurrentVariantName(optionsPanelVariantNames.entityFormCollapsed); const handleAction = async (e: Event): Promise<void> => { e.preventDefault(); if (!formRef.current?.isValid) { return; } if (isInEditMode) { await editPassword( { label: labelInputState.value, login: loginInputState.value, password: passwordInputState.value, refId: editedEntity.refId, }, encryptionKeyInputState.value ); } else { await addNewPassword( { label: labelInputState.value, login: loginInputState.value, password: passwordInputState.value, }, encryptionKeyInputState.value ); } fetchPasswords(encryptionKeyInputState.value, encryptedAdminKey); }; const renderError = (errors: string) => (): VNode => <Text>{errors}</Text>; const renderLabel = (label: string, noteText?: string) => (): VNode => ( <Fragment> <Text>{label}</Text> {renderIfTrue(() => ( <NoteLabelWrapper> <Text>settings.noteLabel</Text>{' '} <Text>{noteText as string}</Text> </NoteLabelWrapper> ))(Boolean(noteText))} </Fragment> ); return ( <Wrapper> <ContentWrapper> <Content> <form ref={formRef} onSubmit={handleAction}> <FormControlWrapper> <FormControl id={labelInputProps.name} hasError={labelInputProps.hasError} renderLabel={renderLabel( 'optionsPanel.labelInputLabel' )} renderInput={(id: string): VNode => ( <TextInput ref={firstInputRef} id={id} placeholder="e.g. My Bank Account" {...labelInputProps} /> )} renderError={renderError( labelInputState.errors )} /> </FormControlWrapper> <FormControlWrapper> <FormControl id={loginInputProps.name} hasError={loginInputProps.hasError} renderLabel={renderLabel( 'optionsPanel.loginInputLabel' )} renderInput={(id: string): VNode => ( <TextInput id={id} placeholder="e.g. [email protected]" {...loginInputProps} /> )} renderError={renderError( loginInputState.errors )} /> </FormControlWrapper> <FormControlWrapper> <FormControl id={passwordInputProps.name} hasError={passwordInputProps.hasError} renderLabel={renderLabel( 'optionsPanel.passwordInputLabel' )} renderInput={(id: string): VNode => ( <TextInput id={id} type="password" placeholder="e.g. myPassWord1234" {...passwordInputProps} /> )} renderError={renderError( passwordInputState.errors )} /> </FormControlWrapper> <FormControlWrapper> <FormControl id={encryptionKeyInputProps.name} hasError={encryptionKeyInputProps.hasError} renderLabel={renderLabel( 'optionsPanel.encryptionKey', 'optionsPanel.noteEncryptionKey' )} renderInput={(id: string): VNode => ( <TextInput id={id} type="password" placeholder="e.g. MyStrongPassword1234" {...encryptionKeyInputProps} /> )} renderError={renderError( encryptionKeyInputState.errors )} /> </FormControlWrapper> <input type="submit" hidden /> </form> </Content> </ContentWrapper> <ButtonWrapper> <Button onClick={handleCancelClick}> <Text>optionsPanel.cancel</Text> </Button> <Button onClick={handleAction} disabled={!formRef.current?.isValid} > <Text>{actionLabel}</Text> </Button> </ButtonWrapper> </Wrapper> ); }
Example #21
Source File: passwordEntity.component.tsx From passwords-fountain with MIT License | 4 votes |
PasswordEntity: TypedComponent<Props> = ({
data,
isSelected,
onClick,
}: Props) => {
const formRef = useRef(undefined as any);
const promptInputRef = useRef(undefined as any);
const [promptType, setPromptType] = useState<PromptType>(
promptTypes.invisible
);
const encryptedAdminKey = useSelector(selectAdminKey);
const selectedAndDecryptedEntity = useSelector(
selectSelectedAndDecryptedEntityByRefId(data.ref.id)
);
const passwordVisibility =
Object.keys(selectedAndDecryptedEntity).length !== 0;
const [
encryptionKeyInputState,
encryptionKeyInputProps,
] = useInputFormControl(formRef, formValidation, 'encryptionKey');
const removePassword = useAction(passwordListActions.removePassword);
const fetchPasswords = useAction(passwordListActions.fetchPasswords);
const setSelectedAndDecryptedEntity = useAction(
passwordListActions.setSelectedAndDecryptedEntity
);
const resetSelectedAndDecryptedEntity = useAction(
passwordListActions.resetSelectedAndDecryptedEntity
);
useEffect(() => {
promptInputRef.current?.base?.focus();
}, [promptInputRef, promptType]);
const resetPromptState = (): void => {
setPromptType(promptTypes.invisible);
encryptionKeyInputState.setValue('');
encryptionKeyInputState.setErrors('');
};
const handleClick = (e: MouseEvent): void => {
if (e.detail === 0) {
// Firefox fix - ignore outclicking via ENTER key
return;
}
onClick(isSelected ? null : data);
resetSelectedAndDecryptedEntity();
resetPromptState();
};
const handleDecryptionPromptConfirm = async (): Promise<void> => {
const { decrypt } = await import('@/modules/cipher/cipher.service');
const { login, password } = decrypt(
data.data.value,
encryptionKeyInputState.value,
true
) as PasswordEntityVulnerablePayload;
setSelectedAndDecryptedEntity({
refId: data.ref.id,
label: data.data.label,
login,
password,
});
};
const handleRemovalPromptConfirm = async (): Promise<void> => {
const { decrypt } = await import('@/modules/cipher/cipher.service');
// only to check if master key is known - not needed to removal operation itself
decrypt(
data.data.value,
encryptionKeyInputState.value,
true
) as PasswordEntityVulnerablePayload;
await removePassword(data.ref.id);
fetchPasswords(encryptionKeyInputState.value, encryptedAdminKey);
};
const handlePromptConfirm = (e: Event): void => {
e.preventDefault();
if (!formRef.current?.isValid) {
return;
}
if (promptType === promptTypes.decryption) {
handleDecryptionPromptConfirm();
} else {
handleRemovalPromptConfirm();
}
resetPromptState();
};
const handleControlClick = (nextPromptType: PromptType) => (
e: Event
): void => {
e.stopPropagation();
if (promptType) {
return;
}
setPromptType(nextPromptType);
};
const handleFilledEyeClick = (e: Event): void => {
e.stopPropagation();
resetSelectedAndDecryptedEntity();
};
const renderPrompt = renderIfTrue(() => {
const confirmBtnLabel =
promptType === promptTypes.decryption
? 'prompt.decrypt'
: 'prompt.remove';
return (
<Prompt
renderContent={() => (
<form ref={formRef} onSubmit={handlePromptConfirm}>
<FormControlWrapper>
<FormControl
id={encryptionKeyInputProps.name}
hasError={encryptionKeyInputProps.hasError}
renderLabel={() => (
<Text>optionsPanel.enterEncryptionKey</Text>
)}
renderError={() => (
<Text>
{encryptionKeyInputState.errors}
</Text>
)}
renderInput={(id: string) => (
<TextInput
ref={promptInputRef}
id={id}
type="password"
placeholder="e.g. MyStrongPassword1234"
{...encryptionKeyInputProps}
/>
)}
/>
</FormControlWrapper>
</form>
)}
renderControls={() => (
<Fragment>
<Button onClick={resetPromptState}>
<Text>optionsPanel.cancel</Text>
</Button>
<Button
onClick={handlePromptConfirm}
disabled={!formRef.current?.isValid}
>
<Text>{confirmBtnLabel}</Text>
</Button>
</Fragment>
)}
/>
);
});
const renderEyeIcon = () => {
if (passwordVisibility) {
return (
<IconButton
iconName="eyeFilled"
onClick={handleFilledEyeClick}
/>
);
}
return (
<IconButton
iconName="eye"
onClick={handleControlClick(promptTypes.decryption)}
/>
);
};
const renderControls = renderIfTrue(() => {
return (
<Fragment>
<IconButton
iconName="bin"
onClick={handleControlClick(promptTypes.removal)}
/>
{renderEyeIcon()}
</Fragment>
);
});
return (
<Wrapper onClick={handleClick} isSelected={isSelected}>
<GridWrapper>
<DataWrapper>
<Row>
<Label>
<Text>passwordEntity.label</Text>
</Label>{' '}
- <Value>{data.data.label}</Value>
</Row>
<Row>
<Label>
<Text>passwordEntity.login</Text>
</Label>{' '}
-{' '}
<Value>
{selectedAndDecryptedEntity.login ??
placeholderEntityValue}
</Value>
</Row>
<Row>
<Label>
<Text>passwordEntity.password</Text>
</Label>{' '}
-{' '}
<Value>
{selectedAndDecryptedEntity.password ??
placeholderEntityValue}
</Value>
</Row>
</DataWrapper>
<ControlsWrapper>{renderControls(isSelected)}</ControlsWrapper>
</GridWrapper>
{renderPrompt(Boolean(promptType) && isSelected)}
</Wrapper>
);
}
Example #22
Source File: settings.tsx From passwords-fountain with MIT License | 4 votes |
Settings: TypedComponent<Props> = () => { const isFirstTimeOnDevice = useSelector(selectIsFirstTimeOnDevice); const fetchPasswords = useAction(passwordListActions.fetchPasswords); const formRef = useRef<HTMLFormElement>(undefined as any); const [adminKeyInputState, adminKeyInputProps] = useInputFormControl( formRef, formValidation, 'adminKey' ); const [masterKeyInputState, masterKeyInputProps] = useInputFormControl( formRef, formValidation, 'masterKey' ); const headingText = isFirstTimeOnDevice ? 'settings.connectToDB' : 'settings.headingText'; const handleConnectClick = async (e: Event): Promise<void> => { e.preventDefault(); if (!formRef.current?.isValid) { return; } await fetchPasswords( masterKeyInputState.value, adminKeyInputState.value, true ); route('/app'); }; const handleBackClick = (): void => { history.back(); }; const renderNoteLabel = (labelDescription: string, shouldRender: boolean) => renderIfTrue(() => ( <NoteLabelWrapper> <Text>settings.noteLabel</Text>{' '} <DescriptiveText> <Text>{labelDescription}</Text> </DescriptiveText> </NoteLabelWrapper> ))(shouldRender); const renderLabel = ( label: string, labelDescription: string, noteLabelDescription: string, shouldRenderNote = false ) => (): VNode => { return ( <Fragment> <LabelWrapper> <Text>{label}</Text> -{' '} <DescriptiveText> <Text>{labelDescription}</Text> </DescriptiveText> </LabelWrapper> {renderNoteLabel(noteLabelDescription, shouldRenderNote)} </Fragment> ); }; const renderError = (error: string) => (): VNode => <Text>{error}</Text>; return ( <Wrapper> <Header> <Heading> <Text>{headingText}</Text> </Heading> </Header> <FormWrapper> <form ref={formRef}> <FormControlWrapper> <FormControl id={adminKeyInputProps.name} hasError={Boolean(adminKeyInputProps.hasError)} renderLabel={renderLabel( 'settings.adminKeyLabel', 'settings.adminKeyLabelDescription', 'settings.noteLabelDescriptionAdminKey' )} renderInput={(id: string): VNode => ( <TextInput id={id} type="password" placeholder="92xIJf_ge234kalfnqql4o25ou4334201" {...adminKeyInputProps} /> )} renderError={renderError(adminKeyInputState.errors)} /> </FormControlWrapper> <FormControlWrapper> <FormControl id={masterKeyInputProps.name} hasError={Boolean(masterKeyInputProps.hasError)} renderLabel={renderLabel( 'settings.masterKeyLabel', 'settings.masterKeyLabelDescription', 'settings.noteLabelDescription', true )} renderInput={(id: string): VNode => ( <TextInput id={id} type="password" placeholder="myMasterPassword1234" {...masterKeyInputProps} /> )} renderError={renderError( masterKeyInputState.errors )} /> </FormControlWrapper> <ControlsWrapper> <Button onClick={handleBackClick}> <Text>settings.back</Text> </Button> <Button type="submit" onClick={handleConnectClick} disabled={!formRef.current?.isValid} > <Text>settings.connect</Text> </Button> </ControlsWrapper> </form> </FormWrapper> </Wrapper> ); }
Example #23
Source File: ContactForm.tsx From help-widget with MIT License | 4 votes |
ContactForm = () => {
const config = useContext(ConfigContext);
const service = useContext(ServiceContext);
const router = useContext(RouterContext);
const mounted = useIsMounted();
const [submitting, setSubmitting] = useState(false);
const [serverError, setServerError] = useState('');
const [emailValue, setEmailValue] = useState('');
const emailError = useMemo(
() => mounted.current && (!emailValue || !(/^\S+@\S+$/.test(emailValue)))
? 'Email is required and must be valid' : '',
[emailValue, submitting, mounted]);
const [messageValue, setMessageValue] = useState('');
const messageError = useMemo(
() => mounted.current && (!messageValue || messageValue.length < 5)
? 'Text is required and must contain at least 5 characters' : '',
[messageValue, submitting, mounted]);
const formValid = useMemo(
() => ![emailError, messageError].reduce((m, n) => m + n),
[emailError, messageError]);
useEffect(() => {
if (!submitting) {
return;
}
setServerError(''); // reset previous server error
if (!formValid) {
setSubmitting(false);
return;
}
console.log('Sending form', { emailValue, messageValue });
service?.sendForm({ email: emailValue, message: messageValue })
.then(() => {
router.setRoute('/thankyou');
})
.catch(() => {
setServerError(`Something went wrong and we couldn't send your form. Please try again later.`);
})
.then(() => setSubmitting(false));
}, [formValid, submitting, emailValue, messageValue, service]);
return (
<div>
<p>{config.text.formSubTitle ??
<Fragment>
Leave your message and we'll get back to you shortly.
You can also read our <RouteLink href='/faq'>FAQ</RouteLink>.</Fragment>}</p>
<form
onSubmit={(e) => {
e.preventDefault();
setSubmitting(true);
}}>
{serverError && <div className={style.error}>{serverError}</div>}
<Field
name='email'
title='Email'
error={emailError}
render={(inputProps) => (
<input
type='text'
inputMode='email'
disabled={submitting}
placeholder='[email protected]'
autoFocus
onInput={(e) => setEmailValue(e.currentTarget.value)}
{...inputProps}
/>)} />
<Field
name='message'
title='Message'
error={messageError}
render={(inputProps) => (
<textarea
rows={7}
disabled={submitting}
autoComplete='disable'
onInput={(e) => setMessageValue(e.currentTarget.value)}
{...inputProps}
/>)} />
<div className={style.actions}>
<button type='submit' disabled={submitting || !formValid}>
{submitting ? 'Sending...' : 'Send'}
</button>
</div>
</form>
</div >);
}
Example #24
Source File: pagination.tsx From gridjs with MIT License | 4 votes |
renderPages() {
if (this.props.buttonsCount <= 0) {
return null;
}
// how many pagination buttons to render?
const maxCount: number = Math.min(this.pages, this.props.buttonsCount);
let pagePivot = Math.min(this.state.page, Math.floor(maxCount / 2));
if (this.state.page + Math.floor(maxCount / 2) >= this.pages) {
pagePivot = maxCount - (this.pages - this.state.page);
}
return (
<Fragment>
{this.pages > maxCount && this.state.page - pagePivot > 0 && (
<Fragment>
<button
tabIndex={0}
role="button"
onClick={this.setPage.bind(this, 0)}
title={this._('pagination.firstPage')}
aria-label={this._('pagination.firstPage')}
className={this.config.className.paginationButton}
>
{this._('1')}
</button>
<button
tabIndex={-1}
className={classJoin(
className('spread'),
this.config.className.paginationButton,
)}
>
...
</button>
</Fragment>
)}
{Array.from(Array(maxCount).keys())
.map((i) => this.state.page + (i - pagePivot))
.map((i) => (
<button
tabIndex={0}
role="button"
onClick={this.setPage.bind(this, i)}
className={classJoin(
this.state.page === i
? classJoin(
className('currentPage'),
this.config.className.paginationButtonCurrent,
)
: null,
this.config.className.paginationButton,
)}
title={this._('pagination.page', i + 1)}
aria-label={this._('pagination.page', i + 1)}
>
{this._(`${i + 1}`)}
</button>
))}
{this.pages > maxCount && this.pages > this.state.page + pagePivot + 1 && (
<Fragment>
<button
tabIndex={-1}
className={classJoin(
className('spread'),
this.config.className.paginationButton,
)}
>
...
</button>
<button
tabIndex={0}
role="button"
onClick={this.setPage.bind(this, this.pages - 1)}
title={this._('pagination.page', this.pages)}
aria-label={this._('pagination.page', this.pages)}
className={this.config.className.paginationButton}
>
{this._(`${this.pages}`)}
</button>
</Fragment>
)}
</Fragment>
);
}
Example #25
Source File: markdown.tsx From obsidian-dataview with MIT License | 4 votes |
/** Intelligently render an arbitrary literal value. */
export function RawLit({
value,
sourcePath,
inline = false,
depth = 0,
}: {
value: Literal | undefined;
sourcePath: string;
inline?: boolean;
depth?: number;
}) {
const context = useContext(DataviewContext);
// Short-circuit if beyond the maximum render depth.
if (depth >= context.settings.maxRecursiveRenderDepth) return <Fragment>...</Fragment>;
if (Values.isNull(value) || value === undefined) {
return <Markdown content={context.settings.renderNullAs} sourcePath={sourcePath} />;
} else if (Values.isString(value)) {
return <Markdown content={value} sourcePath={sourcePath} />;
} else if (Values.isNumber(value)) {
return <Fragment>{"" + value}</Fragment>;
} else if (Values.isBoolean(value)) {
return <Fragment>{"" + value}</Fragment>;
} else if (Values.isDate(value)) {
return <Fragment>{renderMinimalDate(value, context.settings, currentLocale())}</Fragment>;
} else if (Values.isDuration(value)) {
return <Fragment>{renderMinimalDuration(value)}</Fragment>;
} else if (Values.isLink(value)) {
// Special case handling of image/video/etc embeddings to bypass the Obsidian API not working.
if (isImageEmbed(value)) {
let realFile = context.app.metadataCache.getFirstLinkpathDest(value.path, sourcePath);
if (!realFile) return <Markdown content={value.markdown()} sourcePath={sourcePath} />;
let dimensions = extractImageDimensions(value);
let resourcePath = context.app.vault.getResourcePath(realFile);
if (dimensions && dimensions.length == 2)
return <img alt={value.path} src={resourcePath} width={dimensions[0]} height={dimensions[1]} />;
else if (dimensions && dimensions.length == 1)
return <img alt={value.path} src={resourcePath} width={dimensions[0]} />;
else return <img alt={value.path} src={resourcePath} />;
}
return <Markdown content={value.markdown()} sourcePath={sourcePath} />;
} else if (Values.isHtml(value)) {
return <EmbedHtml element={value} />;
} else if (Values.isFunction(value)) {
return <Fragment><function></Fragment>;
} else if (Values.isArray(value) || DataArray.isDataArray(value)) {
if (!inline) {
return (
<ul class={"dataview dataview-ul dataview-result-list-ul"}>
{value.map(subvalue => (
<li class="dataview-result-list-li">
<Lit value={subvalue} sourcePath={sourcePath} inline={inline} depth={depth + 1} />
</li>
))}
</ul>
);
} else {
if (value.length == 0) return <Fragment><Empty List></Fragment>;
return (
<span class="dataview dataview-result-list-span">
{value.map((subvalue, index) => (
<Fragment>
{index == 0 ? "" : ", "}
<Lit value={subvalue} sourcePath={sourcePath} inline={inline} depth={depth + 1} />
</Fragment>
))}
</span>
);
}
} else if (Values.isObject(value)) {
// Don't render classes in case they have recursive references; spoopy.
if (value?.constructor?.name && value?.constructor?.name != "Object") {
return <Fragment><{value.constructor.name}></Fragment>;
}
if (!inline) {
return (
<ul class="dataview dataview-ul dataview-result-object-ul">
{Object.entries(value).map(([key, value]) => (
<li class="dataview dataview-li dataview-result-object-li">
{key}: <Lit value={value} sourcePath={sourcePath} inline={inline} depth={depth + 1} />
</li>
))}
</ul>
);
} else {
if (Object.keys(value).length == 0) return <Fragment><Empty Object></Fragment>;
return (
<span class="dataview dataview-result-object-span">
{Object.entries(value).map(([key, value], index) => (
<Fragment>
{index == 0 ? "" : ", "}
{key}: <Lit value={value} sourcePath={sourcePath} inline={inline} depth={depth + 1} />
</Fragment>
))}
</span>
);
}
}
return <Fragment><Unrecognized: {JSON.stringify(value)}></Fragment>;
}
Example #26
Source File: Field.tsx From adyen-web with MIT License | 4 votes |
Field: FunctionalComponent<FieldProps> = props => {
//
const {
children,
className,
classNameModifiers,
dir,
disabled,
errorMessage,
helper,
inputWrapperModifiers,
isCollatingErrors,
isLoading,
isValid,
label,
name,
onBlur,
onFieldBlur,
onFocus,
onFocusField,
showValidIcon,
useLabelElement,
// Redeclare prop names to avoid internal clashes
filled: propsFilled,
focused: propsFocused
} = props;
const uniqueId = useRef(getUniqueId(`adyen-checkout-${name}`));
const [focused, setFocused] = useState(false);
const [filled, setFilled] = useState(false);
// The means by which focussed/filled is set for securedFields
if (propsFocused != null) setFocused(!!propsFocused);
if (propsFilled != null) setFilled(!!propsFilled);
// The means by which focussed/filled is set for other fields - this function is passed down to them and triggered
const onFocusHandler = useCallback(
(event: h.JSX.TargetedEvent<HTMLInputElement>) => {
setFocused(true);
onFocus?.(event);
},
[onFocus]
);
const onBlurHandler = useCallback(
(event: h.JSX.TargetedEvent<HTMLInputElement>) => {
setFocused(false);
onBlur?.(event);
// When we also need to fire a specific function when a field blurs
onFieldBlur?.(event);
},
[onBlur, onFieldBlur]
);
const renderContent = useCallback(() => {
return (
<Fragment>
{typeof label === 'string' && (
<span
className={classNames({
'adyen-checkout__label__text': true,
'adyen-checkout__label__text--error': errorMessage
})}
>
{label}
</span>
)}
{/*@ts-ignore - function is callable*/}
{typeof label === 'function' && label()}
{helper && <span className={'adyen-checkout__helper-text'}>{helper}</span>}
<div
className={classNames([
'adyen-checkout__input-wrapper',
...inputWrapperModifiers.map(m => `adyen-checkout__input-wrapper--${m}`)
])}
dir={dir}
>
{toChildArray(children).map(
(child: ComponentChild): ComponentChild => {
const childProps = {
isValid,
onFocusHandler,
onBlurHandler,
isInvalid: !!errorMessage,
...(name && { uniqueId: uniqueId.current })
};
return cloneElement(child as VNode, childProps);
}
)}
{isLoading && (
<span className="adyen-checkout-input__inline-validation adyen-checkout-input__inline-validation--loading">
<Spinner size="small" />
</span>
)}
{isValid && showValidIcon !== false && (
<span className="adyen-checkout-input__inline-validation adyen-checkout-input__inline-validation--valid">
<Icon type="checkmark" />
</span>
)}
{errorMessage && (
<span className="adyen-checkout-input__inline-validation adyen-checkout-input__inline-validation--invalid">
<Icon type="field_error" />
</span>
)}
</div>
{errorMessage && typeof errorMessage === 'string' && errorMessage.length && (
<span
className={'adyen-checkout__error-text'}
id={`${uniqueId.current}${ARIA_ERROR_SUFFIX}`}
aria-hidden={isCollatingErrors ? 'true' : null}
aria-live={isCollatingErrors ? null : 'polite'}
>
{errorMessage}
</span>
)}
</Fragment>
);
}, [children, errorMessage, isLoading, isValid, label, onFocusHandler, onBlurHandler]);
const LabelOrDiv = useCallback(({ onFocusField, focused, filled, disabled, name, uniqueId, useLabelElement, children }) => {
const defaultWrapperProps = {
onClick: onFocusField,
className: classNames({
'adyen-checkout__label': true,
'adyen-checkout__label--focused': focused,
'adyen-checkout__label--filled': filled,
'adyen-checkout__label--disabled': disabled
})
};
return useLabelElement ? (
<label {...defaultWrapperProps} htmlFor={name && uniqueId}>
{children}
</label>
) : (
<div {...defaultWrapperProps} role={'form'}>
{children}
</div>
);
}, []);
/**
* RENDER
*/
return (
<div
className={classNames(
'adyen-checkout__field',
className,
classNameModifiers.map(m => `adyen-checkout__field--${m}`),
{
'adyen-checkout__field--error': errorMessage,
'adyen-checkout__field--valid': isValid
}
)}
>
<LabelOrDiv
onFocusField={onFocusField}
name={name}
disabled={disabled}
filled={filled}
focused={focused}
useLabelElement={useLabelElement}
uniqueId={uniqueId.current}
>
{renderContent()}
</LabelOrDiv>
</div>
);
}
Example #27
Source File: CardInput.tsx From adyen-web with MIT License | 4 votes |
CardInput: FunctionalComponent<CardInputProps> = props => {
const sfp = useRef(null);
const billingAddressRef = useRef(null);
const isValidating = useRef(false);
const cardInputRef = useRef<CardInputRef>({});
// Just call once
if (!Object.keys(cardInputRef.current).length) {
props.setComponentRef(cardInputRef.current);
}
const hasPanLengthRef = useRef(0);
const isAutoJumping = useRef(false);
const errorFieldId = 'creditCardErrors';
const { collateErrors, moveFocus, showPanel } = props.SRConfig;
const specifications = useMemo(() => new Specifications(props.specifications), [props.specifications]);
// Creates access to sfp so we can call functionality on it (like handleOnAutoComplete) directly from the console. Used for testing.
if (process.env.NODE_ENV === 'development') cardInputRef.current.sfp = sfp;
/**
* STATE HOOKS
*/
const [status, setStatus] = useState('ready');
const [errors, setErrors] = useState<CardInputErrorState>({});
const [valid, setValid] = useState<CardInputValidState>({
...(props.holderNameRequired && { holderName: false })
});
const [data, setData] = useState<CardInputDataState>({
...(props.hasHolderName && { holderName: props.data.holderName ?? '' })
});
// An object containing a collection of all the errors that can be passed to the ErrorPanel to be read by the screenreader
const [mergedSRErrors, setMergedSRErrors] = useState<ErrorPanelObj>(null);
const [focusedElement, setFocusedElement] = useState('');
const [isSfpValid, setIsSfpValid] = useState(false);
const [expiryDatePolicy, setExpiryDatePolicy] = useState(DATE_POLICY_REQUIRED);
const [cvcPolicy, setCvcPolicy] = useState(CVC_POLICY_REQUIRED);
const [issuingCountryCode, setIssuingCountryCode] = useState<string>(null);
const [dualBrandSelectElements, setDualBrandSelectElements] = useState([]);
const [selectedBrandValue, setSelectedBrandValue] = useState('');
const showBillingAddress = props.billingAddressMode !== AddressModeOptions.none && props.billingAddressRequired;
const partialAddressSchema = handlePartialAddressMode(props.billingAddressMode);
const [storePaymentMethod, setStorePaymentMethod] = useState(false);
const [billingAddress, setBillingAddress] = useState<AddressData>(showBillingAddress ? props.data.billingAddress : null);
const [showSocialSecurityNumber, setShowSocialSecurityNumber] = useState(false);
const [socialSecurityNumber, setSocialSecurityNumber] = useState('');
const [installments, setInstallments] = useState<InstallmentsObj>({ value: null });
// re. Disable arrows for iOS: The name of the element calling for other elements to be disabled
// - either a securedField type (like 'encryptedCardNumber') when call is coming from SF
// or else the name of an internal, Adyen-web, element (like 'holderName')
const [iOSFocusedField, setIOSFocusedField] = useState(null);
/**
* LOCAL VARS
*/
const {
handleChangeFor,
triggerValidation,
data: formData,
valid: formValid,
errors: formErrors,
setSchema,
setData: setFormData,
setValid: setFormValid,
setErrors: setFormErrors
} = useForm<CardInputDataState>({
schema: [],
defaultData: props.data,
formatters: cardInputFormatters,
rules: cardInputValidationRules
});
const hasInstallments = !!Object.keys(props.installmentOptions).length;
const showAmountsInInstallments = props.showInstallmentAmounts ?? true;
const cardCountryCode: string = issuingCountryCode ?? props.countryCode;
const isKorea = cardCountryCode === 'kr'; // If issuingCountryCode or the merchant defined countryCode is set to 'kr'
const showKCP = props.configuration.koreanAuthenticationRequired && isKorea;
const showBrazilianSSN: boolean =
(showSocialSecurityNumber && props.configuration.socialSecurityNumberMode === 'auto') ||
props.configuration.socialSecurityNumberMode === 'show';
/**
* HANDLERS
*/
// SecuredField-only handler
const handleFocus = getFocusHandler(setFocusedElement, props.onFocus, props.onBlur);
const retrieveLayout = () => {
return getLayout({
props,
showKCP,
showBrazilianSSN,
...(props.billingAddressRequired && {
countrySpecificSchemas: specifications.getAddressSchemaForCountry(billingAddress?.country),
billingAddressRequiredFields: props.billingAddressRequiredFields
})
});
};
/**
* re. Disabling arrow keys in iOS:
* Only by disabling all fields in the Card PM except for the active securedField input can we force the iOS soft keyboard arrow keys to disable
*
* @param obj - has fieldType prop saying whether this function is being called in response to an securedFields click ('encryptedCardNumber' etc)
* - in which case we should disable all non-SF fields
* or,
* due to an internal action ('webInternalElement') - in which case we can enable all non-SF fields
*/
const handleTouchstartIOS = useCallback((obj: TouchStartEventObj) => {
const elementType = obj.fieldType !== 'webInternalElement' ? obj.fieldType : obj.name;
setIOSFocusedField(elementType);
}, []);
// Callback for ErrorPanel
const handleErrorPanelFocus = getErrorPanelHandler(isValidating, sfp, handleFocus);
const handleAddress = getAddressHandler(setFormData, setFormValid, setFormErrors);
const doPanAutoJump = getAutoJumpHandler(isAutoJumping, sfp, retrieveLayout());
const handleSecuredFieldsChange = (sfState: SFPState, eventDetails?: OnChangeEventDetails): void => {
// Clear errors so that the screenreader will read them *all* again - without this it only reads the newly added ones
setMergedSRErrors(null);
/**
* Handling auto complete value for holderName (but only if the component is using a holderName field)
*/
if (sfState.autoCompleteName) {
if (!props.hasHolderName) return;
const holderNameValidationFn = getRuleByNameAndMode('holderName', 'blur');
const acHolderName = holderNameValidationFn(sfState.autoCompleteName) ? sfState.autoCompleteName : null;
if (acHolderName) {
setFormData('holderName', acHolderName);
setFormValid('holderName', true); // only if holderName is valid does this fny get called - so we know it's valid and w/o error
setFormErrors('holderName', null);
}
return;
}
/**
* If PAN has just become valid: decide if we can shift focus to the next field.
*
* We can if the config prop, autoFocus, is true AND we have a panLength value from binLookup AND one of the following scenarios is true:
* - If encryptedCardNumber was invalid but now is valid
* [scenario: shopper has typed in a number and field is now valid]
* - If encryptedCardNumber was valid and still is valid and we're handling an onBrand event (triggered by binLookup which has happened after the handleOnFieldValid event)
* [scenario: shopper has pasted in a full, valid, number]
*/
if (
props.autoFocus &&
hasPanLengthRef.current > 0 &&
((!valid.encryptedCardNumber && sfState.valid?.encryptedCardNumber) ||
(valid.encryptedCardNumber && sfState.valid.encryptedCardNumber && eventDetails.event === 'handleOnBrand'))
) {
doPanAutoJump();
}
/**
* Process SFP state
*/
setData({ ...data, ...sfState.data });
setErrors({ ...errors, ...sfState.errors });
setValid({ ...valid, ...sfState.valid });
setIsSfpValid(sfState.isSfpValid);
// Values relating to /binLookup response
setCvcPolicy(sfState.cvcPolicy);
setShowSocialSecurityNumber(sfState.showSocialSecurityNumber);
setExpiryDatePolicy(sfState.expiryDatePolicy);
};
// Farm the handlers for binLookup related functionality out to another 'extensions' file
const extensions = useMemo(
() =>
CIExtensions(
props,
{ sfp },
{ dualBrandSelectElements, setDualBrandSelectElements, setSelectedBrandValue, issuingCountryCode, setIssuingCountryCode },
hasPanLengthRef
),
[dualBrandSelectElements, issuingCountryCode]
);
/**
* EXPOSE METHODS expected by Card.tsx
*/
cardInputRef.current.showValidation = () => {
// Clear errors so that the screenreader will read them *all* again
setMergedSRErrors(null);
// Validate SecuredFields
sfp.current.showValidation();
// Validate holderName & SSN & KCP (taxNumber) but *not* billingAddress
triggerValidation(['holderName', 'socialSecurityNumber', 'taxNumber']);
// Validate Address
if (billingAddressRef?.current) billingAddressRef.current.showValidation();
isValidating.current = true;
};
cardInputRef.current.processBinLookupResponse = (binLookupResponse: BinLookupResponse, isReset: boolean) => {
extensions.processBinLookup(binLookupResponse, isReset);
};
cardInputRef.current.setStatus = setStatus;
/**
* EFFECT HOOKS
*/
useEffect(() => {
// componentDidMount - expose more methods expected by Card.tsx
cardInputRef.current.setFocusOn = sfp.current.setFocusOn;
cardInputRef.current.updateStyles = sfp.current.updateStyles;
cardInputRef.current.handleUnsupportedCard = sfp.current.handleUnsupportedCard;
// componentWillUnmount
return () => {
sfp.current.destroy();
};
}, []);
/**
* Handle form schema updates
*/
useEffect(() => {
const newSchema = [
...(props.hasHolderName ? ['holderName'] : []),
...(showBrazilianSSN ? ['socialSecurityNumber'] : []),
...(showKCP ? ['taxNumber'] : []),
...(showBillingAddress ? ['billingAddress'] : [])
];
setSchema(newSchema);
}, [props.hasHolderName, showBrazilianSSN, showKCP]);
/**
* Handle updates from useForm
*/
useEffect(() => {
// Clear errors so that the screenreader will read them *all* again
setMergedSRErrors(null);
setData({ ...data, holderName: formData.holderName ?? '', taxNumber: formData.taxNumber });
setSocialSecurityNumber(formData.socialSecurityNumber);
if (showBillingAddress) setBillingAddress({ ...formData.billingAddress });
setValid({
...valid,
holderName: props.holderNameRequired ? formValid.holderName : true,
// Setting value to false if it's falsy keeps in line with existing, expected behaviour
// - but there is an argument to allow 'undefined' as a value to indicate the non-presence of the field
socialSecurityNumber: formValid.socialSecurityNumber ? formValid.socialSecurityNumber : false,
taxNumber: formValid.taxNumber ? formValid.taxNumber : false,
billingAddress: formValid.billingAddress ? formValid.billingAddress : false
});
// Check if billingAddress errors object has any properties that aren't null or undefined
const addressHasErrors = formErrors.billingAddress
? Object.entries(formErrors.billingAddress).reduce((acc, [, error]) => acc || error != null, false)
: false;
// Errors
setErrors({
...errors,
holderName: props.holderNameRequired && !!formErrors.holderName ? formErrors.holderName : null,
socialSecurityNumber: showBrazilianSSN && !!formErrors.socialSecurityNumber ? formErrors.socialSecurityNumber : null,
taxNumber: showKCP && !!formErrors.taxNumber ? formErrors.taxNumber : null,
billingAddress: showBillingAddress && addressHasErrors ? formErrors.billingAddress : null
});
}, [formData, formValid, formErrors]);
/**
* Main 'componentDidUpdate' handler
*/
useEffect(() => {
const holderNameValid: boolean = valid.holderName;
const sfpValid: boolean = isSfpValid;
const addressValid: boolean = showBillingAddress ? valid.billingAddress : true;
const koreanAuthentication: boolean = showKCP ? !!valid.taxNumber && !!valid.encryptedPassword : true;
const socialSecurityNumberValid: boolean = showBrazilianSSN ? !!valid.socialSecurityNumber : true;
const isValid: boolean = sfpValid && holderNameValid && addressValid && koreanAuthentication && socialSecurityNumberValid;
const sfStateErrorsObj = sfp.current.mapErrorsToValidationRuleResult();
const mergedErrors = { ...errors, ...sfStateErrorsObj }; // maps sfErrors AND solves race condition problems for sfp from showValidation
// Extract and then flatten billingAddress errors into a new object with *all* the field errors at top level
const { billingAddress: extractedAddressErrors, ...errorsWithoutAddress } = mergedErrors;
const errorsForPanel = { ...errorsWithoutAddress, ...extractedAddressErrors };
const sortedMergedErrors = sortErrorsForPanel({
errors: errorsForPanel,
layout: retrieveLayout(),
i18n: props.i18n,
countrySpecificLabels: specifications.getAddressLabelsForCountry(billingAddress?.country)
});
setMergedSRErrors(sortedMergedErrors);
props.onChange({
data,
valid,
errors: mergedErrors,
isValid,
billingAddress,
selectedBrandValue,
storePaymentMethod,
socialSecurityNumber,
installments
});
}, [data, valid, errors, selectedBrandValue, storePaymentMethod, installments]);
/**
* RENDER
*/
const FieldToRender = props.storedPaymentMethodId ? StoredCardFieldsWrapper : CardFieldsWrapper;
return (
<Fragment>
<SecuredFieldsProvider
ref={sfp}
{...extractPropsForSFP(props)}
styles={{ ...defaultStyles, ...props.styles }}
koreanAuthenticationRequired={props.configuration.koreanAuthenticationRequired}
hasKoreanFields={!!(props.configuration.koreanAuthenticationRequired && props.countryCode === 'kr')}
onChange={handleSecuredFieldsChange}
onBrand={props.onBrand}
onFocus={handleFocus}
type={props.brand}
isCollatingErrors={collateErrors}
onTouchstartIOS={handleTouchstartIOS}
render={({ setRootNode, setFocusOn }, sfpState) => (
<div
ref={setRootNode}
className={`adyen-checkout__card-input ${styles['card-input__wrapper']} adyen-checkout__card-input--${props.fundingSource ??
'credit'}`}
role={collateErrors && 'form'}
aria-describedby={collateErrors ? errorFieldId : null}
>
<FieldToRender
// Extract exact props that we need to pass down
{...extractPropsForCardFields(props)}
// Pass on vars created in CardInput:
// Base (shared w. StoredCard)
data={data}
valid={valid}
errors={errors}
handleChangeFor={handleChangeFor}
focusedElement={focusedElement}
setFocusOn={setFocusOn}
sfpState={sfpState}
collateErrors={collateErrors}
errorFieldId={errorFieldId}
cvcPolicy={cvcPolicy}
hasInstallments={hasInstallments}
showAmountsInInstallments={showAmountsInInstallments}
handleInstallments={setInstallments}
// For Card
brandsIcons={props.brandsIcons}
mergedSRErrors={mergedSRErrors}
moveFocus={moveFocus}
showPanel={showPanel}
handleErrorPanelFocus={handleErrorPanelFocus}
formData={formData}
formErrors={formErrors}
formValid={formValid}
expiryDatePolicy={expiryDatePolicy}
dualBrandSelectElements={dualBrandSelectElements}
extensions={extensions}
selectedBrandValue={selectedBrandValue}
// For KCP
showKCP={showKCP}
// For SSN
showBrazilianSSN={showBrazilianSSN}
socialSecurityNumber={socialSecurityNumber}
// For Store details
handleOnStoreDetails={setStorePaymentMethod}
// For Address
billingAddress={billingAddress}
handleAddress={handleAddress}
billingAddressRef={billingAddressRef}
partialAddressSchema={partialAddressSchema}
//
iOSFocusedField={iOSFocusedField}
/>
</div>
)}
/>
{props.showPayButton &&
props.payButton({
status,
icon: getImage({ loadingContext: props.loadingContext, imageFolder: 'components/' })('lock')
})}
</Fragment>
);
}