diff --git a/src/config.json b/src/config.json index 89d6d1e8c7..75d3b8862f 100644 --- a/src/config.json +++ b/src/config.json @@ -787,7 +787,7 @@ "v15": -1, "author": "vickyYe", "dd": false, - "v16": false + "v16": true }, { "version": "3.0.0", @@ -907,7 +907,7 @@ "v15": 2, "author": "dsj", "dd": true, - "v16": false + "v16": true }, { "version": "3.0.0", @@ -1090,7 +1090,7 @@ "v15": 1, "author": "swag~jun", "dd": true, - "v16": false + "v16": true }, { "version": "3.0.0", diff --git a/src/packages/actionsheet/__test__/actionsheet.spec.tsx b/src/packages/actionsheet/__test__/actionsheet.spec.tsx index f7a55eb862..5933d33872 100644 --- a/src/packages/actionsheet/__test__/actionsheet.spec.tsx +++ b/src/packages/actionsheet/__test__/actionsheet.spec.tsx @@ -1,7 +1,8 @@ import React from 'react' import { render, waitFor, fireEvent } from '@testing-library/react' import '@testing-library/jest-dom' -import { ActionSheet, ActionSheetOption } from '../actionsheet' +import { ActionSheetOption } from '@/types' +import ActionSheet from '@/packages/actionsheet' const menulist: ActionSheetOption[] = [ { @@ -127,3 +128,235 @@ test('props test click disabled item and not call fn', async () => { fireEvent.click(disableItem) await waitFor(() => expect(choose).not.toBeCalled()) }) + +const gridOptions: ActionSheetOption[] = [ + { name: '素材一', icon: 'https://img11.360buyimg.com/test1.png' }, + { name: '素材二', icon: icon }, + { name: '素材三' }, +] + +test('position top renders grid with correct item count', () => { + const { container } = render( + + ) + expect(container.querySelector('.nut-actionsheet-grid')).toBeTruthy() + const items = container.querySelectorAll('.nut-actionsheet-grid-item') + expect(items.length).toBe(3) +}) + +test('grid item keeps fixed width and column-gap adapts to columns', () => { + const { container } = render( + + ) + const grid = container.querySelector('.nut-actionsheet-grid') as HTMLElement + expect(grid.style.columnGap).toContain( + 'var(--nutui-actionsheet-grid-item-width, calc(50px * var(--nut-scale-f, 1)))' + ) + expect(grid.style.columnGap).toContain('/ 3') +}) + +test('grid icon renders img for string and node for ReactNode', () => { + const { container } = render( + + ) + const img = container.querySelector('.nut-actionsheet-grid-icon-img') + expect(img).toBeTruthy() + expect(img).toHaveAttribute('src', 'https://img11.360buyimg.com/test1.png') + expect(container.querySelector('.custom-icon')).toBeTruthy() +}) + +test('clicking grid item triggers onSelect with original item', async () => { + const choose = vi.fn() + const { container } = render( + + ) + const items = container.querySelectorAll('.nut-actionsheet-grid-item') + fireEvent.click(items[0]) + await waitFor(() => expect(choose.mock.calls[0][0].name).toEqual('素材一')) + expect(choose.mock.calls[0][1]).toEqual(0) +}) + +test('cancelText renders collapse button in top mode and triggers onCancel', async () => { + const cancel = vi.fn() + const { container } = render( + + ) + const collapse = container.querySelector('.nut-actionsheet-collapse') + expect(collapse).toBeTruthy() + expect(collapse).toHaveTextContent('点击收起') + expect( + container.querySelector('.nut-actionsheet-collapse-arrow') + ).toBeTruthy() + fireEvent.click(collapse as Element) + await waitFor(() => expect(cancel).toBeCalled()) +}) + +test('titleAlign center renders description, left hides it', () => { + const { container: centerContainer } = render( + + ) + expect( + centerContainer.querySelector('.nut-actionsheet-header-center') + ).toBeTruthy() + expect( + centerContainer.querySelector('.nut-actionsheet-header-description') + ).toHaveTextContent('描述信息') + + const { container: leftContainer } = render( + + ) + expect( + leftContainer.querySelector('.nut-actionsheet-header-left') + ).toBeTruthy() + expect( + leftContainer.querySelector('.nut-actionsheet-header-description') + ).toBeNull() +}) + +test('headerLeft and headerRight render into their slots', () => { + const { container } = render( + X} + headerRight={Y} + /> + ) + expect( + container.querySelector('.nut-actionsheet-header-slot-left .left-slot') + ).toBeTruthy() + expect( + container.querySelector('.nut-actionsheet-header-slot-right .right-slot') + ).toBeTruthy() +}) + +test('header slot takes precedence over closeable on the same side', () => { + const { container } = render( + ✓} + /> + ) + // 同侧冲突:关闭按钮不渲染,以 headerRight 槽为准 + expect(container.querySelector('.nut-popup-title-right')).toBeNull() + expect(container.querySelector('.right-slot')).toBeTruthy() +}) + +const listIconOptions: ActionSheetOption[] = [ + { name: '文案一', icon: 'https://img11.360buyimg.com/list1.png' }, + { name: '文案二', icon: gift }, + { name: '文案三' }, +] + +test('bottom list with icon enters icon-list mode', () => { + const { container } = render( + + ) + expect(container.querySelector('.nut-actionsheet-list-icon')).toBeTruthy() + expect(container.querySelector('.nut-actionsheet-item-icon')).toBeTruthy() +}) + +test('icon-list renders img for string and node for ReactNode', () => { + const { container } = render( + + ) + const img = container.querySelector('.nut-actionsheet-item-icon-img') + expect(img).toBeTruthy() + expect(img).toHaveAttribute('src', 'https://img11.360buyimg.com/list1.png') + expect(container.querySelector('.custom-list-icon')).toBeTruthy() +}) + +test('clicking icon-list item triggers onSelect with original item', async () => { + const choose = vi.fn() + const { container } = render( + + ) + const items = container.querySelectorAll( + '.nut-actionsheet-list .nut-actionsheet-item' + ) + fireEvent.click(items[0]) + await waitFor(() => expect(choose.mock.calls[0][0].name).toEqual('文案一')) + expect(choose.mock.calls[0][1]).toEqual(0) +}) + +test('bottom list without icon stays plain list', () => { + const { container } = render( + + ) + expect(container.querySelector('.nut-actionsheet-list')).toBeTruthy() + expect(container.querySelector('.nut-actionsheet-list-icon')).toBeNull() + expect(container.querySelector('.nut-actionsheet-item-icon')).toBeNull() +}) + +test('columns=4 adds cols-4 modifier, columns=5 does not', () => { + const { container: c4 } = render( + + ) + expect(c4.querySelector('.nut-actionsheet-grid-cols-4')).toBeTruthy() + + const { container: c5 } = render( + + ) + expect(c5.querySelector('.nut-actionsheet-grid')).toBeTruthy() + expect(c5.querySelector('.nut-actionsheet-grid-cols-4')).toBeNull() +}) + +test('layout=grid renders grid even when position is bottom', () => { + const { container } = render( + + ) + expect(container.querySelector('.nut-actionsheet-grid')).toBeTruthy() + expect(container.querySelector('.nut-actionsheet-list')).toBeNull() +}) + +test('layout=list renders list even when position is top', () => { + const { container } = render( + + ) + expect(container.querySelector('.nut-actionsheet-list')).toBeTruthy() + expect(container.querySelector('.nut-actionsheet-grid')).toBeNull() +}) + +test('layout defaults to grid for top and list for bottom', () => { + const { container: top } = render( + + ) + expect(top.querySelector('.nut-actionsheet-grid')).toBeTruthy() + + const { container: bottom } = render( + + ) + expect(bottom.querySelector('.nut-actionsheet-list')).toBeTruthy() +}) + +test('bottom grid with icons does not enter icon-list mode', () => { + const { container } = render( + + ) + expect(container.querySelector('.nut-actionsheet-grid')).toBeTruthy() + expect(container.querySelector('.nut-actionsheet-list-icon')).toBeNull() +}) diff --git a/src/packages/actionsheet/actionsheet.scss b/src/packages/actionsheet/actionsheet.scss index c850df3b0d..cb076c919e 100644 --- a/src/packages/actionsheet/actionsheet.scss +++ b/src/packages/actionsheet/actionsheet.scss @@ -8,27 +8,181 @@ background-color: $actionsheet-background-color; } - .nut-popup-title { - border-bottom: 1px solid $actionsheet-border-color; + &-header { + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + box-sizing: border-box; + padding: $actionsheet-header-padding; + + &-center { + align-items: center; + text-align: center; + } + + &-left { + align-items: flex-start; + text-align: left; + } + + &-title { + color: $actionsheet-title-color; + font-size: $actionsheet-title-font-size; + font-weight: $font-weight-bold; + line-height: $line-height-xxxl; + } + + &-description { + margin-top: 2px; + color: $actionsheet-description-color; + font-size: $actionsheet-description-font-size; + line-height: $line-height-base; + } + + &-slot-left, + &-slot-right { + position: absolute; + top: $actionsheet-header-padding; + display: flex; + align-items: center; + cursor: pointer; + } + + &-slot-left { + left: $actionsheet-header-padding; + } + + &-slot-right { + right: $actionsheet-header-padding; + } + } + + &-grid { + display: flex; + flex-wrap: wrap; + row-gap: $actionsheet-grid-gap; + box-sizing: border-box; + padding: $actionsheet-grid-padding; + } + + &-grid-cols-4 { + padding-right: scale-px(32px); + padding-left: scale-px(32px); + } + + &-grid-item { + display: flex; + flex-direction: column; + align-items: center; + box-sizing: border-box; + width: $actionsheet-grid-item-width; + cursor: pointer; + } + + &-grid-icon { + display: flex; + align-items: center; + justify-content: center; + } + + &-grid-icon-img { + width: scale-px(48px); + height: scale-px(48px); + object-fit: contain; + } + + &-grid-name { + margin-top: 8px; + margin-bottom: 2px; + color: $actionsheet-grid-text-color; + font-size: $actionsheet-grid-text-font-size; + line-height: $actionsheet-grid-text-line-height; + text-align: center; + } + + &-collapse { + display: flex; + align-items: center; + justify-content: center; + padding: 8px 0 12px; + color: $color-title; + font-size: $font-size-base; + cursor: pointer; + + &-arrow { + display: inline-block; + box-sizing: border-box; + width: 7px; + height: 7px; + margin-left: 10.5px; + margin-top: 1px; + border-top: 1px solid $color-title; + border-left: 1px solid $color-title; + transform: rotate(45deg); + } } &-list { display: block; list-style: none; - padding: 0; + padding: 0 16px; margin: 0; border-radius: $actionsheet-border-radius; } + &-list-icon { + .nut-actionsheet-item { + display: flex; + align-items: center; + box-sizing: border-box; + height: scale-px(48px); + padding: 0; + text-align: left; + } + + .nut-actionsheet-item-content { + display: flex; + flex: 1; + flex-direction: column; + justify-content: center; + min-width: 0; + } + + .nut-actionsheet-item-name, + .nut-actionsheet-item-description { + text-align: left; + } + } + + &-item-icon { + display: flex; + flex-shrink: 0; + align-items: center; + justify-content: center; + width: scale-px(14px); + height: scale-px(14px); + margin-right: 6px; + } + + &-item-icon-img { + width: 100%; + height: 100%; + object-fit: contain; + } + &-cancel, &-item { display: block; - padding: 10px; + padding: 13px 0; text-align: $actionsheet-item-text-align; - line-height: $actionsheet-item-line-height; font-size: $font-size-base; color: $actionsheet-item-color; cursor: pointer; + border-bottom: 1px solid $actionsheet-item-border-bottom; + &:last-child { + border-bottom: none; + } &-name { text-align: $actionsheet-item-text-align; line-height: $actionsheet-item-line-height; @@ -54,9 +208,10 @@ } &-cancel { - margin-top: 5px; - border-top: 1px solid $actionsheet-border-color; + padding-top: 0; border-radius: $actionsheet-border-radius; + line-height: scale-px(40px); + font-size: $font-size-md; } &-safe-area { @@ -68,3 +223,33 @@ /* #endif */ } } + +[dir='rtl'] .nut-actionsheet, +.nut-rtl .nut-actionsheet { + .nut-actionsheet-header { + &-left { + align-items: flex-end; + text-align: right; + } + + &-slot-left { + right: $actionsheet-header-padding; + left: auto; + } + + &-slot-right { + left: $actionsheet-header-padding; + right: auto; + } + } + + .nut-actionsheet-collapse-arrow { + margin-right: 10.5px; + margin-left: 0; + } + + .nut-actionsheet-item-icon { + margin-right: 0; + margin-left: 6px; + } +} diff --git a/src/packages/actionsheet/actionsheet.taro.tsx b/src/packages/actionsheet/actionsheet.taro.tsx index 3508bff770..cd8b739bb9 100644 --- a/src/packages/actionsheet/actionsheet.taro.tsx +++ b/src/packages/actionsheet/actionsheet.taro.tsx @@ -1,5 +1,5 @@ -import React, { FunctionComponent } from 'react' -import { View } from '@tarojs/components' +import React, { FunctionComponent, ReactNode } from 'react' +import { View, Image } from '@tarojs/components' import Popup from '@/packages/popup/index.taro' import { ComponentDefaults } from '@/utils/typings' import { mergeProps } from '@/utils/merge-props' @@ -9,8 +9,12 @@ const defaultProps = { ...ComponentDefaults, visible: false, description: '', + titleAlign: 'center', options: [], - optionKey: { name: 'name', description: 'description' }, + optionKey: { name: 'name', description: 'description', icon: 'icon' }, + columns: 5, + position: 'bottom', + closeIconPosition: 'top-right', cancelText: '', onCancel: () => {}, onSelect: () => {}, @@ -24,80 +28,208 @@ export const ActionSheet: FunctionComponent< cancelText, optionKey, title, + titleAlign, description, + headerLeft, + headerRight, options, + columns, + layout, onCancel, onSelect, visible, + position, + closeable, + closeIconPosition, className, style, ...rest } = mergeProps(defaultProps, props) const classPrefix = 'nut-actionsheet' + const isTop = position === 'top' + const effectiveLayout = layout ?? (position === 'top' ? 'grid' : 'list') - const cancelActionSheet = () => { - onCancel && onCancel() - } - - const chooseItem = ( - item: ActionSheetOption, - index: number - ) => { + const chooseItem = (item: ActionSheetOption, index: number) => { if (!item.disabled) { onSelect && onSelect(item, index) } } + const nameKey = optionKey?.name || 'name' + const descriptionKey = optionKey?.description || 'description' + const iconKey = optionKey?.icon || 'icon' + + const hasIcon = (icon: ReactNode) => + icon !== undefined && icon !== null && icon !== '' + const hasListIcon = + effectiveLayout === 'list' && options.some((item) => hasIcon(item[iconKey])) + + // headerLeft/headerRight 为自定义槽,与默认关闭按钮同侧冲突时以槽位为准 + const closeAtLeft = closeIconPosition === 'top-left' + const showClose = closeable && (closeAtLeft ? !headerLeft : !headerRight) + + const renderHeader = () => { + if (!title && !description && !headerLeft && !headerRight) return null + const showDescription = titleAlign === 'center' && description + return ( + + {headerLeft && ( + + {headerLeft} + + )} + {title && ( + {title} + )} + {showDescription && ( + + {description} + + )} + {headerRight && ( + + {headerRight} + + )} + + ) + } + + const renderIcon = (icon: ReactNode) => { + if (!hasIcon(icon)) return null + const content = + typeof icon === 'string' ? ( + + ) : ( + icon + ) + return {content} + } + + const renderListIcon = (icon: ReactNode) => { + if (!hasIcon(icon)) return null + const content = + typeof icon === 'string' ? ( + + ) : ( + icon + ) + return {content} + } + + const renderGrid = () => { + const cols = Number(columns) === 4 ? 4 : 5 + const itemWidth = + 'var(--nutui-actionsheet-grid-item-width, calc(50px * var(--nut-scale-f, 1)))' + const columnGap = + cols > 1 ? `calc((100% - ${cols} * ${itemWidth}) / ${cols - 1})` : '0' + return ( + + {options.map((item, index) => ( + chooseItem(item, index)} + > + {renderIcon(item[iconKey])} + {item[nameKey]} + + ))} + + ) + } + + const renderList = () => ( + + {options.map((item, index) => { + const statusClass = `${item.disabled ? `${classPrefix}-item-disabled` : ''} ${item.danger ? `${classPrefix}-item-danger` : ''}` + const textBlock = ( + <> + + {item[nameKey]} + + + {item[descriptionKey]} + + + ) + return ( + chooseItem(item, index)} + > + {hasListIcon ? ( + <> + {renderListIcon(item[iconKey])} + + {textBlock} + + + ) : ( + textBlock + )} + + ) + })} + + ) + + const renderContent = () => { + if (!options.length) return children + return effectiveLayout === 'grid' ? renderGrid() : renderList() + } + + const renderCancel = () => { + if (!cancelText) return null + if (isTop) { + return ( + onCancel && onCancel()} + > + {cancelText} + + + ) + } + return ( + onCancel && onCancel()} + > + {cancelText} + + ) + } + return ( { onCancel && onCancel() }} > - - {options.length ? ( - - {options.map((item, index) => { - const statusClass = `${item.disabled ? `${classPrefix}-item-disabled` : ''} ${item.danger ? `${classPrefix}-item-danger` : ''}` - return ( - chooseItem(item, index)} - > - - {item[optionKey.name]} - - - {item[optionKey.description]} - - - ) - })} - - ) : ( - children - )} - {cancelText && ( - cancelActionSheet()} - > - {cancelText} - - )} + + {renderHeader()} + {renderContent()} + {renderCancel()} - + {isTop ? null : } ) } diff --git a/src/packages/actionsheet/actionsheet.tsx b/src/packages/actionsheet/actionsheet.tsx index 41904455a9..587a3ee441 100644 --- a/src/packages/actionsheet/actionsheet.tsx +++ b/src/packages/actionsheet/actionsheet.tsx @@ -1,4 +1,4 @@ -import React, { FunctionComponent } from 'react' +import React, { FunctionComponent, ReactNode } from 'react' import Popup from '@/packages/popup/index' import { ComponentDefaults } from '@/utils/typings' import { mergeProps } from '@/utils/merge-props' @@ -8,8 +8,12 @@ const defaultProps = { ...ComponentDefaults, visible: false, description: '', + titleAlign: 'center', options: [], - optionKey: { name: 'name', description: 'description' }, + optionKey: { name: 'name', description: 'description', icon: 'icon' }, + columns: 5, + position: 'bottom', + closeIconPosition: 'top-right', cancelText: '', onCancel: () => {}, onSelect: () => {}, @@ -23,80 +27,202 @@ export const ActionSheet: FunctionComponent< cancelText, optionKey, title, + titleAlign, description, + headerLeft, + headerRight, options, + columns, + layout, onCancel, onSelect, visible, + position, + closeable, + closeIconPosition, className, style, ...rest } = mergeProps(defaultProps, props) const classPrefix = 'nut-actionsheet' + const isTop = position === 'top' + const effectiveLayout = layout ?? (position === 'top' ? 'grid' : 'list') - const cancelActionSheet = () => { - onCancel && onCancel() - } - - const chooseItem = ( - item: ActionSheetOption, - index: number - ) => { + const chooseItem = (item: ActionSheetOption, index: number) => { if (!item.disabled) { onSelect && onSelect(item, index) } } + const nameKey = optionKey?.name || 'name' + const descriptionKey = optionKey?.description || 'description' + const iconKey = optionKey?.icon || 'icon' + + const hasIcon = (icon: ReactNode) => + icon !== undefined && icon !== null && icon !== '' + const hasListIcon = + effectiveLayout === 'list' && options.some((item) => hasIcon(item[iconKey])) + + // headerLeft/headerRight 为自定义槽,与默认关闭按钮同侧冲突时以槽位为准 + const closeAtLeft = closeIconPosition === 'top-left' + const showClose = closeable && (closeAtLeft ? !headerLeft : !headerRight) + + const renderHeader = () => { + if (!title && !description && !headerLeft && !headerRight) return null + const showDescription = titleAlign === 'center' && description + return ( +
+ {headerLeft && ( +
{headerLeft}
+ )} + {title &&
{title}
} + {showDescription && ( +
+ {description} +
+ )} + {headerRight && ( +
+ {headerRight} +
+ )} +
+ ) + } + + const renderIcon = (icon: ReactNode) => { + if (!hasIcon(icon)) return null + const content = + typeof icon === 'string' ? ( + + ) : ( + icon + ) + return
{content}
+ } + + const renderListIcon = (icon: ReactNode) => { + if (!hasIcon(icon)) return null + const content = + typeof icon === 'string' ? ( + + ) : ( + icon + ) + return
{content}
+ } + + const renderGrid = () => { + const cols = Number(columns) === 4 ? 4 : 5 + const itemWidth = + 'var(--nutui-actionsheet-grid-item-width, calc(50px * var(--nut-scale-f, 1)))' + const columnGap = + cols > 1 ? `calc((100% - ${cols} * ${itemWidth}) / ${cols - 1})` : '0' + return ( +
+ {options.map((item, index) => ( +
chooseItem(item, index)} + > + {renderIcon(item[iconKey])} +
{item[nameKey]}
+
+ ))} +
+ ) + } + + const renderList = () => ( +
+ {options.map((item, index) => { + const statusClass = `${item.disabled ? `${classPrefix}-item-disabled` : ''} ${item.danger ? `${classPrefix}-item-danger` : ''}` + const textBlock = ( + <> +
+ {item[nameKey]} +
+
+ {item[descriptionKey]} +
+ + ) + return ( +
chooseItem(item, index)} + > + {hasListIcon ? ( + <> + {renderListIcon(item[iconKey])} +
{textBlock}
+ + ) : ( + textBlock + )} +
+ ) + })} +
+ ) + + const renderContent = () => { + if (!options.length) return children + return effectiveLayout === 'grid' ? renderGrid() : renderList() + } + + const renderCancel = () => { + if (!cancelText) return null + if (isTop) { + return ( +
onCancel && onCancel()} + > + {cancelText} + +
+ ) + } + return ( +
onCancel && onCancel()} + > + {cancelText} +
+ ) + } + return ( { onCancel && onCancel() }} > -
- {options.length ? ( -
- {options.map((item, index) => { - const statusClass = `${item.disabled ? `${classPrefix}-item-disabled` : ''} ${item.danger ? `${classPrefix}-item-danger` : ''}` - return ( -
chooseItem(item, index)} - > -
- {item[optionKey.name]} -
-
- {item[optionKey.description]} -
-
- ) - })} -
- ) : ( - children - )} - {cancelText && ( -
cancelActionSheet()} - > - {cancelText} -
- )} +
+ {renderHeader()} + {renderContent()} + {renderCancel()}
-
+ {isTop ? null :
} ) } diff --git a/src/packages/actionsheet/demo.taro.tsx b/src/packages/actionsheet/demo.taro.tsx index 0904583460..8abf5d11cb 100644 --- a/src/packages/actionsheet/demo.taro.tsx +++ b/src/packages/actionsheet/demo.taro.tsx @@ -10,6 +10,7 @@ import Demo3 from './demos/taro/demo3' import Demo4 from './demos/taro/demo4' import Demo5 from './demos/taro/demo5' import Demo6 from './demos/taro/demo6' +import Demo7 from './demos/taro/demo7' const ActionSheetDemo = () => { const [translated] = useTranslate({ @@ -18,18 +19,21 @@ const ActionSheetDemo = () => { c3a08064: '选项状态', c3a08065: '自定义内容', c3a08066: '自定义key', + c3a08067: '顶部弹出', }, 'zh-TW': { '74fc5d8a': '基礎用法', c3a08064: '選項狀態', c3a08065: '自定義內容', c3a08066: '自定義key', + c3a08067: '頂部彈出', }, 'en-US': { '74fc5d8a': 'Basic Usage', c3a08064: 'Option Status', c3a08065: 'Custom content', c3a08066: 'Custom key', + c3a08067: 'Top Popup', }, }) @@ -47,6 +51,8 @@ const ActionSheetDemo = () => { {translated.c3a08066} + {translated.c3a08067} + ) diff --git a/src/packages/actionsheet/demo.tsx b/src/packages/actionsheet/demo.tsx index db9d0fcf43..8c93443d43 100644 --- a/src/packages/actionsheet/demo.tsx +++ b/src/packages/actionsheet/demo.tsx @@ -7,6 +7,7 @@ import Demo3 from './demos/h5/demo3' import Demo4 from './demos/h5/demo4' import Demo5 from './demos/h5/demo5' import Demo6 from './demos/h5/demo6' +import Demo7 from './demos/h5/demo7' const ActionSheetDemo = () => { const [translated] = useTranslate({ @@ -15,18 +16,21 @@ const ActionSheetDemo = () => { c3a08064: '选项状态', c3a08065: '自定义内容', c3a08066: '自定义key', + c3a08067: '顶部弹出', }, 'zh-TW': { '74fc5d8a': '基礎用法', c3a08064: '選項狀態', c3a08065: '自定義內容', c3a08066: '自定義key', + c3a08067: '頂部彈出', }, 'en-US': { '74fc5d8a': 'Basic Usage', c3a08064: 'Option Status', c3a08065: 'Custom content', c3a08066: 'Custom key', + c3a08067: 'Top Popup', }, }) @@ -43,6 +47,8 @@ const ActionSheetDemo = () => {

{translated.c3a08066}

+

{translated.c3a08067}

+
) diff --git a/src/packages/actionsheet/demos/h5/demo1.tsx b/src/packages/actionsheet/demos/h5/demo1.tsx index 6b36ced9bc..bda841bf22 100644 --- a/src/packages/actionsheet/demos/h5/demo1.tsx +++ b/src/packages/actionsheet/demos/h5/demo1.tsx @@ -1,9 +1,12 @@ import React, { useState } from 'react' import { ActionSheet, Cell } from '@nutui/nutui-react' +import { Gift } from '@nutui/icons-react' const Demo1 = () => { const [val, setVal] = useState('') const [isVisible, setIsVisible] = useState(false) + const [val2, setVal2] = useState('') + const [isVisible2, setIsVisible2] = useState(false) const options = [ { name: '权限设置', @@ -15,12 +18,22 @@ const Demo1 = () => { name: '删除', }, ] + const iconOptions = [ + { name: '文案内容', icon: }, + { name: '文案内容', icon: }, + { name: '文案内容', icon: }, + ] const handleSelect = (item: any) => { setVal(item.name) setIsVisible(false) } + const handleSelect2 = (item: any) => { + setVal2(item.name) + setIsVisible2(false) + } + return ( <> setIsVisible(!isVisible)}> @@ -34,6 +47,17 @@ const Demo1 = () => { onSelect={handleSelect} onCancel={() => setIsVisible(false)} /> + setIsVisible2(!isVisible2)}> + 图标列表 +
{val2}
+
+ setIsVisible2(false)} + /> ) } diff --git a/src/packages/actionsheet/demos/h5/demo5.tsx b/src/packages/actionsheet/demos/h5/demo5.tsx index 678032affa..6cd1ad0377 100644 --- a/src/packages/actionsheet/demos/h5/demo5.tsx +++ b/src/packages/actionsheet/demos/h5/demo5.tsx @@ -1,23 +1,156 @@ import React, { useState } from 'react' -import { ActionSheet, Cell } from '@nutui/nutui-react' +import { ActionSheet, Cell, Checkbox } from '@nutui/nutui-react' +import { Close, Check } from '@nutui/icons-react' + +const options = [ + { value: '1', text: '文案内容' }, + { value: '2', text: '文案内容' }, + { value: '3', text: '文案内容' }, +] + +const rowStyle: (last: boolean) => React.CSSProperties = (last) => ({ + height: '48px', + marginBottom: 0, + boxSizing: 'content-box', + borderBottom: last + ? 'none' + : '0.5px solid var(--nutui-color-border, rgba(0, 0, 0, 0.06))', +}) + +const titleStyle: React.CSSProperties = { + padding: '6px 8px', + borderRadius: '6px', + backgroundColor: 'var(--nutui-color-background-component, #f0f2f7)', + color: 'var(--nutui-color-text)', + fontSize: 11, + lineHeight: '16px', + fontWeight: 400, +} const Demo5 = () => { - const [isVisible, setIsVisible] = useState(false) + const [visibleLeft, setVisibleLeft] = useState(false) + const [visibleRight, setVisibleRight] = useState(false) + const [valueLeft, setValueLeft] = useState(['1']) + const [valueRight, setValueRight] = useState(['2']) + const lastValueLeftRef = React.useRef(valueLeft) + const lastValueRightRef = React.useRef(valueRight) + return ( <> - setIsVisible(!isVisible)}> - 自定义内容 + setVisibleLeft(true)}> + 勾选框居左 + + setValueLeft([])}> + 清空筛选 + + } + headerLeft={ + { + setVisibleLeft(false) + setTimeout(() => setValueLeft(lastValueLeftRef.current), 200) + }} + /> + } + headerRight={ + { + setVisibleLeft(false) + lastValueLeftRef.current = valueLeft + }} + /> + } + onCancel={() => { + setVisibleLeft(false) + setTimeout(() => setValueLeft(lastValueLeftRef.current), 200) + }} + > + + {options.map((opt, idx) => ( + + ))} + + + + setVisibleRight(true)}> + 勾选框居右 { - setIsVisible(false) + visible={visibleRight} + title={ + setValueRight([])}> + 清空筛选 + + } + headerLeft={ + { + setVisibleRight(false) + setTimeout(() => setValueRight(lastValueRightRef.current), 200) + }} + /> + } + headerRight={ + { + setVisibleRight(false) + lastValueRightRef.current = valueRight + }} + /> + } + onCancel={() => { + setVisibleRight(false) + setTimeout(() => setValueRight(lastValueRightRef.current), 200) }} - onCancel={() => setIsVisible(false)} > -
新建表格
-
新建文档
+ + {options.map((opt, idx) => { + const active = valueRight.includes(opt.value) + return ( + + {active ? '选中' : '未选中'} + + } + /> + ) + })} +
) diff --git a/src/packages/actionsheet/demos/h5/demo7.tsx b/src/packages/actionsheet/demos/h5/demo7.tsx new file mode 100644 index 0000000000..f5bdb258a7 --- /dev/null +++ b/src/packages/actionsheet/demos/h5/demo7.tsx @@ -0,0 +1,61 @@ +import React, { useState } from 'react' +import { ActionSheet, Cell } from '@nutui/nutui-react' + +const Demo7 = () => { + const [isVisible, setIsVisible] = useState(false) + const [isGridBottom, setIsGridBottom] = useState(false) + const icon = + 'https://img11.360buyimg.com/img/jfs/t1/507123/33/12779/9964/6a9a601cFbad350ca/0276060060ce38e1.png' + const options = [ + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + ] + const gridOptions = [ + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + ] + + return ( + <> + setIsVisible(!isVisible)}> + 顶部弹出 + + setIsVisible(false)} + onCancel={() => setIsVisible(false)} + /> + setIsGridBottom(!isGridBottom)}> + 底部网格 + + setIsGridBottom(false)} + onCancel={() => setIsGridBottom(false)} + /> + + ) +} +export default Demo7 diff --git a/src/packages/actionsheet/demos/taro/demo1.tsx b/src/packages/actionsheet/demos/taro/demo1.tsx index 483ba8f250..f1516a5c2c 100644 --- a/src/packages/actionsheet/demos/taro/demo1.tsx +++ b/src/packages/actionsheet/demos/taro/demo1.tsx @@ -1,10 +1,13 @@ import React, { useState } from 'react' import { ActionSheet, Cell, pxTransform } from '@nutui/nutui-react-taro' +import { Gift } from '@nutui/icons-react-taro' import { View } from '@tarojs/components' const Demo1 = () => { const [val, setVal] = useState('') const [isVisible, setIsVisible] = useState(false) + const [val2, setVal2] = useState('') + const [isVisible2, setIsVisible2] = useState(false) const options = [ { name: '权限设置', @@ -16,12 +19,22 @@ const Demo1 = () => { name: '删除', }, ] + const iconOptions = [ + { name: '文案内容', icon: }, + { name: '文案内容', icon: }, + { name: '文案内容', icon: }, + ] const handleSelect = (item: any) => { setVal(item.name) setIsVisible(false) } + const handleSelect2 = (item: any) => { + setVal2(item.name) + setIsVisible2(false) + } + return ( <> setIsVisible(!isVisible)}> @@ -37,6 +50,19 @@ const Demo1 = () => { onSelect={handleSelect} onCancel={() => setIsVisible(false)} /> + setIsVisible2(!isVisible2)}> + 图标列表 + + {val2} + + + setIsVisible2(false)} + /> ) } diff --git a/src/packages/actionsheet/demos/taro/demo5.tsx b/src/packages/actionsheet/demos/taro/demo5.tsx index 4d8c230170..cc8c86713b 100644 --- a/src/packages/actionsheet/demos/taro/demo5.tsx +++ b/src/packages/actionsheet/demos/taro/demo5.tsx @@ -1,29 +1,168 @@ -import React, { CSSProperties, useState } from 'react' -import { ActionSheet, Cell, pxTransform } from '@nutui/nutui-react-taro' +import React, { useState } from 'react' +import { + ActionSheet, + Cell, + Checkbox, + pxTransform, +} from '@nutui/nutui-react-taro' +import { Close, Check } from '@nutui/icons-react-taro' import { View } from '@tarojs/components' +const options = [ + { value: '1', text: '文案内容' }, + { value: '2', text: '文案内容' }, + { value: '3', text: '文案内容' }, +] + +const rowStyle: (last: boolean) => React.CSSProperties = (last) => ({ + height: pxTransform(48), + marginBottom: 0, + boxSizing: 'content-box', + borderBottom: last + ? 'none' + : `${pxTransform(0.5)} solid var(--nutui-color-border, rgba(0, 0, 0, 0.06))`, +}) + +const titleStyle: React.CSSProperties = { + padding: `${pxTransform(6)} ${pxTransform(8)}`, + borderRadius: pxTransform(6), + backgroundColor: 'var(--nutui-color-background-component, #f0f2f7)', + color: 'var(--nutui-color-text)', + fontSize: pxTransform(11), + lineHeight: pxTransform(16), + fontWeight: 400, +} + const Demo5 = () => { - const [isVisible, setIsVisible] = useState(false) - const viewStyle: CSSProperties = { - textAlign: 'left', - paddingLeft: pxTransform(20), - paddingTop: pxTransform(10), - } + const [visibleLeft, setVisibleLeft] = useState(false) + const [visibleRight, setVisibleRight] = useState(false) + const [valueLeft, setValueLeft] = useState(['1']) + const [valueRight, setValueRight] = useState(['2']) + const lastValueLeftRef = React.useRef(valueLeft) + const lastValueRightRef = React.useRef(valueRight) + return ( <> - setIsVisible(!isVisible)}> - 自定义内容 + setVisibleLeft(true)}> + 勾选框居左 + + setValueLeft([])}> + 清空筛选 + + } + headerLeft={ + { + setVisibleLeft(false) + setTimeout(() => setValueLeft(lastValueLeftRef.current), 200) + }} + /> + } + headerRight={ + { + setVisibleLeft(false) + lastValueLeftRef.current = valueLeft + }} + /> + } + onCancel={() => { + setVisibleLeft(false) + setTimeout(() => setValueLeft(lastValueLeftRef.current), 200) + }} + > + + {options.map((opt, idx) => ( + + ))} + + + + setVisibleRight(true)}> + 勾选框居右 { - setIsVisible(false) + visible={visibleRight} + title={ + setValueRight([])}> + 清空筛选 + + } + headerLeft={ + { + setVisibleRight(false) + setTimeout(() => setValueRight(lastValueRightRef.current), 200) + }} + /> + } + headerRight={ + { + setVisibleRight(false) + lastValueRightRef.current = valueRight + }} + /> + } + onCancel={() => { + setVisibleRight(false) + setTimeout(() => setValueRight(lastValueRightRef.current), 200) }} - onCancel={() => setIsVisible(false)} > - 新建表格 - 新建文档 + + {options.map((opt, idx) => { + const active = valueRight.includes(opt.value) + return ( + + {active ? '选中' : '未选中'} + + } + /> + ) + })} + ) diff --git a/src/packages/actionsheet/demos/taro/demo7.tsx b/src/packages/actionsheet/demos/taro/demo7.tsx new file mode 100644 index 0000000000..b9e275c802 --- /dev/null +++ b/src/packages/actionsheet/demos/taro/demo7.tsx @@ -0,0 +1,62 @@ +import React, { useState } from 'react' +import { ActionSheet, Cell } from '@nutui/nutui-react-taro' +import { View } from '@tarojs/components' + +const Demo7 = () => { + const [isVisible, setIsVisible] = useState(false) + const [isGridBottom, setIsGridBottom] = useState(false) + const icon = + 'https://img11.360buyimg.com/img/jfs/t1/507123/33/12779/9964/6a9a601cFbad350ca/0276060060ce38e1.png' + const options = [ + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + ] + const gridOptions = [ + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + { name: '素材名称', icon }, + ] + + return ( + <> + setIsVisible(!isVisible)}> + 顶部弹出 + + setIsVisible(false)} + onCancel={() => setIsVisible(false)} + /> + setIsGridBottom(!isGridBottom)}> + 底部网格 + + setIsGridBottom(false)} + onCancel={() => setIsGridBottom(false)} + /> + + ) +} +export default Demo7 diff --git a/src/packages/actionsheet/doc.en-US.md b/src/packages/actionsheet/doc.en-US.md index 8eb4c2b979..66f395f0e7 100644 --- a/src/packages/actionsheet/doc.en-US.md +++ b/src/packages/actionsheet/doc.en-US.md @@ -1,93 +1,134 @@ -# ActionSheet - -Action menu panel that pops up from the bottom. - -## Import - -```tsx -import { ActionSheet } from '@nutui/nutui-react' -``` - -## Demo - -### Basic usage - -:::demo - - - -::: - -### Show Cancel Button - -:::demo - - - -::: - -### Display Description Information - -:::demo - - - -::: - -### Option Status - -:::demo - - - -::: - -### Custom content - -:::demo - - - -::: - -### Custom key - -:::demo - - - -::: - -## ActionSheet - -### Props - -| Property | Description | Type | Default | -| --- | --- | --- | --- | -| visible | Mask layer visible | `boolean` | `false` | -| title | Set panel title | `string` | `-` | -| description | Set panel subtitle/description | `string` | `-` | -| cancelText | Cancel Text | `string` | `Cancel` | -| options | Menu Item | `Array` | `[]` | -| optionKey | Menu Item Custom key | `{ [key: string]: string }` | `-` | -| onSelect | Triggered after selection | `(item: any, index: number) => void` | `-` | -| onCancel | Triggered when onCancel copy is clicked | `() => void` | `-` | - -## Theming - -### CSS Variables - -The component provides the following CSS variables, which can be used to customize styles. Please refer to [ConfigProvider component](#/en-US/component/configprovider). - -| Name | Description | Default Value | -| --- | --- | --- | -| \--nutui-actionsheet-background-color | the backgroundColor of actionsheet panel | `$color-background-overlay` | -| \--nutui-actionsheet-border-radius | the borderRadius of list and cancel button | `0` | -| \--nutui-actionsheet-border-color | title border-bottom and cancle border-top | `#F7F8FC` | -| \--nutui-actionsheet-item-text-align | item text align | `center` | -| \--nutui-actionsheet-item-border-bottom | item border bottom | `none` | -| \--nutui-actionsheet-item-line-height | item line height | `24px` | -| \--nutui-actionsheet-item-color | item color | `$color-title` | -| \--nutui-actionsheet-item-danger | item danger color | `$color-primary` | - - +# ActionSheet + +Action menu panel that pops up from the top or bottom. + +## Import + +```tsx +import { ActionSheet } from '@nutui/nutui-react' +``` + +## Demo + +### Basic usage + +Displayed as a list when popping up from the bottom. When any item in `options` has an `icon` field, the list switches to a left-aligned icon-list layout. + +:::demo + + + +::: + +### Show Cancel Button + +:::demo + + + +::: + +### Display Description Information + +:::demo + + + +::: + +### Option Status + +:::demo + + + +::: + +### Custom content + +Customize the panel content via `children`. Here `Checkbox` is reused to build a multi-select filter list, supporting both checkbox-left and checkbox-right layouts. Click the "清空筛选" title to clear the current selection. + +:::demo + + + +::: + +### Custom key + +:::demo + + + +::: + +### Top Popup + +Pop up from the top via `position="top"`. Content is displayed as a grid, and `options` support an `icon` field (a string is rendered with `img`, or a custom node can be passed). The grid layout can also be used for bottom popups via `layout="grid"`; `columns` only supports `4` or `5`, and `4` gives wider horizontal spacing. + +:::demo + + + +::: + +## ActionSheet + +### Props + +| Property | Description | Type | Default | +| --- | --- | --- | --- | +| visible | Mask layer visible | `boolean` | `false` | +| title | Set panel title | `ReactNode` | `-` | +| titleAlign | Title alignment, `left` \| `center`; `description` only takes effect when `center` | `string` | `center` | +| description | Set panel subtitle/description | `ReactNode` | `-` | +| headerLeft | Custom content on the left of the header | `ReactNode` | `-` | +| headerRight | Custom content on the right of the header | `ReactNode` | `-` | +| position | Popup position, `top` \| `bottom`; displayed as a grid when `top` | `string` | `bottom` | +| layout | Content layout; inferred from `position` when omitted (`top`→`grid`, `bottom`→`list`) | `grid` \| `list` | `-` | +| cancelText | Cancel Text, rendered as a "collapse" button when `top` | `ReactNode` | `Cancel` | +| options | Menu Item | `Array` | `[]` | +| optionKey | Menu Item Custom key | `{ [key: string]: string }` | `-` | +| columns | Grid columns, only supports `4` or `5` | `4` \| `5` | `5` | +| closeable | Whether to show the close button | `boolean` | `false` | +| onSelect | Triggered after selection | `(item: any, index: number) => void` | `-` | +| onCancel | Triggered when onCancel copy is clicked | `() => void` | `-` | + +### options + +| Property | Description | Type | Default | +| --- | --- | --- | --- | +| name | Title key of the menu item | `string` | `-` | +| description | Description key of the menu item | `string` | `-` | +| icon | Icon key of the menu item, supported in both top grid and bottom list; the bottom list switches to icon-list layout when `icon` is present | `ReactNode` \| `string` | `-` | +| danger | Highlight color | `string` | `$color-primary` | +| disabled | Disabled status | `string` | `$disabled-color` | + +## Theming + +### CSS Variables + +The component provides the following CSS variables, which can be used to customize styles. Please refer to [ConfigProvider component](#/en-US/component/configprovider). + +| Name | Description | Default Value | +| --- | --- | --- | +| \--nutui-actionsheet-background-color | the backgroundColor of actionsheet panel | `$color-background-overlay` | +| \--nutui-actionsheet-border-radius | the borderRadius of list and cancel button | `0` | +| \--nutui-actionsheet-item-text-align | item text align | `center` | +| \--nutui-actionsheet-item-border-bottom | item border bottom | `$color-border` | +| \--nutui-actionsheet-item-line-height | item line height | `24px` | +| \--nutui-actionsheet-item-color | item color | `$color-title` | +| \--nutui-actionsheet-item-danger | item danger color | `$color-primary` | +| \--nutui-actionsheet-header-padding | header padding | `16px` | +| \--nutui-actionsheet-title-color | header title color | `$color-title` | +| \--nutui-actionsheet-title-font-size | header title font size | `$font-size-xl` | +| \--nutui-actionsheet-description-color | header description color | `$color-text-help` | +| \--nutui-actionsheet-description-font-size | header description font size | `$font-size-s` | +| \--nutui-actionsheet-grid-padding | grid container padding | `16px` | +| \--nutui-actionsheet-grid-gap | grid item gap | `16px` | +| \--nutui-actionsheet-grid-item-width | grid item width | `50px` | +| \--nutui-actionsheet-grid-text-color | grid text color | `$color-text` | +| \--nutui-actionsheet-grid-text-font-size | grid text font size | `12px` | +| \--nutui-actionsheet-grid-text-line-height | grid text line height | `18px` | + + diff --git a/src/packages/actionsheet/doc.md b/src/packages/actionsheet/doc.md index 2c2a93bc21..9060465180 100644 --- a/src/packages/actionsheet/doc.md +++ b/src/packages/actionsheet/doc.md @@ -1,102 +1,134 @@ -# ActionSheet 动作面板 - -从底部弹出的动作菜单面板。 - -## 引入 - -```tsx -import { ActionSheet } from '@nutui/nutui-react' -``` - -## 示例代码 - -### 基础用法 - -:::demo - - - -::: - -### 展示取消按钮 - -:::demo - - - -::: - -### 展示描述信息 - -:::demo - - - -::: - -### 选项状态 - -:::demo - - - -::: - -### 自定义内容 - -:::demo - - - -::: - -### 自定义key - -:::demo - - - -::: - -## ActionSheet - -### Props - -| 属性 | 说明 | 类型 | 默认值 | -| --- | --- | --- | --- | -| visible | 遮罩层可见 | `boolean` | `false` | -| title | 设置列表面板标题 | `string` | `-` | -| description | 设置列表面板副标题/描述 | `string` | `-` | -| options | 列表项 | `Array` | `[]` | -| optionKey | 列表项的自定义设置 | `{ [key: string]: string }` | `-` | -| cancelText | 取消文案 | `string` | `取消` | -| onSelect | 选择之后触发 | `(item: any, index: number) => void` | `-` | -| onCancel | 点击取消文案时触发 | `() => void` | `-` | - -### options - -| 属性 | 说明 | 类型 | 默认值 | -| --- | --- | --- | --- | -| name | 列表项的标题key值 | `string` | `-` | -| description | 列表项的描述key值 | `string` | `-` | -| danger | 高亮颜色 | `string` | `$color-primary` | -| disabled | 禁用状态 | `string` | `$disabled-color` | - -## 主题定制 - -### 样式变量 - -组件提供了下列 CSS 变量,可用于自定义样式,使用方法请参考 [ConfigProvider 组件](#/zh-CN/component/configprovider)。 - -| 名称 | 说明 | 默认值 | -| --- | --- | --- | -| \--nutui-actionsheet-background-color | 背景色 | `$color-background-overlay` | -| \--nutui-actionsheet-border-radius | 列表和取消按钮圆角 | `0` | -| \--nutui-actionsheet-border-color | 标题和取消位置的border色值 | `#F7F8FC` | -| \--nutui-actionsheet-item-text-align | 列表项的文字对齐方式 | `center` | -| \--nutui-actionsheet-item-border-bottom | 列表项的底部border | `none` | -| \--nutui-actionsheet-item-line-height | 列表项行高 | `24px` | -| \--nutui-actionsheet-item-color | 列表项字色 | `$color-title` | -| \--nutui-actionsheet-item-danger | 列表项danger字色 | `$color-primary` | - - +# ActionSheet 动作面板 + +从顶部或底部弹出的动作菜单面板。 + +## 引入 + +```tsx +import { ActionSheet } from '@nutui/nutui-react' +``` + +## 示例代码 + +### 基础用法 + +底部弹出时以列表展示。当 `options` 中存在 `icon` 字段时,列表自动切换为左对齐的图标列表布局。 + +:::demo + + + +::: + +### 展示取消按钮 + +:::demo + + + +::: + +### 展示描述信息 + +:::demo + + + +::: + +### 选项状态 + +:::demo + + + +::: + +### 自定义内容 + +通过 `children` 自定义面板内容。此处复用 `Checkbox` 实现清空筛选的多选列表,支持勾选框居左、居右两种布局,点击标题「清空筛选」可清空已选项。 + +:::demo + + + +::: + +### 自定义key + +:::demo + + + +::: + +### 顶部弹出 + +通过 `position="top"` 从顶部弹出,内容以网格形式展示,`options` 支持 `icon` 字段(字符串使用 `img` 渲染,也可传入自定义节点)。网格布局也可通过 `layout="grid"` 用于底部弹出;`columns` 仅支持 `4` 或 `5`,为 `4` 时左右间距更宽。 + +:::demo + + + +::: + +## ActionSheet + +### Props + +| 属性 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| visible | 遮罩层可见 | `boolean` | `false` | +| title | 设置列表面板标题 | `ReactNode` | `-` | +| titleAlign | 标题对齐方式,`left` \| `center`,仅 `center` 时 `description` 生效 | `string` | `center` | +| description | 设置列表面板副标题/描述 | `ReactNode` | `-` | +| headerLeft | 头部左侧自定义内容 | `ReactNode` | `-` | +| headerRight | 头部右侧自定义内容 | `ReactNode` | `-` | +| position | 弹出位置,`top` \| `bottom`,`top` 时以网格展示 | `string` | `bottom` | +| layout | 内容布局方式,不传时按 `position` 推导(`top`→`grid`、`bottom`→`list`) | `grid` \| `list` | `-` | +| options | 列表项 | `Array` | `[]` | +| optionKey | 列表项的自定义设置 | `{ [key: string]: string }` | `-` | +| columns | 网格列数,仅支持 `4` 或 `5` | `4` \| `5` | `5` | +| cancelText | 取消文案,`top` 时渲染为「点击收起」按钮 | `ReactNode` | `取消` | +| closeable | 是否显示关闭按钮 | `boolean` | `false` | +| onSelect | 选择之后触发 | `(item: any, index: number) => void` | `-` | +| onCancel | 点击取消文案时触发 | `() => void` | `-` | + +### options + +| 属性 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| name | 列表项的标题key值 | `string` | `-` | +| description | 列表项的描述key值 | `string` | `-` | +| icon | 列表项的图标key值,顶部网格与底部列表均支持,底部列表存在 `icon` 时自动切换为图标列表布局 | `ReactNode` \| `string` | `-` | +| danger | 高亮颜色 | `string` | `$color-primary` | +| disabled | 禁用状态 | `string` | `$disabled-color` | + +## 主题定制 + +### 样式变量 + +组件提供了下列 CSS 变量,可用于自定义样式,使用方法请参考 [ConfigProvider 组件](#/zh-CN/component/configprovider)。 + +| 名称 | 说明 | 默认值 | +| --- | --- | --- | +| \--nutui-actionsheet-background-color | 背景色 | `$color-background-overlay` | +| \--nutui-actionsheet-border-radius | 列表和取消按钮圆角 | `0` | +| \--nutui-actionsheet-item-text-align | 列表项的文字对齐方式 | `center` | +| \--nutui-actionsheet-item-border-bottom | 列表项的底部border | `$color-border` | +| \--nutui-actionsheet-item-line-height | 列表项行高 | `24px` | +| \--nutui-actionsheet-item-color | 列表项字色 | `$color-title` | +| \--nutui-actionsheet-item-danger | 列表项danger字色 | `$color-primary` | +| \--nutui-actionsheet-header-padding | 头部内边距 | `16px` | +| \--nutui-actionsheet-title-color | 头部标题字色 | `$color-title` | +| \--nutui-actionsheet-title-font-size | 头部标题字号 | `$font-size-xl` | +| \--nutui-actionsheet-description-color | 头部描述字色 | `$color-text-help` | +| \--nutui-actionsheet-description-font-size | 头部描述字号 | `$font-size-s` | +| \--nutui-actionsheet-grid-padding | 网格容器内边距 | `16px` | +| \--nutui-actionsheet-grid-gap | 网格项间距 | `16px` | +| \--nutui-actionsheet-grid-item-width | 网格项宽度 | `50px` | +| \--nutui-actionsheet-grid-text-color | 网格文字字色 | `$color-text` | +| \--nutui-actionsheet-grid-text-font-size | 网格文字字号 | `12px` | +| \--nutui-actionsheet-grid-text-line-height | 网格文字行高 | `18px` | + + diff --git a/src/packages/actionsheet/doc.taro.md b/src/packages/actionsheet/doc.taro.md index b5acfb6c27..ae931a5a6e 100644 --- a/src/packages/actionsheet/doc.taro.md +++ b/src/packages/actionsheet/doc.taro.md @@ -1,6 +1,6 @@ # ActionSheet 动作面板 -从底部弹出的动作菜单面板。 +从顶部或底部弹出的动作菜单面板。 ## 引入 @@ -12,6 +12,8 @@ import { ActionSheet } from '@nutui/nutui-react-taro' ### 基础用法 +底部弹出时以列表展示。当 `options` 中存在 `icon` 字段时,列表自动切换为左对齐的图标列表布局。 + :::demo @@ -44,6 +46,8 @@ import { ActionSheet } from '@nutui/nutui-react-taro' ### 自定义内容 +通过 `children` 自定义面板内容。此处复用 `Checkbox` 实现清空筛选的多选列表,支持勾选框居左、居右两种布局,点击标题「清空筛选」可清空已选项。 + :::demo @@ -58,6 +62,16 @@ import { ActionSheet } from '@nutui/nutui-react-taro' ::: +### 顶部弹出 + +通过 `position="top"` 从顶部弹出,内容以网格形式展示,`options` 支持 `icon` 字段(字符串使用 `Image` 渲染,也可传入自定义节点)。网格布局也可通过 `layout="grid"` 用于底部弹出;`columns` 仅支持 `4` 或 `5`,为 `4` 时左右间距更宽。 + +:::demo + + + +::: + ## ActionSheet ### Props @@ -65,11 +79,18 @@ import { ActionSheet } from '@nutui/nutui-react-taro' | 属性 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | | visible | 遮罩层可见 | `boolean` | `false` | -| title | 设置列表面板标题 | `string` | `-` | -| description | 设置列表面板副标题/描述 | `string` | `-` | +| title | 设置列表面板标题 | `ReactNode` | `-` | +| titleAlign | 标题对齐方式,`left` \| `center`,仅 `center` 时 `description` 生效 | `string` | `center` | +| description | 设置列表面板副标题/描述 | `ReactNode` | `-` | +| headerLeft | 头部左侧自定义内容 | `ReactNode` | `-` | +| headerRight | 头部右侧自定义内容 | `ReactNode` | `-` | +| position | 弹出位置,`top` \| `bottom`,`top` 时以网格展示 | `string` | `bottom` | +| layout | 内容布局方式,不传时按 `position` 推导(`top`→`grid`、`bottom`→`list`) | `grid` \| `list` | `-` | | options | 列表项 | `Array` | `[]` | | optionKey | 列表项的自定义设置 | `{ [key: string]: string }` | `-` | -| cancelText | 取消文案 | `string` | `取消` | +| columns | 网格列数,仅支持 `4` 或 `5` | `4` \| `5` | `5` | +| cancelText | 取消文案,`top` 时渲染为「点击收起」按钮 | `ReactNode` | `取消` | +| closeable | 是否显示关闭按钮 | `boolean` | `false` | | onSelect | 选择之后触发 | `(item: any, index: number) => void` | `-` | | onCancel | 点击取消文案时触发 | `() => void` | `-` | @@ -79,6 +100,7 @@ import { ActionSheet } from '@nutui/nutui-react-taro' | --- | --- | --- | --- | | name | 列表项的标题key值 | `string` | `-` | | description | 列表项的描述key值 | `string` | `-` | +| icon | 列表项的图标key值,顶部网格与底部列表均支持,底部列表存在 `icon` 时自动切换为图标列表布局 | `ReactNode` \| `string` | `-` | | danger | 高亮颜色 | `string` | `$color-primary` | | disabled | 禁用状态 | `string` | `$disabled-color` | @@ -92,11 +114,21 @@ import { ActionSheet } from '@nutui/nutui-react-taro' | --- | --- | --- | | \--nutui-actionsheet-background-color | 背景色 | `$color-background-overlay` | | \--nutui-actionsheet-border-radius | 列表和取消按钮圆角 | `0` | -| \--nutui-actionsheet-border-color | 标题和取消位置的border色值 | `#f6f6f6` | | \--nutui-actionsheet-item-text-align | 列表项的文字对齐方式 | `center` | -| \--nutui-actionsheet-item-border-bottom | 列表项的底部border | `none` | +| \--nutui-actionsheet-item-border-bottom | 列表项的底部border | `$color-border` | | \--nutui-actionsheet-item-line-height | 列表项行高 | `24px` | | \--nutui-actionsheet-item-color | 列表项字色 | `$color-title` | | \--nutui-actionsheet-item-danger | 列表项danger字色 | `$color-primary` | +| \--nutui-actionsheet-header-padding | 头部内边距 | `16px` | +| \--nutui-actionsheet-title-color | 头部标题字色 | `$color-title` | +| \--nutui-actionsheet-title-font-size | 头部标题字号 | `$font-size-xl` | +| \--nutui-actionsheet-description-color | 头部描述字色 | `$color-text-help` | +| \--nutui-actionsheet-description-font-size | 头部描述字号 | `$font-size-s` | +| \--nutui-actionsheet-grid-padding | 网格容器内边距 | `16px` | +| \--nutui-actionsheet-grid-gap | 网格项间距 | `16px` | +| \--nutui-actionsheet-grid-item-width | 网格项宽度 | `50px` | +| \--nutui-actionsheet-grid-text-color | 网格文字字色 | `$color-text` | +| \--nutui-actionsheet-grid-text-font-size | 网格文字字号 | `12px` | +| \--nutui-actionsheet-grid-text-line-height | 网格文字行高 | `18px` | diff --git a/src/packages/actionsheet/doc.zh-TW.md b/src/packages/actionsheet/doc.zh-TW.md index 292f95a97c..9a7ab2258f 100644 --- a/src/packages/actionsheet/doc.zh-TW.md +++ b/src/packages/actionsheet/doc.zh-TW.md @@ -1,6 +1,6 @@ # ActionSheet 動作面闆 -從底部彈出的動作菜單面闆。 +從頂部或底部彈出的動作菜單面闆。 ## 引入 @@ -12,6 +12,8 @@ import { ActionSheet } from '@nutui/nutui-react' ### 基礎用法 +底部彈出時以列錶展示。當 `options` 中存在 `icon` 欄位時,列錶自動切換為左對齊的圖示列錶佈局。 + :::demo @@ -44,6 +46,8 @@ import { ActionSheet } from '@nutui/nutui-react' ### 自定義內容 +通過 `children` 自定義面板內容。此處複用 `Checkbox` 實現清空篩選的多選列表,支持勾選框居左、居右兩種佈局,點擊標題「清空篩選」可清空已選項。 + :::demo @@ -58,6 +62,16 @@ import { ActionSheet } from '@nutui/nutui-react' ::: +### 頂部彈出 + +通過 `position="top"` 從頂部彈出,內容以網格形式展示,`options` 支持 `icon` 欄位(字串使用 `img` 渲染,也可傳入自定義節點)。網格佈局也可通過 `layout="grid"` 用於底部彈出;`columns` 僅支持 `4` 或 `5`,為 `4` 時左右間距更寬。 + +:::demo + + + +::: + ## ActionSheet ### Props @@ -65,11 +79,18 @@ import { ActionSheet } from '@nutui/nutui-react' | 屬性 | 說明 | 類型 | 預設值 | | --- | --- | --- | --- | | visible | 遮罩層可見 | `boolean` | `false` | -| title | 設定列錶面闆標題 | `string` | \- | -| description | 設定列錶面闆副標題/描述 | `string` | \- | +| title | 設定列錶面闆標題 | `ReactNode` | \- | +| titleAlign | 標題對齊方式,`left` \| `center`,僅 `center` 時 `description` 生效 | `string` | `center` | +| description | 設定列錶面闆副標題/描述 | `ReactNode` | \- | +| headerLeft | 頭部左側自定義內容 | `ReactNode` | \- | +| headerRight | 頭部右側自定義內容 | `ReactNode` | \- | +| position | 彈出位置,`top` \| `bottom`,`top` 時以網格展示 | `string` | `bottom` | +| layout | 內容佈局方式,不傳時按 `position` 推導(`top`→`grid`、`bottom`→`list`) | `grid` \| `list` | `-` | | options | 列錶項 | `Array` | `[]` | | optionKey | 列錶項的自定義設定 | `{ [key: string]: string }` | `-` | -| cancelText | 取消文案 | `string` | `取消` | +| columns | 網格列數,僅支持 `4` 或 `5` | `4` \| `5` | `5` | +| cancelText | 取消文案,`top` 時渲染為「點選收起」按鈕 | `ReactNode` | `取消` | +| closeable | 是否顯示關閉按鈕 | `boolean` | `false` | | onSelect | 選擇之後觸發 | `(item: any, index: number) => void` | `-` | | onCancel | 點選取消文案時觸發 | `() => void` | `-` | @@ -79,6 +100,7 @@ import { ActionSheet } from '@nutui/nutui-react' | --- | --- | --- | --- | | name | 列錶項的標題key值 | `string` | \- | | description | 列錶項的描述key值 | `string` | \- | +| icon | 列錶項的圖示key值,頂部網格與底部列錶均支持,底部列錶存在 `icon` 時自動切換為圖示列錶佈局 | `ReactNode` \| `string` | \- | | danger | 高亮顏色 | `string` | `$color-primary` | | disabled | 禁用狀態 | `string` | `$disabled-color` | @@ -92,11 +114,21 @@ import { ActionSheet } from '@nutui/nutui-react' | --- | --- | --- | | \--nutui-actionsheet-background-color | 背景色 | `$color-background-overlay` | | \--nutui-actionsheet-border-radius | 列錶和取消按鈕圓角 | `0` | -| \--nutui-actionsheet-border-color | 標題和取消位置的border色值 | `#F7F8FC` | | \--nutui-actionsheet-item-text-align | 列錶項的文字對齊方式 | center | -| \--nutui-actionsheet-item-border-bottom | 列錶項的底部border | `none` | +| \--nutui-actionsheet-item-border-bottom | 列錶項的底部border | `$color-border` | | \--nutui-actionsheet-item-line-height | 列錶項行高 | `24px` | | \--nutui-actionsheet-item-color | 列錶項字色 | `$color-title` | | \--nutui-actionsheet-item-danger | 列錶項danger字色 | `$color-primary` | +| \--nutui-actionsheet-header-padding | 頭部內邊距 | `16px` | +| \--nutui-actionsheet-title-color | 頭部標題字色 | `$color-title` | +| \--nutui-actionsheet-title-font-size | 頭部標題字號 | `$font-size-xl` | +| \--nutui-actionsheet-description-color | 頭部描述字色 | `$color-text-help` | +| \--nutui-actionsheet-description-font-size | 頭部描述字號 | `$font-size-s` | +| \--nutui-actionsheet-grid-padding | 網格容器內邊距 | `16px` | +| \--nutui-actionsheet-grid-gap | 網格項間距 | `16px` | +| \--nutui-actionsheet-grid-item-width | 網格項寬度 | `50px` | +| \--nutui-actionsheet-grid-text-color | 網格文字字色 | `$color-text` | +| \--nutui-actionsheet-grid-text-font-size | 網格文字字號 | `12px` | +| \--nutui-actionsheet-grid-text-line-height | 網格文字行高 | `18px` | diff --git a/src/packages/configprovider/types.ts b/src/packages/configprovider/types.ts index bf1e53ad89..94f7dba402 100644 --- a/src/packages/configprovider/types.ts +++ b/src/packages/configprovider/types.ts @@ -286,13 +286,23 @@ export type NutCSSVariables = | 'nutuiInputnumberIconSize' | 'nutuiInputnumberDisabledColor' | 'nutuiActionsheetBackgroundColor' - | 'nutuiActionsheetBorderColor' | 'nutuiActionsheetBorderRadius' | 'nutuiActionsheetItemTextAlign' | 'nutuiActionsheetItemBorderBottom' | 'nutuiActionsheetItemLineHeight' | 'nutuiActionsheetItemColor' | 'nutuiActionsheetItemDanger' + | 'nutuiActionsheetHeaderPadding' + | 'nutuiActionsheetTitleColor' + | 'nutuiActionsheetTitleFontSize' + | 'nutuiActionsheetDescriptionColor' + | 'nutuiActionsheetDescriptionFontSize' + | 'nutuiActionsheetGridPadding' + | 'nutuiActionsheetGridGap' + | 'nutuiActionsheetGridItemWidth' + | 'nutuiActionsheetGridTextColor' + | 'nutuiActionsheetGridTextFontSize' + | 'nutuiActionsheetGridTextLineHeight' | 'nutuiShortpasswordBackgroundColor' | 'nutuiShortpasswordBorderColor' | 'nutuiShortpasswordError' diff --git a/src/sites/sites-react/doc/docs/react/migrate-from-v3.en-US.md b/src/sites/sites-react/doc/docs/react/migrate-from-v3.en-US.md index 68b6bfe5fb..f1c105b94e 100644 --- a/src/sites/sites-react/doc/docs/react/migrate-from-v3.en-US.md +++ b/src/sites/sites-react/doc/docs/react/migrate-from-v3.en-US.md @@ -132,3 +132,20 @@ npm install @nutui/nutui-react-taro - **Range labels and marks updates (compatible)**: - The left/right range label font size changed from `12px` to `16px` (`$font-size-md`), line height `24px`. - The mark dot size changed from `11px` to `8px`, mark text font size from `12px` to `14px` with `24px` line height, distributed aligned to the mark dot center line. + +### ActionSheet (Feedback) + +- **`position` prop now takes effect (behavior change)**: + - Previously the component hardcoded the popup position to `bottom` and ignored an externally passed `position`. It now supports `top` / `bottom`, still defaulting to `bottom`. If you passed `position` before and relied on it being ignored, remove that prop. +- **Default styling of bottom list items changed (behavior change)**: + - A default divider is now shown between list items: the default value of `--nutui-actionsheet-item-border-bottom` changed from `none` to `$color-border` (the last item has no divider). The item line height changed from `24px` to `22px`, the vertical padding from `10px` to `13px`, and the list container gained `16px` of horizontal padding. The cancel button no longer has a top divider or gap above it, and its line height is now `40px` with a `$font-size-md` font size. To keep the old divider-free look, set `--nutui-actionsheet-item-border-bottom` to `none`. +- **`optionKey` now includes `icon` by default (behavior change)**: + - The default `optionKey` now includes `icon: 'icon'`. When items in a bottom-list `options` contain an `icon` field, the list automatically switches to a left-aligned icon list layout. If your data already has a field named `icon` that you do not want rendered as an icon, point `optionKey` at a different icon field name or remove that field. +- **Header structure moved from Popup rendering to component self-rendering (compatible)**: + - The title and description are now rendered by ActionSheet itself, changing the DOM from `.nut-popup-title*` to `.nut-actionsheet-header*`. If you overrode the action sheet header via `.nut-popup-title` class names, migrate to `.nut-actionsheet-header`, `.nut-actionsheet-header-title`, and `.nut-actionsheet-header-description`. +- **Removed CSS variable `--nutui-actionsheet-border-color` (breaking change)**: + - This variable previously set the color of the divider below the title and above the cancel button. Since the header is now self-rendered and the cancel button no longer has a top divider, the variable is no longer used and has been removed. If you customized the divider color via `--nutui-actionsheet-border-color`, use `--nutui-actionsheet-item-border-bottom` instead. +- **Top popup and grid layout (new)**: + - Pop up from the top via `position="top"`; the content is displayed as a grid. `options` support an `icon` field (a string is rendered with `img`, or a custom node can be passed), and `columns` sets the number of columns (only `4` / `5`, default `5`). `cancelText` is rendered as a "collapse" button in top mode. The grid layout can also be used for bottom popups via `layout="grid"`. +- **Header styling and close capabilities (new)**: + - Added `titleAlign` (`left` / `center`, default `center`; `description` only takes effect when `center`), `headerLeft`, and `headerRight` for customizing the left and right content of the header, as well as `closeable` / `closeIconPosition` to control the display and position of the close button. All of the above are purely additive and do not affect existing usage. diff --git a/src/sites/sites-react/doc/docs/react/migrate-from-v3.md b/src/sites/sites-react/doc/docs/react/migrate-from-v3.md index d94e5e52a2..3530dc5dd3 100644 --- a/src/sites/sites-react/doc/docs/react/migrate-from-v3.md +++ b/src/sites/sites-react/doc/docs/react/migrate-from-v3.md @@ -132,3 +132,20 @@ npm install @nutui/nutui-react-taro - **左右范围文字与刻度调整(兼容升级)**: - 左右范围文字字号由 `12px` 调整为 `16px`(`$font-size-md`),行高 `24px`。 - 刻度点尺寸由 `11px` 调整为 `8px`,刻度文字字号由 `12px` 调整为 `14px`、行高 `24px`,并以刻度点中线对齐分布。 + +### ActionSheet (反馈类) + +- **`position` 属性现已生效(行为变更)**: + - 此前组件内部将弹出位置写死为 `bottom` 并忽略外部传入的 `position`,现已支持 `top` / `bottom`,默认仍为 `bottom`。若此前误传过 `position` 且依赖其被忽略,请移除该属性。 +- **底部列表项默认样式调整(行为变更)**: + - 列表项之间新增默认分隔线:`--nutui-actionsheet-item-border-bottom` 默认值由 `none` 调整为 `$color-border`(最后一项不显示分隔线);列表项行高由 `24px` 调整为 `22px`、上下内边距由 `10px` 调整为 `13px`,列表容器新增左右 `16px` 内边距;取消按钮移除与列表之间的顶部分隔线及间距,行高调整为 `40px`、字号调整为 `$font-size-md`。若需保留旧的无分隔线外观,可将 `--nutui-actionsheet-item-border-bottom` 设为 `none`。 +- **`optionKey` 默认新增 `icon` 字段(行为变更)**: + - `optionKey` 默认值新增 `icon: 'icon'`。当底部列表的 `options` 中存在 `icon` 字段时,列表会自动切换为左对齐的图标列表布局。若你的数据中已有名为 `icon` 的字段但不希望将其渲染为图标,请通过 `optionKey` 指定其它图标字段名或移除该字段。 +- **头部结构由 Popup 渲染改为组件自绘(兼容升级)**: + - 标题、描述改由 ActionSheet 自身渲染,DOM 结构由 `.nut-popup-title*` 变为 `.nut-actionsheet-header*`。若此前通过 `.nut-popup-title` 相关类名覆盖过动作面板头部样式,请迁移至 `.nut-actionsheet-header`、`.nut-actionsheet-header-title`、`.nut-actionsheet-header-description`。 +- **移除 CSS 变量 `--nutui-actionsheet-border-color`(不兼容变更)**: + - 该变量此前用于标题底部与取消按钮顶部的分隔线颜色。头部改为组件自绘、取消按钮移除顶部分隔线后,该变量已不再被使用,故予以移除。若此前通过 `--nutui-actionsheet-border-color` 自定义分隔线颜色,请改用 `--nutui-actionsheet-item-border-bottom`。 +- **顶部弹出与网格布局(新增)**: + - 通过 `position="top"` 从顶部弹出,内容以网格形式展示,`options` 支持 `icon` 字段(字符串使用 `img` 渲染,也可传入自定义节点),可通过 `columns` 设置列数(仅支持 `4` / `5`,默认 `5`);`cancelText` 在顶部模式下渲染为「点击收起」按钮。网格布局也可通过 `layout="grid"` 用于底部弹出。 +- **头部样式与关闭能力(新增)**: + - 新增 `titleAlign`(`left` / `center`,默认 `center`,仅 `center` 时 `description` 生效)、`headerLeft`、`headerRight` 自定义头部左右内容,以及 `closeable` / `closeIconPosition` 控制关闭按钮的显示与位置。以上均为纯新增能力,不影响原有用法。 diff --git a/src/sites/sites-react/doc/docs/taro/migrate-from-v3.en-US.md b/src/sites/sites-react/doc/docs/taro/migrate-from-v3.en-US.md index 68b6bfe5fb..75645c7bee 100644 --- a/src/sites/sites-react/doc/docs/taro/migrate-from-v3.en-US.md +++ b/src/sites/sites-react/doc/docs/taro/migrate-from-v3.en-US.md @@ -132,3 +132,20 @@ npm install @nutui/nutui-react-taro - **Range labels and marks updates (compatible)**: - The left/right range label font size changed from `12px` to `16px` (`$font-size-md`), line height `24px`. - The mark dot size changed from `11px` to `8px`, mark text font size from `12px` to `14px` with `24px` line height, distributed aligned to the mark dot center line. + +### ActionSheet (Feedback) + +- **`position` prop now takes effect (behavior change)**: + - Previously the component hardcoded the popup position to `bottom` and ignored an externally passed `position`. It now supports `top` / `bottom`, still defaulting to `bottom`. If you passed `position` before and relied on it being ignored, remove that prop. +- **Default styling of bottom list items changed (behavior change)**: + - A default divider is now shown between list items: the default value of `--nutui-actionsheet-item-border-bottom` changed from `none` to `$color-border` (the last item has no divider). The item line height changed from `24px` to `22px`, the vertical padding from `10px` to `13px`, and the list container gained `16px` of horizontal padding. The cancel button no longer has a top divider or gap above it, and its line height is now `40px` with a `$font-size-md` font size. To keep the old divider-free look, set `--nutui-actionsheet-item-border-bottom` to `none`. +- **`optionKey` now includes `icon` by default (behavior change)**: + - The default `optionKey` now includes `icon: 'icon'`. When items in a bottom-list `options` contain an `icon` field, the list automatically switches to a left-aligned icon list layout. If your data already has a field named `icon` that you do not want rendered as an icon, point `optionKey` at a different icon field name or remove that field. +- **Header structure moved from Popup rendering to component self-rendering (compatible)**: + - The title and description are now rendered by ActionSheet itself, changing the DOM from `.nut-popup-title*` to `.nut-actionsheet-header*`. If you overrode the action sheet header via `.nut-popup-title` class names, migrate to `.nut-actionsheet-header`, `.nut-actionsheet-header-title`, and `.nut-actionsheet-header-description`. +- **Removed CSS variable `--nutui-actionsheet-border-color` (breaking change)**: + - This variable previously set the color of the divider below the title and above the cancel button. Since the header is now self-rendered and the cancel button no longer has a top divider, the variable is no longer used and has been removed. If you customized the divider color via `--nutui-actionsheet-border-color`, use `--nutui-actionsheet-item-border-bottom` instead. +- **Top popup and grid layout (new)**: + - Pop up from the top via `position="top"`; the content is displayed as a grid. `options` support an `icon` field (a string is rendered with `Image`, or a custom node can be passed), and `columns` sets the number of columns (only `4` / `5`, default `5`). `cancelText` is rendered as a "collapse" button in top mode. The grid layout can also be used for bottom popups via `layout="grid"`. +- **Header styling and close capabilities (new)**: + - Added `titleAlign` (`left` / `center`, default `center`; `description` only takes effect when `center`), `headerLeft`, and `headerRight` for customizing the left and right content of the header, as well as `closeable` / `closeIconPosition` to control the display and position of the close button. All of the above are purely additive and do not affect existing usage. diff --git a/src/sites/sites-react/doc/docs/taro/migrate-from-v3.md b/src/sites/sites-react/doc/docs/taro/migrate-from-v3.md index d94e5e52a2..97574b8aa9 100644 --- a/src/sites/sites-react/doc/docs/taro/migrate-from-v3.md +++ b/src/sites/sites-react/doc/docs/taro/migrate-from-v3.md @@ -132,3 +132,20 @@ npm install @nutui/nutui-react-taro - **左右范围文字与刻度调整(兼容升级)**: - 左右范围文字字号由 `12px` 调整为 `16px`(`$font-size-md`),行高 `24px`。 - 刻度点尺寸由 `11px` 调整为 `8px`,刻度文字字号由 `12px` 调整为 `14px`、行高 `24px`,并以刻度点中线对齐分布。 + +### ActionSheet (反馈类) + +- **`position` 属性现已生效(行为变更)**: + - 此前组件内部将弹出位置写死为 `bottom` 并忽略外部传入的 `position`,现已支持 `top` / `bottom`,默认仍为 `bottom`。若此前误传过 `position` 且依赖其被忽略,请移除该属性。 +- **底部列表项默认样式调整(行为变更)**: + - 列表项之间新增默认分隔线:`--nutui-actionsheet-item-border-bottom` 默认值由 `none` 调整为 `$color-border`(最后一项不显示分隔线);列表项行高由 `24px` 调整为 `22px`、上下内边距由 `10px` 调整为 `13px`,列表容器新增左右 `16px` 内边距;取消按钮移除与列表之间的顶部分隔线及间距,行高调整为 `40px`、字号调整为 `$font-size-md`。若需保留旧的无分隔线外观,可将 `--nutui-actionsheet-item-border-bottom` 设为 `none`。 +- **`optionKey` 默认新增 `icon` 字段(行为变更)**: + - `optionKey` 默认值新增 `icon: 'icon'`。当底部列表的 `options` 中存在 `icon` 字段时,列表会自动切换为左对齐的图标列表布局。若你的数据中已有名为 `icon` 的字段但不希望将其渲染为图标,请通过 `optionKey` 指定其它图标字段名或移除该字段。 +- **头部结构由 Popup 渲染改为组件自绘(兼容升级)**: + - 标题、描述改由 ActionSheet 自身渲染,DOM 结构由 `.nut-popup-title*` 变为 `.nut-actionsheet-header*`。若此前通过 `.nut-popup-title` 相关类名覆盖过动作面板头部样式,请迁移至 `.nut-actionsheet-header`、`.nut-actionsheet-header-title`、`.nut-actionsheet-header-description`。 +- **移除 CSS 变量 `--nutui-actionsheet-border-color`(不兼容变更)**: + - 该变量此前用于标题底部与取消按钮顶部的分隔线颜色。头部改为组件自绘、取消按钮移除顶部分隔线后,该变量已不再被使用,故予以移除。若此前通过 `--nutui-actionsheet-border-color` 自定义分隔线颜色,请改用 `--nutui-actionsheet-item-border-bottom`。 +- **顶部弹出与网格布局(新增)**: + - 通过 `position="top"` 从顶部弹出,内容以网格形式展示,`options` 支持 `icon` 字段(字符串使用 `Image` 渲染,也可传入自定义节点),可通过 `columns` 设置列数(仅支持 `4` / `5`,默认 `5`);`cancelText` 在顶部模式下渲染为「点击收起」按钮。网格布局也可通过 `layout="grid"` 用于底部弹出。 +- **头部样式与关闭能力(新增)**: + - 新增 `titleAlign`(`left` / `center`,默认 `center`,仅 `center` 时 `description` 生效)、`headerLeft`、`headerRight` 自定义头部左右内容,以及 `closeable` / `closeIconPosition` 控制关闭按钮的显示与位置。以上均为纯新增能力,不影响原有用法。 diff --git a/src/styles/variables-daojia.scss b/src/styles/variables-daojia.scss index aa79e22adf..fb7261d1a9 100644 --- a/src/styles/variables-daojia.scss +++ b/src/styles/variables-daojia.scss @@ -530,19 +530,39 @@ $inputnumber-disabled-color: var(--nutui-inputnumber-disabled-color, // actionsheet(✅) $actionsheet-background-color: var(--nutui-actionsheet-background-color, $color-background-overlay) !default; -$actionsheet-border-color: var(--nutui-actionsheet-border-color, - $color-border) !default; $actionsheet-border-radius: var(--nutui-actionsheet-border-radius, 0) !default; $actionsheet-item-text-align: var(--nutui-actionsheet-item-text-align, center) !default; $actionsheet-item-border-bottom: var(--nutui-actionsheet-item-border-bottom, - none) !default; + $color-border) !default; $actionsheet-item-line-height: var(--nutui-actionsheet-item-line-height, - 24px) !default; + 22px) !default; $actionsheet-item-color: var(--nutui-actionsheet-item-color, $color-title) !default; $actionsheet-item-danger: var(--nutui-actionsheet-item-danger, $color-danger) !default; +$actionsheet-header-padding: var(--nutui-actionsheet-header-padding, + 16px) !default; +$actionsheet-title-color: var(--nutui-actionsheet-title-color, + $color-title) !default; +$actionsheet-title-font-size: var(--nutui-actionsheet-title-font-size, + $font-size-xl) !default; +$actionsheet-description-color: var(--nutui-actionsheet-description-color, + $color-text-help) !default; +$actionsheet-description-font-size: var(--nutui-actionsheet-description-font-size, + $font-size-xxs) !default; +$actionsheet-grid-padding: var(--nutui-actionsheet-grid-padding, + 16px) !default; +$actionsheet-grid-gap: var(--nutui-actionsheet-grid-gap, + 16px) !default; +$actionsheet-grid-item-width: var(--nutui-actionsheet-grid-item-width, + 50px) !default; +$actionsheet-grid-text-color: var(--nutui-actionsheet-grid-text-color, + $color-text) !default; +$actionsheet-grid-text-font-size: var(--nutui-actionsheet-grid-text-font-size, + 12px) !default; +$actionsheet-grid-text-line-height: var(--nutui-actionsheet-grid-text-line-height, + 18px) !default; //shortpassword(✅) $shortpassword-background-color: var(--nutui-shortpassword-background-color, diff --git a/src/styles/variables-jmapp.scss b/src/styles/variables-jmapp.scss index 6cd805a2ed..413cf5bc98 100644 --- a/src/styles/variables-jmapp.scss +++ b/src/styles/variables-jmapp.scss @@ -834,7 +834,6 @@ $actionsheet-background-color: var( --nutui-actionsheet-background-color, $color-background ) !default; -$actionsheet-border-color: var(--nutui-actionsheet-border-color, none) !default; $actionsheet-border-radius: var( --nutui-actionsheet-border-radius, $radius-l @@ -845,11 +844,11 @@ $actionsheet-item-text-align: var( ) !default; $actionsheet-item-border-bottom: var( --nutui-actionsheet-item-border-bottom, - none + $color-border ) !default; $actionsheet-item-line-height: var( --nutui-actionsheet-item-line-height, - 24px + 22px ) !default; $actionsheet-item-color: var( --nutui-actionsheet-item-color, @@ -859,6 +858,47 @@ $actionsheet-item-danger: var( --nutui-actionsheet-item-danger, $color-danger ) !default; +$actionsheet-header-padding: var( + --nutui-actionsheet-header-padding, + 16px +) !default; +$actionsheet-title-color: var( + --nutui-actionsheet-title-color, + $color-title +) !default; +$actionsheet-title-font-size: var( + --nutui-actionsheet-title-font-size, + $font-size-xl +) !default; +$actionsheet-description-color: var( + --nutui-actionsheet-description-color, + $color-text-help +) !default; +$actionsheet-description-font-size: var( + --nutui-actionsheet-description-font-size, + $font-size-xxs +) !default; +$actionsheet-grid-padding: var( + --nutui-actionsheet-grid-padding, + 16px +) !default; +$actionsheet-grid-gap: var(--nutui-actionsheet-grid-gap, 16px) !default; +$actionsheet-grid-item-width: var( + --nutui-actionsheet-grid-item-width, + 50px +) !default; +$actionsheet-grid-text-color: var( + --nutui-actionsheet-grid-text-color, + $color-text +) !default; +$actionsheet-grid-text-font-size: var( + --nutui-actionsheet-grid-text-font-size, + 12px +) !default; +$actionsheet-grid-text-line-height: var( + --nutui-actionsheet-grid-text-line-height, + 18px +) !default; //shortpassword(✅) $shortpassword-background-color: var( diff --git a/src/styles/variables-jrkf.scss b/src/styles/variables-jrkf.scss index a4e73e8736..7da7fdb983 100644 --- a/src/styles/variables-jrkf.scss +++ b/src/styles/variables-jrkf.scss @@ -914,7 +914,6 @@ $actionsheet-background-color: var( --nutui-actionsheet-background-color, $color-surface-1 ) !default; -$actionsheet-border-color: var(--nutui-actionsheet-border-color, none) !default; $actionsheet-border-radius: var( --nutui-actionsheet-border-radius, $radius-l @@ -925,11 +924,11 @@ $actionsheet-item-text-align: var( ) !default; $actionsheet-item-border-bottom: var( --nutui-actionsheet-item-border-bottom, - none + $color-border ) !default; $actionsheet-item-line-height: var( --nutui-actionsheet-item-line-height, - 24px + 22px ) !default; $actionsheet-item-color: var( --nutui-actionsheet-item-color, @@ -939,6 +938,47 @@ $actionsheet-item-danger: var( --nutui-actionsheet-item-danger, $color-danger ) !default; +$actionsheet-header-padding: var( + --nutui-actionsheet-header-padding, + 16px +) !default; +$actionsheet-title-color: var( + --nutui-actionsheet-title-color, + $color-title +) !default; +$actionsheet-title-font-size: var( + --nutui-actionsheet-title-font-size, + $font-size-xl +) !default; +$actionsheet-description-color: var( + --nutui-actionsheet-description-color, + $color-text-help +) !default; +$actionsheet-description-font-size: var( + --nutui-actionsheet-description-font-size, + $font-size-xxs +) !default; +$actionsheet-grid-padding: var( + --nutui-actionsheet-grid-padding, + 16px +) !default; +$actionsheet-grid-gap: var(--nutui-actionsheet-grid-gap, 16px) !default; +$actionsheet-grid-item-width: var( + --nutui-actionsheet-grid-item-width, + 50px +) !default; +$actionsheet-grid-text-color: var( + --nutui-actionsheet-grid-text-color, + $color-text +) !default; +$actionsheet-grid-text-font-size: var( + --nutui-actionsheet-grid-text-font-size, + 12px +) !default; +$actionsheet-grid-text-line-height: var( + --nutui-actionsheet-grid-text-line-height, + 18px +) !default; //shortpassword(✅) $shortpassword-background-color: var( diff --git a/src/styles/variables.scss b/src/styles/variables.scss index a4df66f6fc..9d0c557593 100644 --- a/src/styles/variables.scss +++ b/src/styles/variables.scss @@ -820,10 +820,6 @@ $actionsheet-background-color: var( --nutui-actionsheet-background-color, $color-background-overlay ) !default; -$actionsheet-border-color: var( - --nutui-actionsheet-border-color, - $color-border -) !default; $actionsheet-border-radius: var(--nutui-actionsheet-border-radius, 0) !default; $actionsheet-item-text-align: var( --nutui-actionsheet-item-text-align, @@ -831,11 +827,11 @@ $actionsheet-item-text-align: var( ) !default; $actionsheet-item-border-bottom: var( --nutui-actionsheet-item-border-bottom, - none + $color-border ) !default; $actionsheet-item-line-height: var( --nutui-actionsheet-item-line-height, - scale-px(24px) + scale-px(22px) ) !default; $actionsheet-item-color: var( --nutui-actionsheet-item-color, @@ -845,6 +841,44 @@ $actionsheet-item-danger: var( --nutui-actionsheet-item-danger, $color-danger ) !default; +$actionsheet-header-padding: var( + --nutui-actionsheet-header-padding, + scale-px(16px) +) !default; +$actionsheet-title-color: var(--nutui-actionsheet-title-color, $color-title) !default; +$actionsheet-title-font-size: var( + --nutui-actionsheet-title-font-size, + $font-size-xl +) !default; +$actionsheet-description-color: var( + --nutui-actionsheet-description-color, + $color-text-help +) !default; +$actionsheet-description-font-size: var( + --nutui-actionsheet-description-font-size, + $font-size-xxs +) !default; +$actionsheet-grid-padding: var( + --nutui-actionsheet-grid-padding, + scale-px(16px) +) !default; +$actionsheet-grid-gap: var(--nutui-actionsheet-grid-gap, scale-px(16px)) !default; +$actionsheet-grid-item-width: var( + --nutui-actionsheet-grid-item-width, + scale-px(50px) +) !default; +$actionsheet-grid-text-color: var( + --nutui-actionsheet-grid-text-color, + $color-text +) !default; +$actionsheet-grid-text-font-size: var( + --nutui-actionsheet-grid-text-font-size, + scale-font-px(12px) +) !default; +$actionsheet-grid-text-line-height: var( + --nutui-actionsheet-grid-text-line-height, + scale-px(18px) +) !default; //shortpassword(✅) $shortpassword-background-color: var( diff --git a/src/types/spec/actionsheet/base.ts b/src/types/spec/actionsheet/base.ts index 8f61939e18..066846071e 100644 --- a/src/types/spec/actionsheet/base.ts +++ b/src/types/spec/actionsheet/base.ts @@ -3,13 +3,23 @@ import { BaseProps } from '../../base/props' export type ActionSheetOption = { [key: string]: T } +export type ActionSheetTitleAlign = 'left' | 'center' + +export type ActionSheetLayout = 'grid' | 'list' + export type BaseActionSheet = POPUP_PROPS & BaseProps & { visible: boolean + title?: ReactNode description: ReactNode - options: ActionSheetOption[] + titleAlign: ActionSheetTitleAlign + headerLeft?: ReactNode + headerRight?: ReactNode + options: ActionSheetOption[] optionKey: ActionSheetOption + columns: 4 | 5 + layout?: ActionSheetLayout cancelText: ReactNode onCancel: () => void - onSelect: (item: ActionSheetOption, index: number) => void + onSelect: (item: ActionSheetOption, index: number) => void }