Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat(react-spinbutton): add useSpinButtonBase_unstable hook",
"packageName": "@fluentui/react-spinbutton",
"email": "dmytrokirpa@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,25 @@

import type { ComponentProps } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import type { DistributiveOmit } from '@fluentui/react-utilities';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import type { JSXElement } from '@fluentui/react-utilities';
import * as React_2 from 'react';
import type { Slot } from '@fluentui/react-utilities';
import { SlotClassNames } from '@fluentui/react-utilities';

// @public
export const renderSpinButton_unstable: (state: SpinButtonState) => JSXElement;
export const renderSpinButton_unstable: (state: SpinButtonBaseState) => JSXElement;

// @public
export const SpinButton: ForwardRefComponent<SpinButtonProps>;

// @public (undocumented)
export type SpinButtonBaseProps = DistributiveOmit<SpinButtonProps, 'appearance' | 'size'>;

// @public (undocumented)
export type SpinButtonBaseState = DistributiveOmit<SpinButtonState, 'appearance' | 'size'>;

// @public (undocumented)
export type SpinButtonBounds = 'none' | 'min' | 'max' | 'both';

Expand Down Expand Up @@ -68,6 +75,9 @@ export type SpinButtonState = ComponentState<SpinButtonSlots> & Required<Pick<Sp
// @public
export const useSpinButton_unstable: (props: SpinButtonProps, ref: React_2.Ref<HTMLInputElement>) => SpinButtonState;

// @public
export const useSpinButtonBase_unstable: (props: SpinButtonBaseProps, ref: React_2.Ref<HTMLInputElement>) => SpinButtonBaseState;

