Skip to content

Commit

Permalink
feat: Add TS types v3.2.0 (#2)
Browse files Browse the repository at this point in the history
* feat: add generated types
* chore: remove prop-types peer-dep & declarations
* jsx preserve
* feat: expose component prop types as export
* fix: `Group.displayName` type
  • Loading branch information
JakobJingleheimer authored Jan 25, 2024
1 parent 4a4523a commit f254352
Show file tree
Hide file tree
Showing 30 changed files with 429 additions and 168 deletions.
4 changes: 4 additions & 0 deletions lib/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.fixture.js
*.mock.js
*.spec.js
*.test.jsx
13 changes: 13 additions & 0 deletions lib/composeData.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default function composeData(values: ComposedData, element: HTMLButtonElement | HTMLFieldSetElement | HTMLInputElement | HTMLObjectElement | HTMLOutputElement | HTMLSelectElement | HTMLTextAreaElement, i: Int, collection: HTMLFormControlsCollection): ComposedData;
export namespace FIELD_TAGS {
let FIELDSET: "fieldset";
let INPUT: "input";
let SELECT: "select";
let TEXTAREA: "textarea";
}
export type Int = number;
export type ComposedData = {
[k: string]: string | number | boolean | (string | number)[] | FileList | ComposedData | null | undefined;
};
export type FieldTag = (typeof FIELD_TAGS)[keyof typeof FIELD_TAGS];
//# sourceMappingURL=composeData.d.ts.map
1 change: 1 addition & 0 deletions lib/composeData.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 7 additions & 6 deletions lib/composeData.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@ import _isArray from 'lodash-es/isArray.js';
import _reduce from 'lodash-es/reduce.js';


export const FIELD_TAGS = {
/**
* @typedef {typeof FIELD_TAGS[keyof typeof FIELD_TAGS]} FieldTag
*/
export const FIELD_TAGS = /** @type {const} */ ({
FIELDSET: 'fieldset',
INPUT: 'input',
SELECT: 'select',
TEXTAREA: 'textarea',
};
});

const listNameRgx = /\[\d*\]$/;
const isListName = (name) => listNameRgx.test(name);

/**
* @typedef {number} Integer
*/
/**
* @typedef {number} Int
* @typedef {{ [k: string]: Array<number | string> | boolean | FileList | number | string | null |
* undefined | ComposedData }} ComposedData An object with values cast to the type declared by the
* field from which the value was derived. Checkboxses and Radios have boolean values.
Expand All @@ -27,7 +28,7 @@ const isListName = (name) => listNameRgx.test(name);
* contains `__exclude: true`, then fields that are read-only will be excluded from the output.
* @param {HTMLButtonElement|HTMLFieldSetElement|HTMLInputElement|HTMLObjectElement|HTMLOutputElement|HTMLSelectElement|HTMLTextAreaElement}
* element The element currently being processed.
* @param {Integer} i The index of the field in the master list of fields.
* @param {Int} i The index of the field in the master list of fields.
* @param {HTMLFormControlsCollection} collection The parent `<form>` element.
* @returns {ComposedData}
*/
Expand Down
2 changes: 2 additions & 0 deletions lib/deepDiff.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export default function deepDiff(oldVals: any, newVals: any, delta?: Record<string, never>): import('./composeData.js').ComposedData;
//# sourceMappingURL=deepDiff.d.ts.map
1 change: 1 addition & 0 deletions lib/deepDiff.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/deepDiff.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import _isObject from 'lodash-es/isObject.js';
*
* @param {*} oldVals
* @param {*} newVals
* @param {object} delta
* @param {Record<string, never>} delta
* @returns {import('./composeData.js').ComposedData} The delta—the difference between old and new. When an old field is absent
* in new, the old field is set to `null`.
*/
Expand Down
37 changes: 37 additions & 0 deletions lib/react/Button/Button.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
declare function Button({ appearance, children: label, className, fluid, icon: Icon, type, variant, ...others }: ButtonProps & import("react").BaseHTMLAttributes<any>): JSX.Element;
declare namespace Button {
let displayName: "Form5Button";
namespace APPEARANCES {
let AFFIRMING: "affirming";
let BASIC: "basic";
let DANGER: "danger";
let PRIMARY: "primary";
let WARNING: "warning";
}
namespace TYPES {
let BUTTON: "button";
let RESET: "reset";
let SUBMIT: "submit";
}
namespace VARIANTS {
let CTA: "cta";
let GLYPH: "glyph";
}
function Group({ className, ...props }: import("../Group/Group.jsx").GroupProps & import("react").HTMLAttributes<HTMLElement>): JSX.Element;
}
export default Button;
export { styles as buttonClasses };
export type React = typeof import("react");
export type ButtonProps = {
appearance?: Appearance | undefined;
disabled?: boolean | undefined;
children?: React.ReactNode;
fluid?: boolean | undefined;
icon?: React.ReactNode;
type?: "button" | "reset" | "submit" | undefined;
variant?: Variant | undefined;
};
export type Appearance = (typeof Button.APPEARANCES)[keyof typeof Button.APPEARANCES];
export type Type = (typeof Button.TYPES)[keyof typeof Button.TYPES];
export type Variant = (typeof Button.VARIANTS)[keyof typeof Button.VARIANTS];
//# sourceMappingURL=Button.d.ts.map
1 change: 1 addition & 0 deletions lib/react/Button/Button.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 33 additions & 37 deletions lib/react/Button/Button.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { clsx } from 'clsx';
import PropTypes from 'prop-types';

import Group from '../Group/Group.jsx';

Expand All @@ -9,25 +8,34 @@ import styles from './Button.module.css';
export { styles as buttonClasses };

/**
*
* @param {object} props
* @param {APPEARANCE} [props.appearance=Button.APPEARANCES.PRIMARY]
* @param {ReactNode} props.children
* @param {string} props.className
* @param {boolean} [props.fluid=false]
* @param {ReactNode} props.icon
* @param {HTMLButtonElement['type']} [props.type=Button.TYPES.BUTTON]
* @param {VARIANT} [props.variant=Button.VARIANTS.CTA]
* @param {import('react').HTMLProps<HTMLButtonElement>} props.others
* @returns {HTMLButtonElement}
* @typedef {import('react')} React
*/

/**
* @typedef {object} ButtonProps
* @property {Appearance} [ButtonProps.appearance=Button.APPEARANCES.PRIMARY]
* @property {boolean} [ButtonProps.disabled]
* @property {React.ReactNode} [ButtonProps.children]
* @property {boolean} [ButtonProps.fluid=false]
* @property {React.ReactNode} [ButtonProps.icon]
* @property {HTMLButtonElement['type']} [ButtonProps.type=Button.TYPES.BUTTON]
* @property {Variant} [ButtonProps.variant=Button.VARIANTS.CTA]
*/

/**
* @param {ButtonProps & React.BaseHTMLAttributes} props
*/
export default function Button({
appearance = Button.APPEARANCES.PRIMARY,
children: label,
className,
fluid,
icon: Icon,
type = Button.TYPES.BUTTON,
variant = Button.VARIANTS.CTA,
...others
}) {
Object.assign(others, { appearance, type, variant });
return (
<button
{...others}
Expand All @@ -47,49 +55,37 @@ export default function Button({
);
}

Button.displayName = 'Form5Button';
Button.displayName = /** @type {const} */ ('Form5Button');

/**
* @typedef {keyof typeof Button.APPEARANCES} APPEARANCE_KEYS
* @typedef {typeof Button.APPEARANCES[APPEARANCE_KEYS]} APPEARANCE
* @typedef {typeof Button.APPEARANCES[keyof typeof Button.APPEARANCES]} Appearance
*/
Button.APPEARANCES = {
Button.APPEARANCES = /** @type {const} */ ({
AFFIRMING: 'affirming',
BASIC: 'basic',
DANGER: 'danger',
PRIMARY: 'primary',
WARNING: 'warning',
};
});
/**
* @typedef {keyof typeof Button.TYPES} VARIANT_KEYS
* @typedef {typeof Button.TYPES[keyof typeof Button.TYPES]} TYPE
* @typedef {typeof Button.TYPES[keyof typeof Button.TYPES]} Type
*/
Button.TYPES = {
Button.TYPES = /** @type {const} */ ({
BUTTON: 'button',
RESET: 'reset',
SUBMIT: 'submit',
};
});
/**
* @typedef {keyof typeof Button.VARIANTS} VARIANT_KEYS
* @typedef {typeof Button.VARIANTS[VARIANT_KEYS]} VARIANT
* @typedef {typeof Button.VARIANTS[keyof typeof Button.VARIANTS]} Variant
*/
Button.VARIANTS = {
Button.VARIANTS = /** @type {const} */ ({
CTA: 'cta',
GLYPH: 'glyph',
};
Button.defaultProps = {
appearance: Button.APPEARANCES.PRIMARY,
fluid: false,
type: Button.TYPES.BUTTON,
variant: Button.VARIANTS.CTA,
};
Button.propTypes = {
appearance: PropTypes.oneOf(Object.values(Button.APPEARANCES)),
fluid: PropTypes.bool,
type: PropTypes.oneOf(Object.values(Button.TYPES)),
variant: PropTypes.oneOf(Object.values(Button.VARIANTS)),
};
});

/**
* @param {import('../Group/Group.jsx').GroupProps & React.HTMLAttributes<HTMLElement>} props
*/
Button.Group = ({ className, ...props }) => (
<Group className={clsx(className, styles.ButtonGroup)} {...props} />
);
37 changes: 37 additions & 0 deletions lib/react/Field/Field.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
declare function Field({ appearance, arrangement, as: Tag, className, fluid, id, label, name, onBlur, onChange, options, readOnly, required, type, variant, ...others }: FieldProps & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'>): JSX.Element;
declare namespace Field {
let displayName: "Form5Field";
namespace ARRANGEMENTS {
let COMPACT: "compact";
let INLINE: "inline";
let STACKED: "stacked";
let STAND_ALONE: "stand-alone";
}
namespace VARIANTS {
let CTA: "cta";
let GLYPH: "glyph";
let TOGGLE: "toggle";
}
}
export default Field;
export { styles as inputClasses };
export type React = typeof import("react");
export type FieldProps = {
appearance?: import("../Button/Button.jsx").Appearance | undefined;
arrangement?: Arrangement | undefined;
as?: import("react").ElementType<any> | undefined;
fluid?: boolean | undefined;
label: React.ReactNode;
name: HTMLInputElement['name'];
onBlur?: ((event: React.FocusEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => void) | undefined;
onChange?: ((change: {
id: string;
name: string;
value: boolean | number | string;
}, event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => void) | undefined;
options?: Record<string, import("react").ReactNode> | undefined;
variant?: Variant | undefined;
};
export type Arrangement = (typeof Field.ARRANGEMENTS)[keyof typeof Field.ARRANGEMENTS];
export type Variant = (typeof Field.VARIANTS)[keyof typeof Field.VARIANTS];
//# sourceMappingURL=Field.d.ts.map
1 change: 1 addition & 0 deletions lib/react/Field/Field.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

72 changes: 30 additions & 42 deletions lib/react/Field/Field.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { clsx } from 'clsx';
import _isEmpty from 'lodash-es/isEmpty.js';
import _map from 'lodash-es/map.js';
import PropTypes from 'prop-types';
import { useState } from 'react';

import { useInteractiveStates } from '../useInteractiveStates.js';
Expand All @@ -15,25 +14,25 @@ import styles from './Field.module.css';
export { styles as inputClasses };

/**
*
* @param {object} props
* @param {import('../Button/Button.jsx').APPEARANCE} props.appearance
* @param {ARRANGEMENT} props.arrangement
* @param {import('react').ElementType} props.as The element to render.
* @param {string} props.className
* @param {boolean} props.fluid Whether the field should fill its container.
* @param {HTMLElement['id']} props.id
* @param {HTMLLabelElement['textContent']} props.label
* @param {HTMLInputElement['name']} props.name
* @param {(event: import('react').FocusEvent<HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement>)} props.onBlur
* @param {(change: { id: string, name: string, value: boolean | number | string }, event: import('react').ChangeEvent<HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement>)} props.onChange
* @param {Array<{[key: HTMLOptionElement['value']]: HTMLOptionElement['textContent'] }>} props.options
* @param {HTMLInputElement['readOnly']} props.readOnly
* @param {HTMLInputElement['required']} props.required
* @param {HTMLInputElement['type']} props.type
* @param {typeof Field.VARIANTS} props.variant
* @param {import('react').HTMLProps<HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement>} props.others
* @returns {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement}
* @typedef {import('react')} React
*/

/**
* @typedef {object} FieldProps
* @property {import('../Button/Button.jsx').Appearance} [FieldProps.appearance=Button.APPEARANCES.PRIMARY]
* @property {Arrangement} [FieldProps.arrangement=Field.ARRANGEMENTS.INLINE]
* @property {React.ElementType} [FieldProps.as='input'] The element to render.
* @property {boolean} [FieldProps.fluid] Whether the field should fill its container.
* @property {React.ReactNode} FieldProps.label
* @property {HTMLInputElement['name']} FieldProps.name
* @property {(event: React.FocusEvent<HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement>) => void} [FieldProps.onBlur]
* @property {(change: { id: string, name: string, value: boolean | number | string }, event: React.ChangeEvent<HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement>) => void} [FieldProps.onChange]
* @property {Record<HTMLOptionElement['value'], React.ReactNode>} [FieldProps.options]
* @property {Variant} [FieldProps.variant]
*/

/**
* @param {FieldProps & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'>} props
*/
export default function Field({
appearance = Button.APPEARANCES.PRIMARY,
Expand All @@ -44,8 +43,8 @@ export default function Field({
id,
label,
name,
onBlur = () => {},
onChange = () => {},
onBlur,
onChange,
options,
readOnly,
required,
Expand Down Expand Up @@ -74,7 +73,7 @@ export default function Field({

is.onBlur(e);

onBlur(e);
onBlur?.(e);

if (e.target.checkValidity()) setError('');
};
Expand Down Expand Up @@ -102,7 +101,7 @@ export default function Field({

if (type === 'checkbox') value = checked;

onChange({
onChange?.({
id,
name,
value: options?.[value] ?? value,
Expand Down Expand Up @@ -216,36 +215,25 @@ export default function Field({
);
};

Field.displayName = 'Form5Field';
Field.displayName = /** @type {const} */ ('Form5Field');

/**
* @typedef {typeof Field.ARRANGEMENTS[keyof typeof Field.ARRANGEMENTS]} ARRANGEMENT
* @typedef {typeof Field.ARRANGEMENTS[keyof typeof Field.ARRANGEMENTS]} Arrangement
*/
Field.ARRANGEMENTS = {
Field.ARRANGEMENTS = /** @type {const} */ ({
COMPACT: 'compact',
INLINE: 'inline',
STACKED: 'stacked',
STAND_ALONE: 'stand-alone',
};
});
/**
* @typedef {typeof Field.VARIANTS[keyof typeof Field.VARIANTS]} VARIANT
* @typedef {typeof Field.VARIANTS[keyof typeof Field.VARIANTS]} Variant
*/
Field.VARIANTS = {
Field.VARIANTS = /** @type {const} */ ({
CTA: Button.VARIANTS.CTA,
GLYPH: Button.VARIANTS.GLYPH,
TOGGLE: 'toggle',
};
Field.propTypes = {
arrangement: PropTypes.oneOf(Object.values(Field.ARRANGEMENTS)),
as: PropTypes.elementType,
fluid: PropTypes.bool,
label: PropTypes.node,
name: PropTypes.string.isRequired,
onBlur: PropTypes.func,
onChange: PropTypes.func,
options: PropTypes.object,
variant: PropTypes.oneOf(Object.values(Field.VARIANTS)),
};
});

const dtTypes = new Set([
'date',
Expand Down
Loading

0 comments on commit f254352

Please sign in to comment.