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
6 changes: 5 additions & 1 deletion packages/@react-spectrum/s2/src/CenterBaseline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@

import {css} from '../style/style-macro' with {type: 'macro'};
import {CSSProperties, ReactNode} from 'react';
import {DOMAttributes} from '@react-types/shared';
import {filterDOMProps} from 'react-aria/filterDOMProps';
import {mergeStyles} from '../style/runtime';
import {style} from '../style' with {type: 'macro'};
import {StyleString} from '../style/types';

interface CenterBaselineProps {
interface CenterBaselineProps extends DOMAttributes {
style?: CSSProperties,
styles?: StyleString,
children: ReactNode,
Expand All @@ -29,8 +31,10 @@ const styles = style({
});

export function CenterBaseline(props: CenterBaselineProps): ReactNode {
let domProps = filterDOMProps(props);
return (
<div
{...domProps}
slot={props.slot}
style={props.style}
className={mergeStyles(styles, props.styles) + ' ' + centerBaselineBefore}>
Expand Down
39 changes: 33 additions & 6 deletions packages/@react-spectrum/s2/src/ColorField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,20 @@

import {ColorField as AriaColorField, ColorFieldProps as AriaColorFieldProps} from 'react-aria-components/ColorField';

import {ContextValue} from 'react-aria-components/slots';
import {createContext, forwardRef, Ref, useContext, useImperativeHandle, useRef} from 'react';
import {CenterBaseline} from './CenterBaseline';
import {ContextValue, Provider} from 'react-aria-components/slots';
import {createContext, forwardRef, ReactNode, Ref, useContext, useImperativeHandle, useRef} from 'react';
import {createFocusableRef} from './useDOMRef';
import {field, getAllowedOverrides, StyleProps} from './style-utils' with {type: 'macro'};
import {FieldErrorIcon, FieldGroup, FieldLabel, HelpText, Input} from './Field';
import {fontRelative, style} from '../style' with {type: 'macro'};
import {FormContext, useFormProps} from './Form';
import {GlobalDOMAttributes, HelpTextProps, SpectrumLabelableProps} from '@react-types/shared';
import {InputProps} from 'react-aria-components/Input';
import {style} from '../style' with {type: 'macro'};
import {IconContext} from './Icon';
import {InputContext, InputProps} from 'react-aria-components/Input';
import {mergeRefs} from 'react-aria/mergeRefs';
import {TextFieldRef} from './TextField';
import {useId} from 'react-aria/useId';
import {useSpectrumContextProps} from './useSpectrumContextProps';

export interface ColorFieldProps extends Omit<AriaColorFieldProps, 'children' | 'className' | 'style' | 'render' | keyof GlobalDOMAttributes>, StyleProps, SpectrumLabelableProps, HelpTextProps, Pick<InputProps, 'placeholder'> {
Expand All @@ -30,7 +34,11 @@ export interface ColorFieldProps extends Omit<AriaColorFieldProps, 'children' |
*
* @default 'M'
*/
size?: 'S' | 'M' | 'L' | 'XL'
size?: 'S' | 'M' | 'L' | 'XL',
/**
* The prefix to display in the ColorField. A non-interactive element that appears before the input.
*/
prefix?: ReactNode
}

export const ColorFieldContext = createContext<ContextValue<Partial<ColorFieldProps>, TextFieldRef>>(null);
Expand Down Expand Up @@ -70,6 +78,7 @@ export const ColorField = forwardRef(function ColorField(props: ColorFieldProps,
}
}));

let prefixId = useId();
return (
<AriaColorField
{...fieldProps}
Expand All @@ -92,7 +101,25 @@ export const ColorField = forwardRef(function ColorField(props: ColorFieldProps,
{label}
</FieldLabel>
<FieldGroup size={props.size}>
<Input ref={inputRef} />
{props.prefix ? (
<Provider values={[[IconContext, {styles: style({size: fontRelative(20), '--iconPrimary': {type: 'fill', value: 'currentColor'}})}]]}>
<CenterBaseline id={prefixId} styles={style({minWidth: 20, color: 'gray-600', flexShrink: 0, marginEnd: 'text-to-visual'})}>
{props.prefix}
</CenterBaseline>
</Provider>
) : null}
<InputContext.Consumer>
{ctx => (
<InputContext.Provider
value={{
...ctx,
'aria-labelledby': ctx?.['aria-labelledby'] ? `${ctx?.['aria-labelledby']} ${prefixId}` : prefixId,
ref: mergeRefs((ctx as any)?.ref,
inputRef)}}>
<Input />
</InputContext.Provider>
)}
</InputContext.Consumer>
{isInvalid && <FieldErrorIcon isDisabled={isDisabled} />}
</FieldGroup>
<HelpText
Expand Down
26 changes: 22 additions & 4 deletions packages/@react-spectrum/s2/src/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ import {PopoverProps as AriaPopoverProps, Placement} from 'react-aria-components
import {AsyncLoadable, GlobalDOMAttributes, HelpTextProps, LoadingState, SingleSelection, SpectrumLabelableProps} from '@react-types/shared';
import {AvatarContext} from './Avatar';
import {BaseCollection, CollectionNode} from 'react-aria/private/collections/BaseCollection';
import {baseColor, centerPadding, focusRing, space, style} from '../style' with {type: 'macro'};
import {baseColor, centerPadding, focusRing, fontRelative, space, style} from '../style' with {type: 'macro'};
import {Button, ButtonRenderProps} from 'react-aria-components/Button';
import {centerBaseline} from './CenterBaseline';
import {CenterBaseline, centerBaseline} from './CenterBaseline';
import {
checkmark,
description,
Expand Down Expand Up @@ -64,6 +64,7 @@ import {Popover} from './Popover';
import {pressScale} from './pressScale';
import {ProgressCircle} from './ProgressCircle';
import {TextFieldRef} from './TextField';
import {useId} from 'react-aria/useId';
import {useLocalizedStringFormatter} from 'react-aria/useLocalizedStringFormatter';
import {useScale} from './utils';
import {useSlotId} from 'react-aria/private/utils/useId';
Expand All @@ -76,7 +77,11 @@ export interface ComboboxStyleProps {
*
* @default 'M'
*/
size?: 'S' | 'M' | 'L' | 'XL'
size?: 'S' | 'M' | 'L' | 'XL',
/**
* The prefix to display in the ComboBox. A non-interactive element that appears before the input.
*/
prefix?: ReactNode
}
export interface ComboBoxProps<T extends object> extends
Omit<AriaComboBoxProps<T>, 'children' | 'style' | 'className' | 'render' | 'defaultFilter' | 'allowsEmptyCollection' | 'selectionMode' | 'selectedKey' | 'defaultSelectedKey' | 'onSelectionChange' | 'value' | 'defaultValue' | 'onChange' | keyof GlobalDOMAttributes>,
Expand Down Expand Up @@ -581,6 +586,7 @@ const ComboboxInner = forwardRef(function ComboboxInner(props: ComboBoxProps<any
);
}
let scale = useScale();
let prefixId = useId();

return (
<>
Expand All @@ -607,9 +613,21 @@ const ComboboxInner = forwardRef(function ComboboxInner(props: ComboBoxProps<any
// [9, 4], [12, 6], [15, 8], [18, 8]
paddingEnd: 'calc(self(height, self(minHeight)) * 3 / 16 - self(borderEndWidth, 2px))'
})({size})}>
{props.prefix ? (
<Provider values={[[IconContext, {styles: style({size: fontRelative(20), '--iconPrimary': {type: 'fill', value: 'currentColor'}})}]]}>
<CenterBaseline id={prefixId} styles={style({minWidth: 20, color: 'gray-600', flexShrink: 0, marginEnd: 'text-to-visual'})}>
{props.prefix}
</CenterBaseline>
</Provider>
) : null}
<InputContext.Consumer>
{ctx => (
<InputContext.Provider value={{...ctx, ref: mergeRefs((ctx as any)?.ref, inputRef)}}>
<InputContext.Provider
value={{
...ctx,
'aria-labelledby': ctx?.['aria-labelledby'] ? `${ctx?.['aria-labelledby']} ${prefixId}` : prefixId,
ref: mergeRefs((ctx as any)?.ref,
inputRef)}}>
<Input aria-describedby={spinnerId} />
</InputContext.Provider>
)}
Expand Down
28 changes: 24 additions & 4 deletions packages/@react-spectrum/s2/src/NumberField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
import Add from '../ui-icons/Add';
import {ButtonProps as AriaButtonProps, ButtonContext, ButtonRenderProps} from 'react-aria-components/Button';
import {NumberField as AriaNumberField, NumberFieldProps as AriaNumberFieldProps} from 'react-aria-components/NumberField';
import {baseColor, space, style} from '../style' with {type: 'macro'};
import {ContextValue, useContextProps} from 'react-aria-components/slots';
import {baseColor, fontRelative, space, style} from '../style' with {type: 'macro'};
import {CenterBaseline} from './CenterBaseline';
import {ContextValue, Provider, useContextProps} from 'react-aria-components/slots';
import {controlBorderRadius, field, fieldInput, getAllowedOverrides, StyleProps} from './style-utils' with {type: 'macro'};
import {createContext, CSSProperties, ForwardedRef, forwardRef, ReactNode, Ref, useContext, useImperativeHandle, useMemo, useRef} from 'react';
import {createFocusableRef} from './useDOMRef';
Expand All @@ -23,6 +24,7 @@ import {FieldErrorIcon, FieldGroup, FieldLabel, HelpText, Input} from './Field';
import {filterDOMProps} from 'react-aria/filterDOMProps';
import {FormContext, useFormProps} from './Form';
import {GlobalDOMAttributes, HelpTextProps, SpectrumLabelableProps} from '@react-types/shared';
import {IconContext} from './Icon';
import {InputContext, InputProps} from 'react-aria-components/Input';
import {mergeProps} from 'react-aria/mergeProps';
import {mergeRefs} from 'react-aria/mergeRefs';
Expand All @@ -31,6 +33,7 @@ import {TextFieldRef} from './TextField';
import {useButton} from 'react-aria/useButton';
import {useFocusRing} from 'react-aria/useFocusRing';
import {useHover} from 'react-aria/useHover';
import {useId} from 'react-aria/useId';
import {useSpectrumContextProps} from './useSpectrumContextProps';


Expand All @@ -50,7 +53,11 @@ export interface NumberFieldProps extends
*
* @default 'M'
*/
size?: 'S' | 'M' | 'L' | 'XL'
size?: 'S' | 'M' | 'L' | 'XL',
/**
* The prefix to display in the NumberField. A non-interactive element that appears before the input.
*/
prefix?: ReactNode
}

export const NumberFieldContext = createContext<ContextValue<Partial<NumberFieldProps>, TextFieldRef>>(null);
Expand Down Expand Up @@ -173,6 +180,7 @@ export const NumberField = forwardRef(function NumberField(props: NumberFieldPro
}
}));

let prefixId = useId();
return (
<AriaNumberField
ref={domRef}
Expand Down Expand Up @@ -208,9 +216,21 @@ export const NumberField = forwardRef(function NumberField(props: NumberFieldPro
isStepperHidden: 'edge-to-text'
}
})({size, isStepperHidden: hideStepper})}>
{props.prefix ? (
<Provider values={[[IconContext, {styles: style({size: fontRelative(20), '--iconPrimary': {type: 'fill', value: 'currentColor'}})}]]}>
<CenterBaseline id={prefixId} styles={style({minWidth: 20, color: 'gray-600', flexShrink: 0, marginEnd: 'text-to-visual'})}>
{props.prefix}
</CenterBaseline>
</Provider>
) : null}
<InputContext.Consumer>
{ctx => (
<InputContext.Provider value={{...ctx, ref: mergeRefs((ctx as any)?.ref, inputRef)}}>
<InputContext.Provider
value={{
...ctx,
'aria-labelledby': ctx?.['aria-labelledby'] ? `${ctx?.['aria-labelledby']} ${prefixId}` : prefixId,
ref: mergeRefs((ctx as any)?.ref,
inputRef)}}>
<Input />
</InputContext.Provider>
)}
Expand Down
37 changes: 18 additions & 19 deletions packages/@react-spectrum/s2/src/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@
*/

import {TextArea as AriaTextArea, TextAreaContext as AriaTextAreaContext} from 'react-aria-components/TextArea';
import {TextContext as AriaTextContext} from 'react-aria-components/Text';
import {TextField as AriaTextField, TextFieldProps as AriaTextFieldProps} from 'react-aria-components/TextField';
import {centerBaseline} from './CenterBaseline';
import {CenterBaseline} from './CenterBaseline';
import {centerPadding, fontRelative, style} from '../style' with {type: 'macro'};
import {composeRenderProps} from 'react-aria-components/composeRenderProps';
import {ContextValue, DEFAULT_SLOT, Provider, useSlottedContext} from 'react-aria-components/slots';
import {ContextValue, Provider, useSlottedContext} from 'react-aria-components/slots';
import {controlSize, field, getAllowedOverrides, StyleProps} from './style-utils' with {type: 'macro'};
import {createContext, forwardRef, ReactNode, Ref, useContext, useImperativeHandle, useRef} from 'react';
import {createFocusableRef} from './useDOMRef';
Expand All @@ -27,7 +26,8 @@ import {IconContext} from './Icon';
import {InputContext, InputProps} from 'react-aria-components/Input';
import {mergeRefs} from 'react-aria/mergeRefs';
import {StyleString} from '../style/types';
import {Text, TextContext} from './Content';
import {TextContext} from './Content';
import {useId} from 'react-aria/useId';
import {useSpectrumContextProps} from './useSpectrumContextProps';

export interface TextFieldRef<T extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement> extends FocusableRefValue<T, HTMLDivElement> {
Expand All @@ -43,7 +43,8 @@ export interface TextFieldProps extends Omit<AriaTextFieldProps, 'children' | 'c
*/
size?: 'S' | 'M' | 'L' | 'XL',
/**
* The prefix to display in the text field. Either a string or workflow icon.
* The prefix to display in the text field.
* A non-interactive element that appears before the input.
*/
prefix?: ReactNode
}
Expand All @@ -57,8 +58,10 @@ export const TextFieldContext = createContext<ContextValue<Partial<TextFieldProp
*/
export const TextField = forwardRef(function TextField(props: TextFieldProps, ref: Ref<TextFieldRef>) {
[props, ref] = useSpectrumContextProps(props, ref, TextFieldContext);
let prefixId = useId();
return (
<TextFieldBase
prefixId={prefixId}
{...props}
ref={ref}>
<Input />
Expand Down Expand Up @@ -90,7 +93,7 @@ export const TextArea = forwardRef(function TextArea(props: TextAreaProps, ref:
);
});

export const TextFieldBase = forwardRef(function TextFieldBase(props: TextFieldProps & {children: ReactNode, fieldGroupCss?: StyleString}, ref: Ref<TextFieldRef<HTMLInputElement | HTMLTextAreaElement>>) {
export const TextFieldBase = forwardRef(function TextFieldBase(props: TextFieldProps & {children: ReactNode, fieldGroupCss?: StyleString, prefixId?: string}, ref: Ref<TextFieldRef<HTMLInputElement | HTMLTextAreaElement>>) {
let inputRef = useRef<HTMLInputElement>(null);
let domRef = useRef<HTMLDivElement>(null);
let formContext = useContext(FormContext);
Expand Down Expand Up @@ -148,33 +151,29 @@ export const TextFieldBase = forwardRef(function TextFieldBase(props: TextFieldP
<Provider
values={[
[IconContext, {
render: centerBaseline({}),
styles: style({
size: fontRelative(20),
'--iconPrimary': {
type: 'fill',
value: 'currentColor'
}
})
}],
[AriaTextContext, {}],
[TextContext, {
slots: {
[DEFAULT_SLOT]: {
styles: style({minWidth: 20, display: 'flex', alignItems: 'center', justifyContent: 'center'})
}
}
}]
]}>
<div className={style({color: 'gray-600', flexShrink: 0, marginEnd: 'text-to-visual'})}>
{typeof props.prefix === 'string' ? <Text>{props.prefix}</Text> : props.prefix}
</div>
<CenterBaseline id={props.prefixId} styles={style({minWidth: 20, color: 'gray-600', flexShrink: 0, marginEnd: 'text-to-visual'})}>
{props.prefix}
</CenterBaseline>
</Provider>
) : null
}
<InputContext.Consumer>
{ctx => (
<InputContext.Provider value={{...ctx, ref: mergeRefs((ctx as any)?.ref, inputRef)}}>
<InputContext.Provider
value={{
...ctx,
'aria-labelledby': ctx?.['aria-labelledby'] ? `${ctx?.['aria-labelledby']} ${props.prefixId}` : props.prefixId,
ref: mergeRefs((ctx as any)?.ref,
inputRef)}}>
{children}
</InputContext.Provider>
)}
Expand Down
17 changes: 16 additions & 1 deletion packages/@react-spectrum/s2/stories/ColorField.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
* governing permissions and limitations under the License.
*/

import {ColorField} from '../src/ColorField';
import {Color} from 'react-aria-components/ColorField';
import {ColorField, ColorFieldProps} from '../src/ColorField';
import {ColorSwatch} from '../src/ColorSwatch';

import {Content, Footer, Heading, Text} from '../src/Content';
import {ContextualHelp} from '../src/ContextualHelp';
import {Link} from '../src/Link';
import type {Meta, StoryObj} from '@storybook/react';
import {useState} from 'react';

const meta: Meta<typeof ColorField> = {
component: ColorField,
Expand Down Expand Up @@ -72,3 +75,15 @@ export const ContextualHelpExample: Story = {
label: 'Color'
}
};

function ColorSwatchExample(props: ColorFieldProps) {
let [color, setColor] = useState<Color | null>(null);
return <ColorField {...props} value={color} onChange={setColor} prefix={<ColorSwatch size="XS" color={color ?? undefined} />} />;
}

export const WithPrefix: Story = {
render: (args) => <ColorSwatchExample {...args} />,
args: {
label: 'Color'
}
};
17 changes: 17 additions & 0 deletions packages/@react-spectrum/s2/stories/ComboBox.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -393,3 +393,20 @@ export const ComboboxInsideDialog: Story = {
),
args: Example.args
};


export const WithPrefix: Story = {
render: (args: ComboBoxProps<any>) => (
<ComboBox {...args}>
<ComboBoxItem>Chocolate</ComboBoxItem>
<ComboBoxItem>Mint</ComboBoxItem>
<ComboBoxItem>Strawberry</ComboBoxItem>
<ComboBoxItem>Vanilla</ComboBoxItem>
<ComboBoxItem>Chocolate Chip Cookie Dough</ComboBoxItem>
</ComboBox>
),
args: {
prefix: <Avatar size={20} src="https://i.imgur.com/xIe7Wlb.png" />,
label: 'User ice cream flavor'
}
};
Loading
Loading