Skip to content

Commit

Permalink
Add action button for cards and collapse
Browse files Browse the repository at this point in the history
  • Loading branch information
vin0401 committed Jan 13, 2025
1 parent bd2b4bd commit 594edb6
Show file tree
Hide file tree
Showing 26 changed files with 568 additions and 58 deletions.
2 changes: 2 additions & 0 deletions assets/js/src/core/app/config/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ import {
DynamicTypeBatchEditTextArea
} from '@Pimcore/modules/element/dynamic-types/defintinitions/batch-edits/types/text/dynamic-type-batch-edit-text-area'
import { DynamicTypeObjectDataFieldCollection } from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/types/dynamic-type-object-data-field-collection'
import { DynamicTypeObjectDataObjectBrick } from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/types/dynamic-type-object-data-object-brick'

// Widget manager
container.bind(serviceIds.widgetManager).to(WidgetRegistry).inSingletonScope()
Expand Down Expand Up @@ -265,6 +266,7 @@ container.bind(serviceIds['DynamicTypes/ObjectData/StructuredTable']).to(Dynamic
container.bind(serviceIds['DynamicTypes/ObjectData/Block']).to(DynamicTypeObjectDataBlock).inSingletonScope()
container.bind(serviceIds['DynamicTypes/ObjectData/LocalizedFields']).to(DynamicTypeObjectDataLocalizedFields).inSingletonScope()
container.bind(serviceIds['DynamicTypes/ObjectData/FieldCollection']).to(DynamicTypeObjectDataFieldCollection).inSingletonScope()
container.bind(serviceIds['DynamicTypes/ObjectData/ObjectBrick']).to(DynamicTypeObjectDataObjectBrick).inSingletonScope()

// Execution engine
container.bind(serviceIds['ExecutionEngine/JobComponentRegistry']).to(JobComponentRegistry).inSingletonScope()
Expand Down
1 change: 1 addition & 0 deletions assets/js/src/core/app/config/services/service-ids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export const serviceIds = {
'DynamicTypes/ObjectData/Block': 'DynamicTypes/ObjectData/Block',
'DynamicTypes/ObjectData/LocalizedFields': 'DynamicTypes/ObjectData/LocalizedFields',
'DynamicTypes/ObjectData/FieldCollection': 'DynamicTypes/ObjectData/FieldCollection',
'DynamicTypes/ObjectData/ObjectBrick': 'DynamicTypes/ObjectData/ObjectBrick',

// Execution engine
'ExecutionEngine/JobComponentRegistry': 'ExecutionEngine/JobComponentRegistry',
Expand Down
11 changes: 11 additions & 0 deletions assets/js/src/core/components/button/button.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ export const useStyles = createStyles(({ token, css }) => {
margin: auto;
color: inherit;
}
&.button--type-action {
background-color: ${token.colorBgToolbar};
border: none;
box-shadow: none;
border-radius: ${token.borderRadius}px ${token.borderRadius}px 0 0;
&.ant-btn-variant-outlined:not(:disabled):not(.ant-btn-disabled):hover {
background-color: ${token.colorFillActive};
}
}
`
}
})
7 changes: 5 additions & 2 deletions assets/js/src/core/components/button/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ import cn from 'classnames'
import { Spin } from '../spin/spin'
import { useStyles } from './button.styles'

export interface ButtonProps extends AntdButtonProps {
export interface ButtonProps extends Omit<AntdButtonProps, 'type'> {
type?: AntdButtonProps['type'] | 'action'
loading?: boolean
}

const Component = ({ loading, children, className, ...props }: ButtonProps, ref: RefObject<HTMLButtonElement | null>): React.JSX.Element => {
const Component = ({ loading, children, className, type, ...props }: ButtonProps, ref: RefObject<HTMLButtonElement | null>): React.JSX.Element => {
const buttonRef = useRef<HTMLButtonElement>(null)

const { styles } = useStyles()
Expand All @@ -31,6 +32,7 @@ const Component = ({ loading, children, className, ...props }: ButtonProps, ref:

const buttonClassNames = cn(
'button',
`button--type-${type}`,
styles.button,
{
'ant-btn-loading': loading
Expand All @@ -56,6 +58,7 @@ const Component = ({ loading, children, className, ...props }: ButtonProps, ref:
<AntdButton
className={ buttonClassNames }
ref={ buttonRef }
type={ type === 'action' ? undefined : type }
{ ...props }
>
<AnimatePresence>
Expand Down
16 changes: 16 additions & 0 deletions assets/js/src/core/components/card/card.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { IconTextButton } from '@Pimcore/components/icon-text-button/icon-text-b
import { Flex } from 'antd'
import Input from 'antd/es/input/Input'
import { Button } from '@Pimcore/components/button/button'
import { ExtraPosition } from '../collapse/item/collapse-item.stories'

Check failure on line 23 in assets/js/src/core/components/card/card.stories.tsx

View workflow job for this annotation

GitHub Actions / lint

'ExtraPosition' is defined but never used
import { extractComponentProps } from 'storybook/internal/docs-tools'

Check failure on line 24 in assets/js/src/core/components/card/card.stories.tsx

View workflow job for this annotation

GitHub Actions / lint

'extractComponentProps' is defined but never used

const config: Meta = {
title: 'Components/Data Display/Card',
Expand Down Expand Up @@ -56,6 +58,20 @@ export const HeadWithButtons = {
]
}
}

export const HeadWithActionButton = {
args: {
..._default.args,
title: 'Action Buttons',
extraPosition: 'flex-start',
extra: (
<IconTextButton icon={{ value: 'edit' }} type='action' onClick={ () => { console.log('click action button 1') } }>
Edit
</IconTextButton>
)
}
}

export const CloseButton = {
args: {
..._default.args,
Expand Down
16 changes: 16 additions & 0 deletions assets/js/src/core/components/card/card.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export const useStyles = createStyles(({ token, css }) => {
.ant-card-head {
min-height: 38px;
padding: ${token.paddingXXS}px ${token.paddingSM}px;
.button--type-action {
margin-bottom: -4px;
}
}
&.ant-card:not(.ant-card-bordered) {
Expand All @@ -40,6 +44,18 @@ export const useStyles = createStyles(({ token, css }) => {
color: ${token.colorTextSecondary};
}
.ant-card-head-wrapper {
gap: ${token.paddingXS}px;
.ant-card-head-title {
min-width: fit-content;
}
.ant-card-extra {
width: 100%;
}
}
.ant-card-body {
padding: 0;
}
Expand Down
12 changes: 8 additions & 4 deletions assets/js/src/core/components/card/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,18 @@ import { Icon } from '@Pimcore/components/icon/icon'
import { PimcoreImage as Image } from '@Pimcore/components/pimcore-image/pimcore-image'
import { useTranslation } from 'react-i18next'
import { Box, type BoxProps } from '../box/box'
import { Flex, type FlexProps } from '../flex/flex'

export interface CardProps extends AntdCardProps {
loading?: boolean
fitContent?: boolean
onClose?: () => void
icon?: string
image?: { src: string, alt?: string } | null
extra?: any[]
footer?: React.ReactNode
theme?: 'default' | 'fieldset' | 'card-with-highlight'
contentPadding?: BoxProps['padding']
extraPosition?: FlexProps['justify']
}

const Component = ({ loading, children, footer, fitContent, className, theme = 'default', contentPadding = 'small', ...props }: CardProps, ref: RefObject<HTMLElement | null>): React.JSX.Element => {
Expand All @@ -45,7 +46,10 @@ const Component = ({ loading, children, footer, fitContent, className, theme = '

const renderExtraContent = (): React.ReactElement | null => {
return (
<Fragment>
<Flex
className='w-full'
justify={ props.extraPosition ?? 'flex-end' }
>
{Array.isArray(props.extra)
? (
<div>
Expand All @@ -65,7 +69,7 @@ const Component = ({ loading, children, footer, fitContent, className, theme = '
))}
</div>
)
: null}
: props.extra}

{props.onClose !== undefined
? (
Expand All @@ -79,7 +83,7 @@ const Component = ({ loading, children, footer, fitContent, className, theme = '
/>
)
: null}
</Fragment>
</Flex>
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { Icon } from '@Pimcore/components/icon/icon'
import { IconButton } from '@Pimcore/components/icon-button/icon-button'
import { Flex } from '@Pimcore/components/flex/flex'
import { Button } from '@Pimcore/components/button/button'
import { IconTextButton } from '@Pimcore/components/icon-text-button/icon-text-button'

const config: Meta = {
title: 'Components/Layout/Collapse/CollapseItem',
Expand Down Expand Up @@ -65,6 +66,18 @@ export const ExtraPosition: StoryObj<CollapseItemProps> = {
}
}

export const ActionButtons: StoryObj<CollapseItemProps> = {
args: {
..._default.args,
extra: <Flex>
<IconTextButton icon={{ value: 'edit' }} type='action' onClick={ (e) => { e.stopPropagation(); console.log('click action button 1') } }>
Edit
</IconTextButton>
</Flex>,
extraPosition: 'start'
}
}

export const Borderless: StoryObj<CollapseItemProps> = {
args: {
...Extra.args,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ export const useStyles = createStyles(({ css, token }) => {
background: transparent;
border: none;
&.ant-collapse-small >.ant-collapse-item >.ant-collapse-header {
padding: ${token.paddingXXS}px ${token.paddingSM}px;
}
&>.ant-collapse-item >.ant-collapse-header {
.button--type-action {
margin-bottom: -4px;
}
}
.collapse-header__title-container {
flex-grow: 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const RootComponent = ({ layout, data, className }: RootComponentProps):
>
<Form
className={ className }
initialValues={ data }

layout='vertical'
onFinish={ onFinish }
preserve
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { useElementContext } from '@Pimcore/modules/element/hooks/use-element-co
import { Content } from '@Pimcore/components/content/content'
import { FieldCollectionProvider } from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/components/field-collection/providers/field-collection-provider'
import { useStyles } from './edit-container.styles'
import { ObjectBrickProvider } from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/components/object-brick/providers/object-brick-provider'

export const EditContainer = (): React.JSX.Element => {
const { id } = useElementContext()
Expand All @@ -33,11 +34,13 @@ export const EditContainer = (): React.JSX.Element => {

return (
<FieldCollectionProvider>
<RootComponent
className={ styles.editContainer }
data={ data?.objectData }
layout={ layoutData }
/>
<ObjectBrickProvider>
<RootComponent
className={ styles.editContainer }
data={ data?.objectData }
layout={ layoutData }
/>
</ObjectBrickProvider>
</FieldCollectionProvider>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ import { Form } from '@Pimcore/components/form/form'
import React, { type ComponentType, type ReactNode } from 'react'
import { CollectionContent } from './content/collection-content'
import { type FormListFieldData, type FormListOperation } from 'antd'
import { type BaseViewProps } from '../../../layout-related/views/base-view'

export interface CollectionOnTabCloseEvent {
tabName: string
fields: FormListFieldData[]
operation: FormListOperation
}

export interface CollectionProps {
name: string | number | Array<string | number>
Expand All @@ -28,6 +35,10 @@ export interface CollectionProps {
disallowAdd?: boolean
disallowDelete?: boolean
disallowReorder?: boolean
type?: 'list' | 'tabs'
onTabClose?: (event: CollectionOnTabCloseEvent) => void
extra?: BaseViewProps['extra']
extraPosition?: BaseViewProps['extraPosition']
}

export interface CollectionItemProps extends Omit<CollectionProps, 'itemComponent'> {
Expand All @@ -47,6 +58,7 @@ export const Collection = ({
disallowAdd = false,
disallowDelete = false,
disallowReorder = false,
type = 'list',
...props
}: CollectionProps): React.JSX.Element => {
return (
Expand All @@ -59,6 +71,7 @@ export const Collection = ({
disallowAdd={ disallowAdd }
disallowDelete={ disallowDelete }
disallowReorder={ disallowReorder }
type={ type }
{ ...props }
fields={ fields }
operation={ operation }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,14 @@
*/

import React from 'react'
import { type CollectionContentProps } from './collection-content'
import { type CollectionContentBaseProps } from './collection-content'
import { Space } from '@Pimcore/components/space/space'
import { Text } from '@Pimcore/components/text/text'
import { Box } from '@Pimcore/components/box/box'
import { isEmptyValue } from '@Pimcore/utils/type-utils'
import { useTranslation } from 'react-i18next'

interface CollectionEmptyProps extends CollectionContentProps {
operation: CollectionContentProps['operation']
}
interface CollectionEmptyProps extends CollectionContentBaseProps {}

export const CollectionContentEmpty = (props: CollectionEmptyProps): React.JSX.Element => {
const { addButtonComponent, title, ...baseButtonProps } = props
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,61 +15,27 @@ import { type FormListFieldData, type FormListOperation } from 'antd'
import { type CollectionProps } from '../collection'
import { CollectionContentEmpty } from './collection-content-empty'
import React from 'react'
import { BaseView } from '../../../../layout-related/views/base-view'
import { Space } from '@Pimcore/components/space/space'
import { usePrevious } from '@Pimcore/utils/hooks/use-previous'
import { CollectionContentTabs, type CollectionContentTabsProps } from './types/collection-content-tabs'
import { CollectionContentList, type CollectionContentListProps } from './types/collection-content-list'

export interface CollectionContentProps extends CollectionProps {
export interface CollectionContentBaseProps extends CollectionProps {
fields: FormListFieldData[]
operation: FormListOperation
}

export const CollectionContent = (props: CollectionContentProps): React.JSX.Element => {
const { itemComponent, ...baseItemProps } = props
const [ItemComponent, additionalItemProps] = itemComponent
export type CollectionContentProps = CollectionContentBaseProps & (CollectionContentListProps | CollectionContentTabsProps)

export const CollectionContent = (props: CollectionContentProps): React.JSX.Element => {
const { fields } = props
const hasFields = fields.length > 0
const previousFields = usePrevious(props.fields)
const hasFirstFieldAdded = previousFields?.length === undefined ? false : (previousFields.length) === 0 && hasFields
const collapsed = hasFirstFieldAdded ? false : props.collapsed

const itemProps = {
...baseItemProps,
...additionalItemProps,
...{
collapsed
}
}

if (!hasFields) {
return <CollectionContentEmpty { ...props } />
}

const ContentComponent = props.type === 'tabs' ? CollectionContentTabs : CollectionContentList

return (
<BaseView
border={ props.border }
collapsed={ collapsed }
collapsible={ props.collapsible }
contentPadding={ props.border === true ? { x: 'none', top: 'small', bottom: 'none' } : 'small' }
theme='default'
title={ props.title }
>
<Space
className='w-full'
direction="vertical"
size={ 'small' }
>
{ props.fields.map((field) => {
return (
<ItemComponent
field={ field }
key={ field.name }
{ ...itemProps }
/>
)
}) }
</Space>
</BaseView>
<ContentComponent { ...props } />
)
}
Loading

0 comments on commit 594edb6

Please sign in to comment.