diff options
author | Samuel Newman <mozzius@protonmail.com> | 2025-08-27 19:00:36 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-08-27 09:00:36 -0700 |
commit | 39d460db510d6545794f6acba8226fb52b506b40 (patch) | |
tree | b3ca00a3dfb4c790fb78369942c122cee79ba9d0 /src/view/com/util/forms/DropdownButton.tsx | |
parent | eac02901435d7bc79a28e0bff665352b814f9508 (diff) | |
download | voidsky-39d460db510d6545794f6acba8226fb52b506b40.tar.zst |
Language select final tweaks (#8914)
* [APP-1303] Redesign/refactor post language select (#8884) * Nightly source-language update * Nightly source-language update * [APP-1303] Redesign/refactor post language select * update: stylesheets.create to use the latest structure * update styles to modern structure * update: dialog breakpoints on web and delete depricated language modals * remove unused post languages settings dialog * restructure Post languages dialog * place the Dialog.Close inside the Dialog.ScrollableInner * add: language search * update search and language variables for clarity * fix: memoize language state lists * chore: add comments * update proper colors to the background * add back older error boundary * add: tweaks to the mobile and web responsiveness * add tweaks to center the container * update labels * update button and border * added translation updates * Update: text input to reuse search input * remove unused file * update: web breakpoints * run eslint and prettier --------- Co-authored-by: Elijah Seed-Arita <elijaharita@gmail.com> Co-authored-by: Anastasiya Uraleva <anastasiya@Anastasiyas-MacBook-Pro.local> Co-authored-by: Anastasiya Uraleva <anastasiya@Mac.localdomain> * rm old file * sort out styles, add FlatListFooter component * rm cancel button in favor of search input X * get dialog height working on iOS * delete `DropdownButton` * hide scroll indicators on android * ios scroll indicator insets * get footer sorta working on android * change button color on press * rm empty file --------- Co-authored-by: Anastasiya Uraleva <anastasiyauraleva@gmail.com> Co-authored-by: Elijah Seed-Arita <elijaharita@gmail.com> Co-authored-by: Anastasiya Uraleva <anastasiya@Anastasiyas-MacBook-Pro.local> Co-authored-by: Anastasiya Uraleva <anastasiya@Mac.localdomain>
Diffstat (limited to 'src/view/com/util/forms/DropdownButton.tsx')
-rw-r--r-- | src/view/com/util/forms/DropdownButton.tsx | 397 |
1 files changed, 0 insertions, 397 deletions
diff --git a/src/view/com/util/forms/DropdownButton.tsx b/src/view/com/util/forms/DropdownButton.tsx deleted file mode 100644 index e20dadb49..000000000 --- a/src/view/com/util/forms/DropdownButton.tsx +++ /dev/null @@ -1,397 +0,0 @@ -import {type PropsWithChildren} from 'react' -import {useMemo, useRef} from 'react' -import { - Dimensions, - type GestureResponderEvent, - type Insets, - type StyleProp, - StyleSheet, - TouchableOpacity, - TouchableWithoutFeedback, - useWindowDimensions, - View, - type ViewStyle, -} from 'react-native' -import Animated, {FadeIn, FadeInDown, FadeInUp} from 'react-native-reanimated' -import RootSiblings from 'react-native-root-siblings' -import {type IconProp} from '@fortawesome/fontawesome-svg-core' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import type React from 'react' - -import {HITSLOP_10} from '#/lib/constants' -import {usePalette} from '#/lib/hooks/usePalette' -import {colors} from '#/lib/styles' -import {useTheme} from '#/lib/ThemeContext' -import {isWeb} from '#/platform/detection' -import {native} from '#/alf' -import {FullWindowOverlay} from '#/components/FullWindowOverlay' -import {Text} from '../text/Text' -import {Button, type ButtonType} from './Button' - -const ESTIMATED_BTN_HEIGHT = 50 -const ESTIMATED_SEP_HEIGHT = 16 -const ESTIMATED_HEADING_HEIGHT = 60 - -export interface DropdownItemButton { - testID?: string - icon?: IconProp - label: string - onPress: () => void -} -export interface DropdownItemSeparator { - sep: true -} -export interface DropdownItemHeading { - heading: true - label: string -} -export type DropdownItem = - | DropdownItemButton - | DropdownItemSeparator - | DropdownItemHeading -type MaybeDropdownItem = DropdownItem | false | undefined - -export type DropdownButtonType = ButtonType | 'bare' - -interface DropdownButtonProps { - testID?: string - type?: DropdownButtonType - style?: StyleProp<ViewStyle> - items: MaybeDropdownItem[] - label?: string - menuWidth?: number - children?: React.ReactNode - openToRight?: boolean - openUpwards?: boolean - rightOffset?: number - bottomOffset?: number - hitSlop?: Insets - accessibilityLabel?: string - accessibilityHint?: string -} - -/** - * @deprecated use Menu from `#/components/Menu.tsx` instead - */ -export function DropdownButton({ - testID, - type = 'bare', - style, - items, - label, - menuWidth, - children, - openToRight = false, - openUpwards = false, - rightOffset = 0, - bottomOffset = 0, - hitSlop = HITSLOP_10, - accessibilityLabel, -}: PropsWithChildren<DropdownButtonProps>) { - const {_} = useLingui() - - const ref1 = useRef<View>(null) - const ref2 = useRef<View>(null) - - const onPress = (e: GestureResponderEvent) => { - const ref = ref1.current || ref2.current - const {height: winHeight} = Dimensions.get('window') - const pressY = e.nativeEvent.pageY - ref?.measure( - ( - _x: number, - _y: number, - width: number, - _height: number, - pageX: number, - pageY: number, - ) => { - if (!menuWidth) { - menuWidth = 200 - } - let estimatedMenuHeight = 0 - for (const item of items) { - if (item && isSep(item)) { - estimatedMenuHeight += ESTIMATED_SEP_HEIGHT - } else if (item && isBtn(item)) { - estimatedMenuHeight += ESTIMATED_BTN_HEIGHT - } else if (item && isHeading(item)) { - estimatedMenuHeight += ESTIMATED_HEADING_HEIGHT - } - } - const newX = openToRight - ? pageX + width + rightOffset - : pageX + width - menuWidth - - // Add a bit of additional room - let newY = pressY + bottomOffset + 20 - if (openUpwards || newY + estimatedMenuHeight > winHeight) { - newY -= estimatedMenuHeight - } - createDropdownMenu( - newX, - newY, - pageY, - menuWidth, - items.filter(v => !!v) as DropdownItem[], - openUpwards, - ) - }, - ) - } - - const numItems = useMemo( - () => - items.filter(item => { - if (item === undefined || item === false) { - return false - } - - return isBtn(item) - }).length, - [items], - ) - - if (type === 'bare') { - return ( - <TouchableOpacity - testID={testID} - style={style} - onPress={onPress} - hitSlop={hitSlop} - ref={ref1} - accessibilityRole="button" - accessibilityLabel={ - accessibilityLabel || _(msg`Opens ${numItems} options`) - } - accessibilityHint=""> - {children} - </TouchableOpacity> - ) - } - return ( - <View ref={ref2}> - <Button - type={type} - testID={testID} - onPress={onPress} - style={style} - label={label}> - {children} - </Button> - </View> - ) -} - -function createDropdownMenu( - x: number, - y: number, - pageY: number, - width: number, - items: DropdownItem[], - opensUpwards = false, -): RootSiblings { - const onPressItem = (index: number) => { - sibling.destroy() - const item = items[index] - if (isBtn(item)) { - item.onPress() - } - } - const onOuterPress = () => sibling.destroy() - const sibling = new RootSiblings( - ( - <DropdownItems - onOuterPress={onOuterPress} - x={x} - y={y} - pageY={pageY} - width={width} - items={items} - onPressItem={onPressItem} - openUpwards={opensUpwards} - /> - ), - ) - return sibling -} - -type DropDownItemProps = { - onOuterPress: () => void - x: number - y: number - pageY: number - width: number - items: DropdownItem[] - onPressItem: (index: number) => void - openUpwards: boolean -} - -const DropdownItems = ({ - onOuterPress, - x, - y, - pageY, - width, - items, - onPressItem, - openUpwards, -}: DropDownItemProps) => { - const pal = usePalette('default') - const theme = useTheme() - const {_} = useLingui() - const {height: screenHeight} = useWindowDimensions() - const dropDownBackgroundColor = - theme.colorScheme === 'dark' ? pal.btn : pal.view - const separatorColor = - theme.colorScheme === 'dark' ? pal.borderDark : pal.border - - const numItems = items.filter(isBtn).length - - // TODO: Refactor dropdown components to: - // - (On web, if not handled by React Native) use semantic <select /> - // and <option /> elements for keyboard navigation out of the box - // - (On mobile) be buttons by default, accept `label` and `nativeID` - // props, and always have an explicit label - return ( - <FullWindowOverlay> - {/* This TouchableWithoutFeedback renders the background so if the user clicks outside, the dropdown closes */} - <TouchableWithoutFeedback - onPress={onOuterPress} - accessibilityLabel={_(msg`Toggle dropdown`)} - accessibilityHint=""> - <Animated.View - entering={FadeIn} - style={[ - styles.bg, - // On web we need to adjust the top and bottom relative to the scroll position - isWeb - ? { - top: -pageY, - bottom: pageY - screenHeight, - } - : { - top: 0, - bottom: 0, - }, - ]} - /> - </TouchableWithoutFeedback> - <Animated.View - entering={native( - openUpwards ? FadeInDown.springify(1000) : FadeInUp.springify(1000), - )} - style={[ - styles.menu, - {left: x, top: y, width}, - dropDownBackgroundColor, - ]}> - {items.map((item, index) => { - if (isBtn(item)) { - return ( - <TouchableOpacity - testID={item.testID} - key={index} - style={[styles.menuItem]} - onPress={() => onPressItem(index)} - accessibilityRole="button" - accessibilityLabel={item.label} - accessibilityHint={_( - msg`Selects option ${index + 1} of ${numItems}`, - )}> - {item.icon && ( - <FontAwesomeIcon - style={styles.icon} - icon={item.icon} - color={pal.text.color as string} - /> - )} - <Text style={[styles.label, pal.text]}>{item.label}</Text> - </TouchableOpacity> - ) - } else if (isSep(item)) { - return ( - <View key={index} style={[styles.separator, separatorColor]} /> - ) - } else if (isHeading(item)) { - return ( - <View style={[styles.heading, pal.border]} key={index}> - <Text style={[pal.text, styles.headingLabel]}> - {item.label} - </Text> - </View> - ) - } - return null - })} - </Animated.View> - </FullWindowOverlay> - ) -} - -function isSep(item: DropdownItem): item is DropdownItemSeparator { - return 'sep' in item && item.sep -} -function isHeading(item: DropdownItem): item is DropdownItemHeading { - return 'heading' in item && item.heading -} -function isBtn(item: DropdownItem): item is DropdownItemButton { - return !isSep(item) && !isHeading(item) -} - -const styles = StyleSheet.create({ - bg: { - position: 'absolute', - left: 0, - width: '100%', - backgroundColor: 'rgba(0, 0, 0, 0.1)', - }, - menu: { - position: 'absolute', - backgroundColor: '#fff', - borderRadius: 14, - paddingVertical: 6, - }, - menuItem: { - flexDirection: 'row', - alignItems: 'center', - paddingVertical: 10, - paddingLeft: 15, - paddingRight: 40, - }, - menuItemBorder: { - borderTopWidth: 1, - borderTopColor: colors.gray1, - marginTop: 4, - paddingTop: 12, - }, - icon: { - marginLeft: 2, - marginRight: 8, - flexShrink: 0, - }, - label: { - fontSize: 18, - flexShrink: 1, - flexGrow: 1, - }, - separator: { - borderTopWidth: 1, - marginVertical: 8, - }, - heading: { - flexDirection: 'row', - justifyContent: 'center', - paddingVertical: 10, - paddingLeft: 15, - paddingRight: 20, - borderBottomWidth: 1, - marginBottom: 6, - }, - headingLabel: { - fontSize: 18, - fontWeight: '600', - }, -}) |