From 2244dbd4630e4f6822e56d8a975990ebbf0dc9bd Mon Sep 17 00:00:00 2001 From: Jared Scott Date: Mon, 2 Sep 2024 19:12:14 +0800 Subject: [PATCH] feat: #6870 Ensure steps can be programmatically styled (#7101) * feat: #6870 Ensure steps can be programmatically styled based on parent and context - Pull code from TabView.js and ensure it works correctly under steps - Put in some initial styling for `step` and `label` - TODO add typing for parent and context * feat: #6870 Add typing to steps.d.ts Do not implement child props at the moment --- components/lib/passthrough/tailwind/index.js | 19 ++++--- components/lib/steps/Steps.js | 44 ++++++++++++++--- components/lib/steps/StepsBase.js | 5 +- components/lib/steps/steps.d.ts | 52 ++++++++++++++++++-- 4 files changed, 100 insertions(+), 20 deletions(-) diff --git a/components/lib/passthrough/tailwind/index.js b/components/lib/passthrough/tailwind/index.js index 979b489e96..15922964ce 100644 --- a/components/lib/passthrough/tailwind/index.js +++ b/components/lib/passthrough/tailwind/index.js @@ -2109,12 +2109,19 @@ const Tailwind = { 'focus:outline-none focus:outline-offset-0 focus:shadow-[0_0_0_0.2rem_rgba(191,219,254,1)] dark:focus:shadow-[0_0_0_0.2rem_rgba(147,197,253,0.5)]' ) }, - step: { - className: classNames('flex items-center justify-center', 'text-gray-700 dark:text-white/80 border border-gray-300 dark:border-blue-900/40 bg-white dark:bg-gray-900 w-[2rem] h-[2rem] leading-2rem text-sm z-10 rounded-full') - }, - label: { - className: classNames('block', 'whitespace-nowrap overflow-hidden overflow-ellipsis max-w-full', 'mt-2 text-gray-500 dark:text-white/60') - } + step: ({ parent, context }) => ({ + className: classNames('flex items-center justify-center', 'text-gray-700 dark:text-white/80 border border-gray-300 dark:border-blue-900/40 bg-white dark:bg-gray-900 w-[2rem] h-[2rem] leading-2rem text-sm z-10 rounded-full', { + 'bg-white': parent.state.activeIndex !== context.index, // unselected item. + 'bg-blue-500': parent.state.activeIndex === context.index // Selected item. + }) + }), + label: ({ parent, context }) => ({ + className: classNames('block', 'whitespace-nowrap overflow-hidden overflow-ellipsis max-w-full', 'mt-2 text-gray-500 dark:text-white/60', { + 'font-normal': parent.state.activeIndex !== context.index, // unselected item. + 'font-bold': parent.state.activeIndex === context.index, // Selected item. + 'text-gray-500/60': context.disabled + }) + }) }, tabmenu: { root: 'overflow-x-auto', diff --git a/components/lib/steps/Steps.js b/components/lib/steps/Steps.js index 4cbed933b6..ce829341c3 100644 --- a/components/lib/steps/Steps.js +++ b/components/lib/steps/Steps.js @@ -12,18 +12,44 @@ export const Steps = React.memo( const props = StepsBase.getProps(inProps, context); const [idState, setIdState] = React.useState(props.id); + const [activeIndexState, setActiveIndexState] = React.useState(props.activeIndex); const elementRef = React.useRef(null); const listRef = React.useRef(null); + const count = React.Children.count(props.children); - const { ptm, cx, isUnstyled } = StepsBase.setMetaData({ + const metaData = { props, state: { - id: idState + id: idState, + activeIndex: activeIndexState } + }; + + const { ptm, ptmo, cx, isUnstyled } = StepsBase.setMetaData({ + ...metaData }); useHandleStyle(StepsBase.css.styles, isUnstyled, { name: 'steps' }); + const getStepPT = (step, key, index) => { + const stepMetaData = { + // props: step.props, + parent: metaData, + context: { + index, + count, + first: index === 0, + last: index === count - 1, + active: index === activeIndexState, + disabled: getStepProp(step, 'disabled') + } + }; + + return mergeProps(ptm(`step.${key}`, { step: stepMetaData }), ptm(`steps.${key}`, { steps: stepMetaData }), ptm(`steps.${key}`, stepMetaData), ptmo(getStepProp(step, 'pt'), key, stepMetaData)); + }; + + const getStepProp = (step, name) => StepsBase.getCProp(step, name); + const itemClick = (event, item, index) => { if (props.readOnly || item.disabled) { event.preventDefault(); @@ -47,6 +73,8 @@ export const Steps = React.memo( }); } + setActiveIndexState(index); + if (!item.url) { event.preventDefault(); event.stopPropagation(); @@ -163,15 +191,15 @@ export const Steps = React.memo( } const key = item.id || idState + '_' + index; - const active = index === props.activeIndex; - const disabled = item.disabled || (index !== props.activeIndex && props.readOnly); + const active = index === activeIndexState; + const disabled = item.disabled || (index !== activeIndexState && props.readOnly); const iconClassName = classNames('p-menuitem-icon', item.icon); const iconProps = mergeProps( { className: cx('icon', { item }) }, - ptm('icon') + getStepPT(item, 'icon', index) ); const icon = IconUtils.getJSXIcon(item.icon, { ...iconProps }, { props }); @@ -180,7 +208,7 @@ export const Steps = React.memo( { className: cx('label') }, - ptm('label') + getStepPT(item, 'label', index) ); const label = item.label && {item.label}; @@ -189,7 +217,7 @@ export const Steps = React.memo( { className: cx('step') }, - ptm('step') + getStepPT(item, 'step', index) ); const actionProps = mergeProps( @@ -202,7 +230,7 @@ export const Steps = React.memo( onKeyDown: (event) => onItemKeyDown(event, item, index), onClick: (event) => itemClick(event, item, index) }, - ptm('action') + getStepPT(item, 'action', index) ); let content = ( diff --git a/components/lib/steps/StepsBase.js b/components/lib/steps/StepsBase.js index f3dd6f1bac..bad7a72b82 100644 --- a/components/lib/steps/StepsBase.js +++ b/components/lib/steps/StepsBase.js @@ -1,5 +1,5 @@ import { ComponentBase } from '../componentbase/ComponentBase'; -import { classNames } from '../utils/Utils'; +import { classNames, ObjectUtils } from '../utils/Utils'; const classes = { icon: ({ item }) => classNames('p-menuitem-icon', item.icon), @@ -84,5 +84,6 @@ export const StepsBase = ComponentBase.extend({ css: { classes, styles - } + }, + getCProp: (step, name) => ObjectUtils.getComponentProp(step, name, StepsBase.defaultProps) }); diff --git a/components/lib/steps/steps.d.ts b/components/lib/steps/steps.d.ts index 940514f4a5..7dff8ceb3a 100644 --- a/components/lib/steps/steps.d.ts +++ b/components/lib/steps/steps.d.ts @@ -14,6 +14,50 @@ import { PassThroughOptions } from '../passthrough'; import { PassThroughType } from '../utils/utils'; export declare type StepsPassThroughType = PassThroughType; +export declare type StepPassThroughType = PassThroughType; + +/** + * Custom passthrough(pt) option method. + */ +export interface StepPassThroughMethodOptions { + // props: StepsProps; + parent: StepsThroughMethodOptions; + context: StepContext; +} + +/** + * Defines current inline context in Steps component. + */ +export interface StepContext { + /** + * Step index. + */ + index: number; + /** + * Total number of steps + */ + count: number; + /** + * Is this the first step? + * @defaultValue false + */ + first: boolean; + /** + * Is this the last step? + * @defaultValue false + */ + last: boolean; + /** + * Is this step currently selected. + * @defaultValue false + */ + selected: boolean; + /** + * Is this step currently disabled. + * @defaultValue false + */ + disabled: boolean; +} /** * Custom passthrough(pt) option method. @@ -42,19 +86,19 @@ export interface StepsPassThroughOptions { /** * Uses to pass attributes to the action's DOM element. */ - action?: StepsPassThroughType>; + action?: StepPassThroughType>; /** * Uses to pass attributes to the step's DOM element. */ - step?: StepsPassThroughType>; + step?: StepPassThroughType>; /** * Uses to pass attributes to the label's DOM element. */ - label?: StepsPassThroughType>; + label?: StepPassThroughType>; /** * Uses to pass attributes to the icon's DOM element. */ - icon?: StepsPassThroughType | React.HTMLAttributes>; + icon?: StepPassThroughType | React.HTMLAttributes>; /** * Used to manage all lifecycle hooks * @see {@link ComponentHooks}