From cdaac5602c4a23626dd5108957baa9f9ba3ea26e Mon Sep 17 00:00:00 2001 From: CatsJuice Date: Mon, 4 Nov 2024 05:28:06 +0000 Subject: [PATCH] feat(mobile): manage docs/tags/collections in explorer (#8649) close AF-1564, AF-1542 --- .../src/ui/modal/confirm-modal.css.ts | 86 +++++ .../component/src/ui/modal/confirm-modal.tsx | 18 +- .../frontend/component/src/ui/modal/modal.tsx | 15 +- .../component/src/ui/modal/styles.css.ts | 32 -- .../components/page-list/selector/index.ts | 6 +- .../page-list/selector/use-select-dialog.tsx | 40 ++- .../explorer/nodes/folder/index.tsx | 29 +- .../explorer/nodes/tag/operations.tsx | 36 ++- .../core/src/mobile/components/index.ts | 1 + .../selector/collection-selector.tsx | 40 +++ .../components/selector/doc-selector.tsx | 58 ++++ .../components/selector/generic-selector.tsx | 293 ++++++++++++++++++ .../mobile/components/selector/generic.css.ts | 141 +++++++++ .../src/mobile/components/selector/index.tsx | 47 +++ .../components/selector/tag-selector.tsx | 64 ++++ .../core/src/mobile/styles/mobile.css.ts | 2 +- .../i18n/src/i18n-completenesses.json | 10 +- packages/frontend/i18n/src/resources/en.json | 20 +- 18 files changed, 861 insertions(+), 77 deletions(-) create mode 100644 packages/frontend/component/src/ui/modal/confirm-modal.css.ts create mode 100644 packages/frontend/core/src/mobile/components/selector/collection-selector.tsx create mode 100644 packages/frontend/core/src/mobile/components/selector/doc-selector.tsx create mode 100644 packages/frontend/core/src/mobile/components/selector/generic-selector.tsx create mode 100644 packages/frontend/core/src/mobile/components/selector/generic.css.ts create mode 100644 packages/frontend/core/src/mobile/components/selector/index.tsx create mode 100644 packages/frontend/core/src/mobile/components/selector/tag-selector.tsx diff --git a/packages/frontend/component/src/ui/modal/confirm-modal.css.ts b/packages/frontend/component/src/ui/modal/confirm-modal.css.ts new file mode 100644 index 0000000000..3c070f245a --- /dev/null +++ b/packages/frontend/component/src/ui/modal/confirm-modal.css.ts @@ -0,0 +1,86 @@ +import { style } from '@vanilla-extract/css'; + +// desktop +export const desktopStyles = { + container: style({ + display: 'flex', + flexDirection: 'column', + }), + description: style({}), + header: style({}), + content: style({ + height: '100%', + overflowY: 'auto', + padding: '12px 4px 20px 4px', + }), + footer: style({ + display: 'flex', + justifyContent: 'flex-end', + alignItems: 'center', + paddingTop: '40px', + marginTop: 'auto', + gap: '20px', + selectors: { + '&.modalFooterWithChildren': { + paddingTop: '20px', + }, + '&.reverse': { + flexDirection: 'row-reverse', + justifyContent: 'flex-start', + }, + }, + }), + action: style({}), +}; + +// mobile +export const mobileStyles = { + container: style({ + display: 'flex', + flexDirection: 'column', + padding: '12px 0 !important', + borderRadius: 22, + }), + description: style({ + padding: '11px 22px', + fontSize: 17, + fontWeight: 400, + letterSpacing: -0.43, + lineHeight: '22px', + }), + header: style({ + padding: '10px 16px', + marginBottom: '0px !important', + fontSize: 17, + fontWeight: 600, + letterSpacing: -0.43, + lineHeight: '22px', + }), + content: style({ + padding: '11px 22px', + fontSize: 17, + fontWeight: 400, + letterSpacing: -0.43, + lineHeight: '22px', + }), + footer: style({ + padding: '8px 16px', + display: 'flex', + flexDirection: 'column', + gap: 16, + selectors: { + '&.reverse': { + flexDirection: 'column-reverse', + }, + }, + }), + action: style({ + width: '100%', + height: 44, + borderRadius: 8, + fontSize: 17, + fontWeight: 400, + letterSpacing: -0.43, + lineHeight: '22px', + }), +}; diff --git a/packages/frontend/component/src/ui/modal/confirm-modal.tsx b/packages/frontend/component/src/ui/modal/confirm-modal.tsx index 7728db56f8..299ed53386 100644 --- a/packages/frontend/component/src/ui/modal/confirm-modal.tsx +++ b/packages/frontend/component/src/ui/modal/confirm-modal.tsx @@ -5,9 +5,11 @@ import { createContext, useCallback, useContext, useState } from 'react'; import type { ButtonProps } from '../button'; import { Button } from '../button'; +import { desktopStyles, mobileStyles } from './confirm-modal.css'; import type { ModalProps } from './modal'; import { Modal } from './modal'; -import * as styles from './styles.css'; + +const styles = BUILD_CONFIG.isMobileEdition ? mobileStyles : desktopStyles; export interface ConfirmModalProps extends ModalProps { confirmButtonOptions?: Omit; @@ -36,6 +38,8 @@ export const ConfirmModal = ({ onCancel, width = 480, autoFocusConfirm = true, + headerClassName, + descriptionClassName, ...props }: ConfirmModalProps) => { const onConfirmClick = useCallback(() => { @@ -46,7 +50,7 @@ export const ConfirmModal = ({ return ( { e.stopPropagation(); onCancel?.(); @@ -56,19 +60,20 @@ export const ConfirmModal = ({ closeButtonOptions={{ onClick: onCancel, }} + headerClassName={clsx(styles.header, headerClassName)} + descriptionClassName={clsx(styles.description, descriptionClassName)} {...props} > - {children ? ( -
{children}
- ) : null} + {children ?
{children}
: null}
+
+ + + ); +}; diff --git a/packages/frontend/core/src/mobile/components/selector/generic.css.ts b/packages/frontend/core/src/mobile/components/selector/generic.css.ts new file mode 100644 index 0000000000..9de164702e --- /dev/null +++ b/packages/frontend/core/src/mobile/components/selector/generic.css.ts @@ -0,0 +1,141 @@ +import { cssVarV2 } from '@toeverything/theme/v2'; +import { style } from '@vanilla-extract/css'; + +const flexCenter = style({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +}); + +export const root = style({ + width: '100%', + height: '100%', + display: 'flex', + flexDirection: 'column', +}); + +// header +export const headerTitle = style({ + fontSize: 17, + fontWeight: 600, + letterSpacing: -0.43, + lineHeight: '22px', + color: cssVarV2('text/primary'), +}); +export const scrollArea = style({ + height: 0, + flex: 1, +}); + +// content +export const list = style({ + padding: '0 8px', + position: 'relative', +}); +export const quickSelect = style({ + width: 32 + 8 * 2, + height: '100%', + position: 'absolute', + top: 0, + left: 0, +}); +export const listItem = style({ + display: 'flex', + alignItems: 'center', + padding: 8, + gap: 12, + borderBottom: `0.5px solid ${cssVarV2('layer/insideBorder/border')}`, +}); +export const listItemCheckbox = style([ + flexCenter, + { + width: 32, + height: 32, + fontSize: 24, + color: cssVarV2('icon/primary'), + }, +]); +export const listItemIcon = style([ + flexCenter, + { + width: 32, + height: 32, + fontSize: 24, + color: cssVarV2('icon/primary'), + }, +]); +export const listItemLabel = style({ + fontWeight: 400, + fontSize: 17, + lineHeight: '22px', + letterSpacing: -0.43, + color: cssVarV2('text/primary'), + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + width: 0, + flex: 1, +}); +export const listItemArrow = style({ + fontSize: 16, + color: cssVarV2('icon/disable'), +}); + +// footer +export const footer = style({ + padding: '0 16px', + borderTop: `1px solid ${cssVarV2('layer/insideBorder/border')}`, +}); +export const actions = style({ + width: '100%', + padding: '8px 16px', +}); +export const actionButton = style({ + width: '100%', + height: 44, + borderRadius: 8, + + fontSize: 17, + fontWeight: 400, + letterSpacing: -0.43, +}); + +export const changedInfo = style({ + fontSize: 15, + fontWeight: 400, + letterSpacing: -0.23, + lineHeight: '20px', + color: cssVarV2('text/primary'), + height: 20, +}); +export const totalInfo = style({ + fontSize: 13, + fontWeight: 400, + letterSpacing: -0.08, + lineHeight: '18px', + color: cssVarV2('text/tertiary'), + height: 18, +}); +export const info = style({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + height: 0, + gap: 8, + overflow: 'hidden', + transition: 'all 0.3s ease', + selectors: { + [`&:has(> ${changedInfo}), &:has(> ${totalInfo})`]: { + padding: '8px 0', + }, + [`&:has(> ${totalInfo})`]: { + height: 18 + 8 * 2, + }, + [`&:has(> ${changedInfo})`]: { + height: 20 + 8 * 2, + }, + [`&:has(> ${changedInfo}):has(> ${totalInfo})`]: { + height: 20 + 18 + 8 + 8 * 2, + }, + }, +}); diff --git a/packages/frontend/core/src/mobile/components/selector/index.tsx b/packages/frontend/core/src/mobile/components/selector/index.tsx new file mode 100644 index 0000000000..c1bfda8dc2 --- /dev/null +++ b/packages/frontend/core/src/mobile/components/selector/index.tsx @@ -0,0 +1,47 @@ +import { useSelectDialog } from '@affine/core/components/page-list/selector'; +import { cssVarV2 } from '@toeverything/theme/v2'; + +import { + CollectionsSelector, + type CollectionsSelectorProps, +} from './collection-selector'; +import { DocsSelector, type DocsSelectorProps } from './doc-selector'; +import { TagsSelector, type TagsSelectorProps } from './tag-selector'; + +const options: Parameters[2] = { + modalProps: { + fullScreen: true, + width: undefined, + height: undefined, + contentOptions: { + style: { + background: cssVarV2('layer/background/secondary'), + padding: 0, + }, + }, + }, +}; + +export const useSelectDoc = () => { + return useSelectDialog( + DocsSelector, + 'select-doc-dialog', + options + ); +}; + +export const useSelectCollection = () => { + return useSelectDialog( + CollectionsSelector, + 'select-collection-dialog', + options + ); +}; + +export const useSelectTag = () => { + return useSelectDialog( + TagsSelector, + 'select-tag-dialog', + options + ); +}; diff --git a/packages/frontend/core/src/mobile/components/selector/tag-selector.tsx b/packages/frontend/core/src/mobile/components/selector/tag-selector.tsx new file mode 100644 index 0000000000..224b13ff44 --- /dev/null +++ b/packages/frontend/core/src/mobile/components/selector/tag-selector.tsx @@ -0,0 +1,64 @@ +import type { BaseSelectorDialogProps } from '@affine/core/components/page-list/selector'; +import { TagService } from '@affine/core/modules/tag'; +import { useLiveData, useService } from '@toeverything/infra'; +import { useMemo } from 'react'; + +import { GenericSelector, type GenericSelectorProps } from './generic-selector'; + +export interface TagsSelectorProps + extends BaseSelectorDialogProps, + Pick {} + +const TagIcon = ({ tagId }: { tagId: string }) => { + const tagService = useService(TagService); + const tag = useLiveData(tagService.tagList.tagByTagId$(tagId)); + const color = useLiveData(tag?.color$); + + return ( + + + + ); +}; + +const TagLabel = ({ tagId }: { tagId: string }) => { + const tagService = useService(TagService); + const tag = useLiveData(tagService.tagList.tagByTagId$(tagId)); + const name = useLiveData(tag?.value$); + + return name; +}; + +export const TagsSelector = ({ + init = [], + onCancel, + onConfirm, + ...otherProps +}: TagsSelectorProps) => { + const tagService = useService(TagService); + const tags = useLiveData(tagService.tagList.tags$); + + const list = useMemo(() => { + return tags.map(tag => ({ + id: tag.id, + icon: , + label: , + })); + }, [tags]); + + return ( + + ); +}; diff --git a/packages/frontend/core/src/mobile/styles/mobile.css.ts b/packages/frontend/core/src/mobile/styles/mobile.css.ts index bb1c6312ec..ba96a4c369 100644 --- a/packages/frontend/core/src/mobile/styles/mobile.css.ts +++ b/packages/frontend/core/src/mobile/styles/mobile.css.ts @@ -7,7 +7,7 @@ export const globalVars = { globalStyle(':root', { vars: { - [globalVars.appTabHeight]: '62px', + [globalVars.appTabHeight]: BUILD_CONFIG.isIOS ? '49px' : '62px', }, }); diff --git a/packages/frontend/i18n/src/i18n-completenesses.json b/packages/frontend/i18n/src/i18n-completenesses.json index e99e217acc..ea4eebbd37 100644 --- a/packages/frontend/i18n/src/i18n-completenesses.json +++ b/packages/frontend/i18n/src/i18n-completenesses.json @@ -1,5 +1,5 @@ { - "ar": 82, + "ar": 83, "ca": 6, "da": 6, "de": 31, @@ -13,10 +13,10 @@ "ja": 97, "ko": 86, "pl": 0, - "pt-BR": 94, - "ru": 80, + "pt-BR": 95, + "ru": 81, "sv-SE": 5, "ur": 3, - "zh-Hans": 98, - "zh-Hant": 97 + "zh-Hans": 97, + "zh-Hant": 96 } \ No newline at end of file diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 2e8b1ffafb..27aefbbb4a 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -1366,8 +1366,26 @@ "com.affine.m.explorer.tag.rename-confirm": "Done", "com.affine.m.explorer.tag.new-tip-empty": "Create a tag in this workspace.", "com.affine.m.explorer.tag.new-tip-not-empty": "Create \"{{value}}\" tag in this workspace.", + "com.affine.m.explorer.tag.manage-docs": "Manage Doc(s)", "com.affine.m.explorer.collection.rename": "Rename", "com.affine.m.explorer.collection.rename-menu-title": "Rename Collection", "com.affine.m.explorer.collection.new-dialog-title": "Create Collection", - "com.affine.m.explorer.doc.rename": "Rename" + "com.affine.m.explorer.doc.rename": "Rename", + "com.affine.m.selector.type-doc": "Doc", + "com.affine.m.selector.type-tag": "Tag", + "com.affine.m.selector.type-collection": "Collection", + "com.affine.m.selector.where-folder": "Folder", + "com.affine.m.selector.where-tag": "Tag", + "com.affine.m.selector.where-collection": "Collection", + "com.affine.m.selector.confirm-default": "Apply", + "com.affine.m.selector.title": "Manage {{type}}(s)", + "com.affine.m.selector.info-total": "{{total}} item(s) in this {{where}}", + "com.affine.m.selector.info-added": "Add {{count}} {{type}}(s)", + "com.affine.m.selector.info-removed": "Remove {{count}} {{type}}(s)", + "com.affine.m.selector.remove-warning.title": "Remove items", + "com.affine.m.selector.remove-warning.message": "You unchecked {{type}} that already exist in the current {{where}}, which means you will remove them from this {{where}}. The item will not be deleted.", + "com.affine.m.selector.remove-warning.confirm": "Do not ask again", + "com.affine.m.selector.remove-warning.cancel": "Cancel", + "com.affine.m.selector.remove-warning.where-tag": "tag", + "com.affine.m.selector.remove-warning.where-folder": "folder" }