diff --git a/.changeset/chilly-dancers-switch.md b/.changeset/chilly-dancers-switch.md new file mode 100644 index 0000000000..97fe3da37d --- /dev/null +++ b/.changeset/chilly-dancers-switch.md @@ -0,0 +1,10 @@ +--- +"@nextui-org/date-picker": patch +"@nextui-org/date-input": patch +"@nextui-org/select": patch +"@nextui-org/input": patch +"@nextui-org/system": patch +"@nextui-org/theme": patch +--- + +Adding support for global labelPlacement prop.(ENG-1694) diff --git a/apps/docs/content/docs/api-references/nextui-provider.mdx b/apps/docs/content/docs/api-references/nextui-provider.mdx index c64fd50061..60c223f467 100644 --- a/apps/docs/content/docs/api-references/nextui-provider.mdx +++ b/apps/docs/content/docs/api-references/nextui-provider.mdx @@ -142,6 +142,15 @@ interface AppProviderProps { +`labelPlacement` + +- **Description**: Determines the position where label should appear, such as inside, outside or outside-left of the component. +- **Type**: `string` | `undefined` +- **Possible Values**: `inside` | `outside` | `outside-left` | `undefined` +- **Default**: `undefined` + + + `disableAnimation` - **Description**: Disables animations globally. This will also avoid `framer-motion` features to be loaded in the bundle which can potentially reduce the bundle size. diff --git a/packages/components/date-input/src/use-date-input.ts b/packages/components/date-input/src/use-date-input.ts index e34065333e..e1cb2b1935 100644 --- a/packages/components/date-input/src/use-date-input.ts +++ b/packages/components/date-input/src/use-date-input.ts @@ -192,15 +192,15 @@ export function useDateInput(originalProps: UseDateInputPro const isInvalid = isInvalidProp || ariaIsInvalid; const labelPlacement = useMemo(() => { - if ( - (!originalProps.labelPlacement || originalProps.labelPlacement === "inside") && - !props.label - ) { + const labelPlacement = + originalProps.labelPlacement ?? globalContext?.labelPlacement ?? "inside"; + + if (labelPlacement === "inside" && !label) { return "outside"; } - return originalProps.labelPlacement ?? "inside"; - }, [originalProps.labelPlacement, props.label]); + return labelPlacement; + }, [originalProps.labelPlacement, globalContext?.labelPlacement, label]); const shouldLabelBeOutside = labelPlacement === "outside" || labelPlacement === "outside-left"; diff --git a/packages/components/date-input/src/use-time-input.ts b/packages/components/date-input/src/use-time-input.ts index 7b540a626b..b953425efd 100644 --- a/packages/components/date-input/src/use-time-input.ts +++ b/packages/components/date-input/src/use-time-input.ts @@ -134,15 +134,15 @@ export function useTimeInput(originalProps: UseTimeInputPro const baseStyles = clsx(classNames?.base, className); const labelPlacement = useMemo(() => { - if ( - (!originalProps.labelPlacement || originalProps.labelPlacement === "inside") && - !props.label - ) { + const labelPlacement = + originalProps.labelPlacement ?? globalContext?.labelPlacement ?? "inside"; + + if (labelPlacement === "inside" && !label) { return "outside"; } - return originalProps.labelPlacement ?? "inside"; - }, [originalProps.labelPlacement, props.label]); + return labelPlacement; + }, [originalProps.labelPlacement, globalContext?.labelPlacement, label]); const shouldLabelBeOutside = labelPlacement === "outside" || labelPlacement === "outside-left"; diff --git a/packages/components/date-picker/src/use-date-picker-base.ts b/packages/components/date-picker/src/use-date-picker-base.ts index 8a88836d74..a89ac5ff4e 100644 --- a/packages/components/date-picker/src/use-date-picker-base.ts +++ b/packages/components/date-picker/src/use-date-picker-base.ts @@ -9,7 +9,7 @@ import type {ValueBase} from "@react-types/shared"; import {dataAttr} from "@nextui-org/shared-utils"; import {dateInput, DatePickerVariantProps} from "@nextui-org/theme"; -import {useCallback} from "react"; +import {useCallback, useMemo} from "react"; import {HTMLNextUIProps, mapPropsVariants, useProviderContext} from "@nextui-org/system"; import {mergeProps} from "@react-aria/utils"; import {useDOMRef} from "@nextui-org/react-utils"; @@ -234,10 +234,21 @@ export function useDatePickerBase(originalProps: UseDatePic "data-invalid": dataAttr(originalProps?.isInvalid), } as DateInputProps; + const labelPlacement = useMemo(() => { + const labelPlacement = + originalProps.labelPlacement ?? globalContext?.labelPlacement ?? "inside"; + + if (labelPlacement === "inside" && !label) { + return "outside"; + } + + return labelPlacement; + }, [originalProps.labelPlacement, globalContext?.labelPlacement, label]); + const timeInputProps = { ...userTimeInputProps, size: "sm", - labelPlacement: "outside-left", + labelPlacement, label: userTimeInputProps?.label || stringFormatter.format("time"), placeholderValue: timePlaceholder, hourCycle: props.hourCycle, diff --git a/packages/components/date-picker/src/use-date-range-picker.ts b/packages/components/date-picker/src/use-date-range-picker.ts index 2dabebb84d..a1301d5bbd 100644 --- a/packages/components/date-picker/src/use-date-range-picker.ts +++ b/packages/components/date-picker/src/use-date-range-picker.ts @@ -60,6 +60,7 @@ export type UseDateRangePickerProps = Props & AriaDateRa export function useDateRangePicker({ as, + label, isInvalid: isInvalidProp, description, startContent, @@ -144,15 +145,15 @@ export function useDateRangePicker({ const showTimeField = !!timeGranularity; const labelPlacement = useMemo(() => { - if ( - (!originalProps.labelPlacement || originalProps.labelPlacement === "inside") && - !originalProps.label - ) { + const labelPlacement = + originalProps.labelPlacement ?? globalContext?.labelPlacement ?? "inside"; + + if (labelPlacement === "inside" && !label) { return "outside"; } - return originalProps.labelPlacement ?? "inside"; - }, [originalProps.labelPlacement, originalProps.label]); + return labelPlacement; + }, [originalProps.labelPlacement, globalContext?.labelPlacement, label]); const shouldLabelBeOutside = labelPlacement === "outside" || labelPlacement === "outside-left"; @@ -395,7 +396,7 @@ export function useDateRangePicker({ const getDateInputGroupProps = () => { return { as, - label: originalProps.label, + label, description, endContent, errorMessage, @@ -423,7 +424,7 @@ export function useDateRangePicker({ return { state, - label: originalProps.label, + label, slots, classNames, startContent, diff --git a/packages/components/input/src/use-input.ts b/packages/components/input/src/use-input.ts index d8cae3c3d7..af3d22a4fb 100644 --- a/packages/components/input/src/use-input.ts +++ b/packages/components/input/src/use-input.ts @@ -231,7 +231,15 @@ export function useInput(originalProps: UseSelectProps) { const {isHovered, hoverProps} = useHover({isDisabled: originalProps.isDisabled}); const labelPlacement = useMemo(() => { - if ((!originalProps.labelPlacement || originalProps.labelPlacement === "inside") && !label) { + const labelPlacement = + originalProps.labelPlacement ?? globalContext?.labelPlacement ?? "inside"; + + if (labelPlacement === "inside" && !label) { return "outside"; } - return originalProps.labelPlacement ?? "inside"; - }, [originalProps.labelPlacement, label]); + return labelPlacement; + }, [originalProps.labelPlacement, globalContext?.labelPlacement, label]); const hasPlaceholder = !!placeholder; const shouldLabelBeOutside = diff --git a/packages/core/system/src/provider-context.ts b/packages/core/system/src/provider-context.ts index c33e2266a4..b6e863cc39 100644 --- a/packages/core/system/src/provider-context.ts +++ b/packages/core/system/src/provider-context.ts @@ -11,6 +11,13 @@ export type ProviderContextProps = { * @default false */ disableAnimation?: boolean; + /** + * Position where the label should appear. + * + * @default undefined + */ + labelPlacement?: "inside" | "outside" | "outside-left" | undefined; + /** /** * Whether to disable the ripple effect in the whole application. * If `disableAnimation` is set to `true`, this prop will be ignored. diff --git a/packages/core/system/src/provider.tsx b/packages/core/system/src/provider.tsx index 45e85e775f..c6cc1a3be3 100644 --- a/packages/core/system/src/provider.tsx +++ b/packages/core/system/src/provider.tsx @@ -58,6 +58,7 @@ export const NextUIProvider: React.FC = ({ reducedMotion = "never", validationBehavior, locale = "en-US", + labelPlacement, // if minDate / maxDate are not specified in `defaultDates` // then they will be set in `use-date-input.ts` or `use-calendar-base.ts` defaultDates, @@ -85,6 +86,7 @@ export const NextUIProvider: React.FC = ({ disableAnimation, disableRipple, validationBehavior, + labelPlacement, }; }, [ createCalendar, @@ -93,6 +95,7 @@ export const NextUIProvider: React.FC = ({ disableAnimation, disableRipple, validationBehavior, + labelPlacement, ]); return ( diff --git a/packages/core/theme/src/components/date-input.ts b/packages/core/theme/src/components/date-input.ts index 4334d08d48..70773a85ba 100644 --- a/packages/core/theme/src/components/date-input.ts +++ b/packages/core/theme/src/components/date-input.ts @@ -217,7 +217,6 @@ const dateInput = tv({ color: "default", size: "md", fullWidth: true, - labelPlacement: "inside", isDisabled: false, }, compoundVariants: [ diff --git a/packages/core/theme/src/components/input.ts b/packages/core/theme/src/components/input.ts index 5cb6ef14a5..5e5d40fd67 100644 --- a/packages/core/theme/src/components/input.ts +++ b/packages/core/theme/src/components/input.ts @@ -258,7 +258,6 @@ const input = tv({ color: "default", size: "md", fullWidth: true, - labelPlacement: "inside", isDisabled: false, isMultiline: false, }, diff --git a/packages/core/theme/src/components/select.ts b/packages/core/theme/src/components/select.ts index 67c216126e..4d0c4e835b 100644 --- a/packages/core/theme/src/components/select.ts +++ b/packages/core/theme/src/components/select.ts @@ -209,7 +209,6 @@ const select = tv({ variant: "flat", color: "default", size: "md", - labelPlacement: "inside", fullWidth: true, isDisabled: false, isMultiline: false, diff --git a/packages/storybook/.storybook/preview.tsx b/packages/storybook/.storybook/preview.tsx index c519a071c4..c294cc65af 100644 --- a/packages/storybook/.storybook/preview.tsx +++ b/packages/storybook/.storybook/preview.tsx @@ -7,13 +7,13 @@ import "./style.css"; import {withStrictModeSwitcher} from "./addons/react-strict-mode"; const decorators: Preview["decorators"] = [ - (Story, {globals: {locale, disableAnimation}}) => { + (Story, {globals: {locale, disableAnimation, labelPlacement}}) => { const direction = // @ts-ignore locale && new Intl.Locale(locale)?.textInfo?.direction === "rtl" ? "rtl" : undefined; return ( - +
@@ -127,6 +127,18 @@ const globalTypes: Preview["globalTypes"] = { ], }, }, + labelPlacement: { + name: "Label Placement", + description: "Position of label.", + toolbar: { + icon: "component", + items: [ + {value: "inside", title: "Inside"}, + {value: "outside", title: "Outside"}, + {value: "outside-left", title: "Outside Left"}, + ], + }, + } }; const preview: Preview = {