// @public
export const useSpinButtonStyles_unstable: (state: SpinButtonState) => SpinButtonState;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export type {
SpinButtonBaseProps,
SpinButtonBaseState,
SpinButtonBounds,
SpinButtonChangeEvent,
SpinButtonOnChangeData,
Expand All @@ -12,5 +14,6 @@ export {
renderSpinButton_unstable,
spinButtonClassNames,
useSpinButtonStyles_unstable,
useSpinButtonBase_unstable,
useSpinButton_unstable,
} from './components/SpinButton/index';
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import type { ComponentProps, ComponentState, DistributiveOmit, Slot } from '@fluentui/react-utilities';
import * as React from 'react';

export type SpinButtonSlots = {
Expand Down Expand Up @@ -157,3 +157,6 @@ export type SpinButtonOnChangeData = {

export type SpinButtonSpinState = 'rest' | 'up' | 'down';
export type SpinButtonBounds = 'none' | 'min' | 'max' | 'both';

export type SpinButtonBaseProps = DistributiveOmit<SpinButtonProps, 'appearance' | 'size'>;
export type SpinButtonBaseState = DistributiveOmit<SpinButtonState, 'appearance' | 'size'>;
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export { SpinButton } from './SpinButton';
export type {
SpinButtonBaseProps,
SpinButtonBaseState,
SpinButtonBounds,
SpinButtonChangeEvent,
SpinButtonOnChangeData,
Expand All @@ -9,5 +11,5 @@ export type {
SpinButtonState,
} from './SpinButton.types';
export { renderSpinButton_unstable } from './renderSpinButton';
export { useSpinButton_unstable } from './useSpinButton';
export { useSpinButtonBase_unstable, useSpinButton_unstable } from './useSpinButton';
export { spinButtonClassNames, useSpinButtonStyles_unstable } from './useSpinButtonStyles.styles';
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@

import { assertSlots } from '@fluentui/react-utilities';
import type { JSXElement } from '@fluentui/react-utilities';
import type { SpinButtonState, SpinButtonSlots } from './SpinButton.types';
import type { SpinButtonBaseState, SpinButtonSlots } from './SpinButton.types';

/**
* Render the final JSX of SpinButton
*/
export const renderSpinButton_unstable = (state: SpinButtonState): JSXElement => {
export const renderSpinButton_unstable = (state: SpinButtonBaseState): JSXElement => {
assertSlots<SpinButtonSlots>(state);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
} from '@fluentui/react-utilities';
import { ArrowUp, ArrowDown, End, Enter, Escape, Home, PageDown, PageUp } from '@fluentui/keyboard-keys';
import {
SpinButtonBaseProps,
SpinButtonBaseState,
SpinButtonProps,
SpinButtonState,
SpinButtonSpinState,
Expand Down Expand Up @@ -41,26 +43,21 @@ const MAX_SPIN_TIME_MS = 1000;
const lerp = (start: number, end: number, percent: number): number => start + (end - start) * percent;

/**
* Create the state required to render SpinButton.
*
* The returned state can be modified with hooks such as useSpinButtonStyles_unstable,
* before being passed to renderSpinButton_unstable.
* Create the base state required to render SpinButton without design-specific props.
*
* @param props - props from this instance of SpinButton
* @param props - props from this instance of SpinButton (without appearance/size)
* @param ref - reference to root HTMLElement of SpinButton
*/
export const useSpinButton_unstable = (props: SpinButtonProps, ref: React.Ref<HTMLInputElement>): SpinButtonState => {
// Merge props from surrounding <Field>, if any
props = useFieldControlProps_unstable(props, { supportsLabelFor: true, supportsRequired: true });

export const useSpinButtonBase_unstable = (
props: SpinButtonBaseProps,
ref: React.Ref<HTMLInputElement>,
): SpinButtonBaseState => {
const nativeProps = getPartitionedNativeProps({
props,
primarySlotTagName: 'input',
excludedPropNames: ['defaultValue', 'max', 'min', 'onChange', 'size', 'value'],
excludedPropNames: ['defaultValue', 'max', 'min', 'onChange', 'value'],
});

const overrides = useOverrides();

const {
value,
displayValue,
Expand All @@ -71,8 +68,6 @@ export const useSpinButton_unstable = (props: SpinButtonProps, ref: React.Ref<HT
stepPage = 1,
precision: precisionFromProps,
onChange,
size = 'medium',
appearance = overrides.inputDefaultAppearance ?? 'outline',
root,
input,
incrementButton,
Expand Down Expand Up @@ -159,6 +154,7 @@ export const useSpinButton_unstable = (props: SpinButtonProps, ref: React.Ref<HT
if (inputRef.current) {
// we need to set this here using the IDL attribute directly, because otherwise the timing of the ARIA value update
// is not in sync with the user-entered native input value, and some screen readers end up reading the wrong value.
// eslint-disable-next-line react-compiler/react-compiler
inputRef.current.ariaValueNow = newValue;
}
};
Expand Down Expand Up @@ -281,9 +277,7 @@ export const useSpinButton_unstable = (props: SpinButtonProps, ref: React.Ref<HT
}
}

const state: SpinButtonState = {
size,
appearance,
const state: SpinButtonBaseState = {
spinState: keyboardSpinState,
atBound: internalState.current.atBound,

Expand All @@ -301,7 +295,6 @@ export const useSpinButton_unstable = (props: SpinButtonProps, ref: React.Ref<HT
defaultProps: {
autoComplete: 'off',
role: 'spinbutton',
appearance,
type: 'text',
...nativeProps.primary,
},
Expand All @@ -310,7 +303,6 @@ export const useSpinButton_unstable = (props: SpinButtonProps, ref: React.Ref<HT
incrementButton: slot.always(incrementButton, {
defaultProps: {
tabIndex: -1,
children: <ChevronUp16Regular />,
disabled:
nativeProps.primary.readOnly ||
nativeProps.primary.disabled ||
Expand All @@ -324,7 +316,6 @@ export const useSpinButton_unstable = (props: SpinButtonProps, ref: React.Ref<HT
decrementButton: slot.always(decrementButton, {
defaultProps: {
tabIndex: -1,
children: <ChevronDown16Regular />,
disabled:
nativeProps.primary.readOnly ||
nativeProps.primary.disabled ||
Expand Down Expand Up @@ -359,3 +350,28 @@ export const useSpinButton_unstable = (props: SpinButtonProps, ref: React.Ref<HT

return state;
};

/**
* Create the state required to render SpinButton.
*
* The returned state can be modified with hooks such as useSpinButtonStyles_unstable,
* before being passed to renderSpinButton_unstable.
*
* @param props - props from this instance of SpinButton
* @param ref - reference to root HTMLElement of SpinButton
*/
export const useSpinButton_unstable = (props: SpinButtonProps, ref: React.Ref<HTMLInputElement>): SpinButtonState => {
// Merge props from surrounding <Field>, if any
props = useFieldControlProps_unstable(props, { supportsLabelFor: true, supportsRequired: true });

const overrides = useOverrides();

const { appearance = overrides.inputDefaultAppearance ?? 'outline', size = 'medium', ...baseProps } = props;

const state = useSpinButtonBase_unstable(baseProps, ref);

state.incrementButton.children ??= <ChevronUp16Regular />;
state.decrementButton.children ??= <ChevronDown16Regular />;

return { ...state, appearance, size };
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ export {
renderSpinButton_unstable,
spinButtonClassNames,
useSpinButtonStyles_unstable,
useSpinButtonBase_unstable,
useSpinButton_unstable,
} from './SpinButton';
export type {
SpinButtonBaseProps,
SpinButtonBaseState,
SpinButtonOnChangeData,
SpinButtonChangeEvent,
SpinButtonProps,
Expand Down
Loading