, 'size' | 'style' | 'label' | 'multiple'> {
+/**
+ * Props of the {@link MenuMultiSelect} component.
+ */
+export interface MenuMultiSelectProps extends Omit
, 'size' | 'label' | 'multiple'> {
/**
* Short textual description indicating the nature of the component's value.
*/
@@ -88,6 +94,7 @@ export const MenuMultiSelect = React.forwardRef<
className,
startingHeight = '15rem',
id: idProp,
+ style,
...etcProps
},
forwardedRef,
@@ -107,6 +114,7 @@ export const MenuMultiSelect = React.forwardRef<
className,
)}
data-testid="base"
+ style={style}
>
{label && (
<>
diff --git a/categories/multichoice/react/src/components/TagInput/index.tsx b/categories/multichoice/react/src/components/TagInput/index.tsx
index 748984a..2cf598c 100644
--- a/categories/multichoice/react/src/components/TagInput/index.tsx
+++ b/categories/multichoice/react/src/components/TagInput/index.tsx
@@ -17,12 +17,24 @@ const TAG_INPUT_VALUE_SEPARATOR_MAP = {
'semicolon': ';',
} as const;
+/**
+ * Separator for splitting the input value into multiple tags.
+ */
export type TagInputSeparator = keyof typeof TAG_INPUT_SEPARATOR_MAP
+/**
+ * Derived HTML element of the {@link TagInput} component.
+ */
export type TagInputDerivedElement = HTMLTextAreaElement | HTMLInputElement;
+/**
+ * Proxied HTML element of the {@link TagInput} component.
+ */
export type TagInputProxiedElement = HTMLTextAreaElement & HTMLInputElement;
+/**
+ * Props of the {@link TagsInput} component.
+ */
export interface TagInputProps extends Omit, 'size' | 'type' | 'style' | 'label' | 'list'> {
/**
* Short textual description indicating the nature of the component's value.
diff --git a/categories/multichoice/react/src/components/ToggleButton/index.tsx b/categories/multichoice/react/src/components/ToggleButton/index.tsx
index ed8b42c..c68ed6b 100644
--- a/categories/multichoice/react/src/components/ToggleButton/index.tsx
+++ b/categories/multichoice/react/src/components/ToggleButton/index.tsx
@@ -4,15 +4,42 @@ import { Button } from '@tesseract-design/web-base';
import plugin from 'tailwindcss/plugin';
import { useFallbackId } from '@modal-sh/react-utils';
+/**
+ * Derived HTML element of the {@link ToggleButton} component.
+ */
export type ToggleButtonDerivedElement = HTMLInputElement;
+/**
+ * Props of the {@link ToggleButton} component.
+ */
export interface ToggleButtonProps extends Omit, 'type' | 'size'> {
+ /**
+ * Should the component occupy the whole width of its parent?
+ */
block?: boolean;
+ /**
+ * Should the component's content use minimal space?
+ */
compact?: boolean;
+ /**
+ * Size of the component.
+ */
size?: Button.Size;
+ /**
+ * Complementary content of the component.
+ */
subtext?: React.ReactNode;
+ /**
+ * Short complementary content displayed at the edge of the component.
+ */
badge?: React.ReactNode;
+ /**
+ * Variant of the component.
+ */
variant?: Button.Variant;
+ /**
+ * Is the component in an indeterminate state?
+ */
indeterminate?: boolean;
}
@@ -35,6 +62,9 @@ export const toggleButtonPlugin = plugin(({ addComponents, }) => {
});
});
+/**
+ * Component for toggling a Boolean value.
+ */
export const ToggleButton = React.forwardRef((
{
children,
@@ -47,6 +77,7 @@ export const ToggleButton = React.forwardRef
, 'type' | 'size'> {
+ /**
+ * Should the component occupy the whole width of its parent?
+ */
block?: boolean;
+ /**
+ * Complementary content of the component.
+ */
subtext?: React.ReactNode;
+ /**
+ * Is the component in an indeterminate state?
+ */
indeterminate?: boolean;
+ /**
+ * Label to display when the component is checked.
+ */
checkedLabel?: React.ReactNode;
+ /**
+ * Label to display when the component is unchecked.
+ */
uncheckedLabel?: React.ReactNode;
}
@@ -134,6 +155,9 @@ export const toggleSwitchPlugin = plugin(({ addComponents }) => {
});
});
+/**
+ * Component for toggling a Boolean value in an appearance of a toggle switch.
+ */
export const ToggleSwitch = React.forwardRef((
{
uncheckedLabel,
diff --git a/categories/multichoice/react/src/components/ToggleTickBox/index.tsx b/categories/multichoice/react/src/components/ToggleTickBox/index.tsx
index 706f544..b353d73 100644
--- a/categories/multichoice/react/src/components/ToggleTickBox/index.tsx
+++ b/categories/multichoice/react/src/components/ToggleTickBox/index.tsx
@@ -3,11 +3,26 @@ import clsx from 'clsx';
import plugin from 'tailwindcss/plugin';
import { useFallbackId } from '@modal-sh/react-utils';
+/**
+ * Derived HTML element of the {@link ToggleTickBox} component.
+ */
export type ToggleTickBoxDerivedElement = HTMLInputElement;
+/**
+ * Props of the {@link ToggleTickBox} component.
+ */
export interface ToggleTickBoxProps extends Omit, 'type' | 'size'> {
+ /**
+ * Should the component occupy the whole width of its parent?
+ */
block?: boolean;
+ /**
+ * Complementary content of the component.
+ */
subtext?: React.ReactNode;
+ /**
+ * Is the component in an indeterminate state?
+ */
indeterminate?: boolean;
}
@@ -30,6 +45,9 @@ export const toggleTickBoxPlugin = plugin(({ addComponents, }) => {
});
});
+/**
+ * Component for toggling a Boolean value with the appearance of a tick box.
+ */
export const ToggleTickBox = React.forwardRef((
{
children,
diff --git a/categories/navigation/react/src/components/LinkButton/index.tsx b/categories/navigation/react/src/components/LinkButton/index.tsx
index da3014d..3205cda 100644
--- a/categories/navigation/react/src/components/LinkButton/index.tsx
+++ b/categories/navigation/react/src/components/LinkButton/index.tsx
@@ -2,17 +2,50 @@ import * as React from 'react';
import clsx from 'clsx';
import { Button } from '@tesseract-design/web-base';
+/**
+ * Derived HTML element of the {@link LinkButton} component.
+ */
export type LinkButtonDerivedElement = HTMLAnchorElement;
+/**
+ * Props of the {@link LinkButton} component.
+ */
export interface LinkButtonProps extends Omit, 'size'> {
+ /**
+ * Should the component occupy the whole width of its parent?
+ */
block?: boolean;
+ /**
+ * Variant of the component.
+ */
variant?: Button.Variant;
+ /**
+ * Complementary content of the component.
+ */
subtext?: React.ReactNode;
+ /**
+ * Short complementary content displayed at the edge of the component.
+ */
badge?: React.ReactNode;
+ /**
+ * Is this component part of a menu?
+ */
menuItem?: boolean;
+ /**
+ * Size of the component.
+ */
size?: Button.Size;
+ /**
+ * Should the component's content use minimal space?
+ */
compact?: boolean;
+ /**
+ * Component to use in rendering.
+ */
component?: React.ElementType;
+ /**
+ * Is the component unable to receive activation?
+ */
disabled?: boolean;
}
@@ -30,6 +63,7 @@ export const LinkButton = React.forwardRef
, 'size' | 'type' | 'label'> {
/**
* Short textual description indicating the nature of the component's value.
@@ -39,8 +45,17 @@ export interface NumberSpinnerProps extends Omit {
});
/**
- * Component for inputting numeric values.
+ * Component for inputting discrete numeric values.
*/
export const NumberSpinner = React.forwardRef((
{
@@ -96,63 +111,131 @@ export const NumberSpinner = React.forwardRef(null);
const ref = forwardedRef ?? defaultRef;
- const intervalRef = React.useRef<
- number | undefined
- >() as React.MutableRefObject;
+ const intervalRef = React.useRef();
+ const clickYRef = React.useRef();
+ const spinnerYRef = React.useRef();
+ const spinEventSource = React.useRef<'mouse' | 'keyboard'>();
+ const [displayStep, setDisplayStep] = React.useState();
- const handleStepUp = React.useCallback(() => {
- const { current } = typeof ref === 'object' ? ref : defaultRef;
- if (current) {
- const theStep = current.step ?? 'any';
+ const performStepMouse = (
+ input: NumberSpinnerDerivedElement,
+ theStepUpMode?: boolean,
+ ) => {
+ if (typeof theStepUpMode !== 'boolean') {
+ return;
+ }
+ const current = input;
+ const theStep = current.step ?? 'any';
+ current.step = theStep === 'any' ? '1' : theStep;
+ if (theStepUpMode) {
+ current.stepUp();
+ } else {
+ current.stepDown();
+ }
+ current.step = theStep;
+ current.focus();
+ };
+
+ const windowMouseMove = (e: MouseEvent) => {
+ if (spinEventSource.current !== 'mouse') {
+ return;
+ }
+ clickYRef.current = e.pageY;
+ };
- const doStepUp = () => {
- current.step = theStep === 'any' ? '1' : theStep;
- current.stepUp();
- current.step = theStep;
- current.focus();
- };
+ const checkMouseStepUpMode = () => {
+ if (typeof clickYRef.current !== 'number' || typeof spinnerYRef.current !== 'number') {
+ return undefined;
+ }
+ return clickYRef.current < spinnerYRef.current;
+ };
+ const doStepMouse: React.MouseEventHandler = (e) => {
+ if (spinEventSource.current === 'keyboard') {
+ return;
+ }
+ const { current } = typeof ref === 'object' ? ref : defaultRef;
+ if (!current) {
+ return;
+ }
+ spinEventSource.current = 'mouse';
+ const { top, bottom } = e.currentTarget.getBoundingClientRect();
+ const { pageY } = e;
+ spinnerYRef.current = top + ((bottom - top) / 2);
+ clickYRef.current = pageY;
+ window.addEventListener('mousemove', windowMouseMove);
+ setTimeout(() => {
clearInterval(intervalRef.current);
- doStepUp();
+ const stepUpMode = checkMouseStepUpMode();
+ setDisplayStep(stepUpMode);
+ performStepMouse(current, stepUpMode);
intervalRef.current = window.setTimeout(() => {
- doStepUp();
+ const stepUpMode = checkMouseStepUpMode();
+ setDisplayStep(stepUpMode);
+ performStepMouse(current, stepUpMode);
intervalRef.current = window.setInterval(() => {
- doStepUp();
+ const stepUpMode = checkMouseStepUpMode();
+ setDisplayStep(stepUpMode);
+ performStepMouse(current, stepUpMode);
}, stepInterval);
}, initialStepDelay);
- }
- }, [ref, defaultRef, intervalRef, stepInterval, initialStepDelay]);
-
- const handleStepDown = React.useCallback(() => {
- const { current } = typeof ref === 'object' ? ref : defaultRef;
- if (current) {
- const theStep = current.step ?? 'any';
- const doStepDown = () => {
- current.step = theStep === 'any' ? '1' : theStep;
- current.stepDown();
- current.step = theStep;
- current.focus();
- };
+ });
+ };
+ const doStepKeyboard: React.KeyboardEventHandler = (e) => {
+ if (spinEventSource.current === 'mouse') {
+ return;
+ }
+ if (!(e.code === 'ArrowUp' || e.code === 'ArrowDown')) {
+ return;
+ }
+ e.preventDefault();
+ spinEventSource.current = 'keyboard';
+ const current = e.currentTarget;
+ const theStepUpMode = e.code === 'ArrowUp';
+ setDisplayStep(theStepUpMode);
+ setTimeout(() => {
clearInterval(intervalRef.current);
- doStepDown();
+ performStepMouse(current, theStepUpMode);
intervalRef.current = window.setTimeout(() => {
- doStepDown();
+ performStepMouse(current, theStepUpMode);
intervalRef.current = window.setInterval(() => {
- doStepDown();
+ performStepMouse(current, theStepUpMode);
}, stepInterval);
}, initialStepDelay);
- }
- }, [ref, defaultRef, intervalRef, stepInterval, initialStepDelay]);
+ });
+ };
React.useEffect(() => {
- const stopStep = () => {
+ const stopStepMouse = () => {
+ if (spinEventSource.current === 'keyboard') {
+ return;
+ }
clearInterval(intervalRef.current);
+ clickYRef.current = undefined;
+ spinnerYRef.current = undefined;
+ window.removeEventListener('mousemove', windowMouseMove);
+ spinEventSource.current = undefined;
+ setDisplayStep(undefined);
};
- window.addEventListener('mouseup', stopStep, { capture: true });
+ const stopStepKeyboard = (e: KeyboardEvent) => {
+ if (spinEventSource.current === 'mouse') {
+ return;
+ }
+ if (!(e.code === 'ArrowUp' || e.code === 'ArrowDown')) {
+ return;
+ }
+ clearInterval(intervalRef.current);
+ spinEventSource.current = undefined;
+ setDisplayStep(undefined);
+ };
+
+ window.addEventListener('mouseup', stopStepMouse, { capture: true });
+ window.addEventListener('keyup', stopStepKeyboard, { capture: true });
return () => {
- window.removeEventListener('mouseup', stopStep, { capture: true });
+ window.removeEventListener('mouseup', stopStepMouse, { capture: true });
+ window.addEventListener('keyup', stopStepKeyboard, { capture: true });
};
}, []);
@@ -209,6 +292,7 @@ export const NumberSpinner = React.forwardRef
)}
{indicator && (
-
-
-
-
+
+ Step Down
+
+
+
)}
{border && (
{
* sliders and vv.
*/
+/**
+ * Orientation of the {@link Slider} component.
+ */
export type SliderOrientation = 'horizontal' | 'vertical';
+/**
+ * Derived HTML element of the {@link Slider} component.
+ */
export type SliderDerivedElement = HTMLInputElement;
+/**
+ * Props of the {@link Slider} component.
+ */
export interface SliderProps extends Omit, 'type'> {
+ /**
+ * Orientation of the component.
+ */
orient?: SliderOrientation;
+ /**
+ * Options of the component.
+ */
children?: React.ReactNode;
+ /**
+ * Length of the component.
+ */
length?: React.CSSProperties['width'];
}
@@ -93,7 +111,7 @@ export const sliderPlugin = plugin(({ addComponents }) => {
'aspect-ratio': '1 / 1',
'z-index': '1',
position: 'relative',
- 'box-shadow': '-100000.5em 0 0 100000em rgb(var(--color-primary) / 50%)',
+ 'box-shadow': 'calc(-200001em / 2) 0 0 100000em rgb(var(--color-primary) / 50%)',
},
'& > input:focus::-webkit-slider-container': {
@@ -105,11 +123,11 @@ export const sliderPlugin = plugin(({ addComponents }) => {
},
'& > input:focus::-webkit-slider-thumb': {
- 'box-shadow': '-100000.5em 0 0 100000em rgb(var(--color-secondary) / 50%)',
+ 'box-shadow': 'calc(-200001em / 2) 0 0 100000em rgb(var(--color-secondary) / 50%)',
},
'& > input:active::-webkit-slider-thumb': {
- 'box-shadow': '-100000.5em 0 0 100000em rgb(var(--color-tertiary) / 50%)',
+ 'box-shadow': 'calc(-200001em / 2) 0 0 100000em rgb(var(--color-tertiary) / 50%)',
},
'&[data-orient="horizontal"]': {
@@ -155,15 +173,15 @@ export const sliderPlugin = plugin(({ addComponents }) => {
'aspect-ratio': '1 / 1',
'z-index': '1',
position: 'relative',
- 'box-shadow': '-100000.5em 0 0 100000em rgb(var(--color-primary) / 50%)',
+ 'box-shadow': 'calc(-200001em / 2) 0 0 100000em rgb(var(--color-primary) / 50%)',
},
'& > input:focus::-moz-range-thumb': {
- 'box-shadow': '-100000.5em 0 0 100000em rgb(var(--color-secondary) / 50%)',
+ 'box-shadow': 'calc(-200001em / 2) 0 0 100000em rgb(var(--color-secondary) / 50%)',
},
'& > input:active::-moz-range-thumb': {
- 'box-shadow': '-100000.5em 0 0 100000em rgb(var(--color-tertiary) / 50%)',
+ 'box-shadow': 'calc(-200001em / 2) 0 0 100000em rgb(var(--color-tertiary) / 50%)',
},
'& > input[orient="vertical"]': {
@@ -179,15 +197,15 @@ export const sliderPlugin = plugin(({ addComponents }) => {
'& > input[orient="vertical"]::-moz-range-thumb': {
width: '100%',
height: '1em',
- 'box-shadow': '0 100000.5em 0 100000em rgb(var(--color-primary) / 50%)',
+ 'box-shadow': '0 calc(200001em / 2) 0 100000em rgb(var(--color-primary) / 50%)',
},
'& > input[orient="vertical"]:focus::-moz-range-thumb': {
- 'box-shadow': '0 100000.5em 0 100000em rgb(var(--color-secondary) / 50%)',
+ 'box-shadow': '0 calc(200001em / 2) 0 100000em rgb(var(--color-secondary) / 50%)',
},
'& > input[orient="vertical"]:active::-moz-range-thumb': {
- 'box-shadow': '0 100000.5em 0 100000em rgb(var(--color-tertiary) / 50%)',
+ 'box-shadow': '0 calc(200001em / 2) 0 100000em rgb(var(--color-tertiary) / 50%)',
},
'&[data-chrome] > input + *': {
@@ -208,6 +226,9 @@ export const sliderPlugin = plugin(({ addComponents }) => {
});
});
+/**
+ * Component for inputting continuous numeric values.
+ */
export const Slider = React.forwardRef((
{
className,
diff --git a/categories/temporal/react/src/components/DateDropdown/index.tsx b/categories/temporal/react/src/components/DateDropdown/index.tsx
index 06b25a4..68522c4 100644
--- a/categories/temporal/react/src/components/DateDropdown/index.tsx
+++ b/categories/temporal/react/src/components/DateDropdown/index.tsx
@@ -4,8 +4,14 @@ import clsx from 'clsx';
import plugin from 'tailwindcss/plugin';
import { useFallbackId } from '@modal-sh/react-utils';
+/**
+ * Derived HTML element of the {@link DateDropdown} component.
+ */
export type DateDropdownDerivedElement = HTMLInputElement;
+/**
+ * Props of the {@link DateDropdown} component.
+ */
export interface DateDropdownProps extends Omit, 'size' | 'type' | 'label' | 'pattern'> {
/**
* Short textual description indicating the nature of the component's value.
@@ -68,7 +74,7 @@ export const dateDropdownPlugin = plugin(({ addComponents }) => {
});
/**
- * Component for inputting textual values.
+ * Component for inputting date values.
*/
export const DateDropdown = React.forwardRef<
DateDropdownDerivedElement,
diff --git a/categories/temporal/react/src/components/TimeSpinner/index.tsx b/categories/temporal/react/src/components/TimeSpinner/index.tsx
index bc1bb41..c18f195 100644
--- a/categories/temporal/react/src/components/TimeSpinner/index.tsx
+++ b/categories/temporal/react/src/components/TimeSpinner/index.tsx
@@ -4,6 +4,9 @@ import clsx from 'clsx';
import plugin from 'tailwindcss/plugin';
import { useFallbackId } from '@modal-sh/react-utils';
+/**
+ * Derived HTML element of the {@link TimeSpinner} component.
+ */
export type TimeSpinnerDerivedElement = HTMLInputElement;
type Digit = (0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9);
@@ -16,6 +19,9 @@ type StepHhMmSs = `${StepHhMm}:${Segment}`;
type Step = StepHhMm | StepHhMmSs;
+/**
+ * Props of the {@link TimeSpinner} component.
+ */
export interface TimeSpinnerProps extends Omit, 'size' | 'type' | 'label' | 'step' | 'pattern'> {
/**
* Short textual description indicating the nature of the component's value.
@@ -82,7 +88,7 @@ export const timeSpinnerPlugin = plugin(({ addComponents }) => {
});
/**
- * Component for inputting textual values.
+ * Component for inputting time values.
*/
export const TimeSpinner = React.forwardRef<
TimeSpinnerDerivedElement,
diff --git a/showcases/web-kitchensink-reactnext/src/pages/examples/registration-form/index.tsx b/showcases/web-kitchensink-reactnext/src/pages/examples/registration-form/index.tsx
index 0320508..19977c3 100644
--- a/showcases/web-kitchensink-reactnext/src/pages/examples/registration-form/index.tsx
+++ b/showcases/web-kitchensink-reactnext/src/pages/examples/registration-form/index.tsx
@@ -102,6 +102,7 @@ const RegistrationFormPage: NextPage = () => {
label="Password"
name="password"
onChange={handleChange}
+ enhanced
/>
@@ -111,6 +112,7 @@ const RegistrationFormPage: NextPage = () => {
label="Confirm Password"
name="confirmPassword"
onChange={handleChange}
+ enhanced
/>