diff --git a/lib/discord/src/ui/common.ts b/lib/discord/src/ui/common.ts new file mode 100644 index 0000000..ee70102 --- /dev/null +++ b/lib/discord/src/ui/common.ts @@ -0,0 +1,57 @@ +import { lookupModule } from '@revenge-mod/modules/finders' +import { byDependencies, byProps, looseDeps, moduleStateAware, relativeDep } from '@revenge-mod/modules/finders/filters' + +import { proxify } from '@revenge-mod/utils/proxy' + +import type { DiscordModules } from '../../types' + +export let Components: Components = proxify( + () => { + // ID: 3236 + // [3237, 1366, 3238, 3239, 2, ...]; + const module = lookupModule( + moduleStateAware( + byProps('TableRow', 'Button'), + byDependencies(looseDeps([relativeDep(1), undefined, relativeDep(2), relativeDep(3), 2])), + ), + { + includeUninitialized: true, + }, + ) as Components | undefined + + if (module) { + // This allows the Proxy instance to be garbage collected + // after the module is initialized. + Components = module + gc() + return module + } + }, + { + cache: true, + hint: 'object', + }, +)! + +export interface Components { + ActionSheet: DiscordModules.Components.ActionSheet + ActionSheetRow: DiscordModules.Components.ActionSheetRow + ActionSheetSwitchRow: DiscordModules.Components.ActionSheetSwitchRow + AlertActionButton: DiscordModules.Components.AlertActionButton + AlertModal: DiscordModules.Components.AlertModal + Button: DiscordModules.Components.Button + Card: DiscordModules.Components.Card + ContextMenu: DiscordModules.Components.ContextMenu + ContextMenuItem: DiscordModules.Components.ContextMenuItem + FormSwitch: DiscordModules.Components.FormSwitch + IconButton: DiscordModules.Components.IconButton + ImageButton: DiscordModules.Components.ImageButton + Stack: DiscordModules.Components.Stack + TableRow: DiscordModules.Components.TableRow + TableRowTrailingText: DiscordModules.Components.TableRowTrailingText + TableSwitchRow: DiscordModules.Components.TableSwitchRow + Text: DiscordModules.Components.Text + TextArea: DiscordModules.Components.TextArea + TextField: DiscordModules.Components.TextField + TextInput: DiscordModules.Components.TextInput +} diff --git a/lib/discord/src/ui/index.ts b/lib/discord/src/ui/index.ts index 6813118..c4c12c5 100644 --- a/lib/discord/src/ui/index.ts +++ b/lib/discord/src/ui/index.ts @@ -1 +1,2 @@ +export * from './common' export * as settings from './settings' diff --git a/lib/discord/src/ui/settings/common.ts b/lib/discord/src/ui/settings/common.ts index f948b5a..70d1656 100644 --- a/lib/discord/src/ui/settings/common.ts +++ b/lib/discord/src/ui/settings/common.ts @@ -1,6 +1,8 @@ import { lookupModule } from '@revenge-mod/modules/finders' import { byDependencies, byProps, looseDeps, moduleStateAware, relativeDep } from '@revenge-mod/modules/finders/filters' + import { ReactJsxRuntimeModuleId, ReactModuleId, ReactNativeModuleId } from '@revenge-mod/react' + import { proxify } from '@revenge-mod/utils/proxy' import type { ComponentType, ReactNode } from 'react' @@ -11,13 +13,12 @@ export let SettingListRenderer: SettingListRenderer = proxify( moduleStateAware( byProps('SettingsList'), byDependencies( - // biome-ignore lint/suspicious/noSparseArray: don't care looseDeps([ ReactModuleId, ReactNativeModuleId, relativeDep(1), relativeDep(2), - , + undefined, ReactJsxRuntimeModuleId, ]), ), @@ -43,7 +44,7 @@ export let SettingListRenderer: SettingListRenderer = proxify( export interface SettingListRenderer { SettingsList: ComponentType<{ - ListHeaderComponent: ComponentType - sections: Array<{ label: string | ReactNode; settings: string[] }> + ListHeaderComponent?: ComponentType + sections: Array<{ label?: string | ReactNode; settings: string[]; subLabel?: string | ReactNode }> }> } diff --git a/lib/discord/types/index.ts b/lib/discord/types/index.ts index ae235ef..a2976a1 100644 --- a/lib/discord/types/index.ts +++ b/lib/discord/types/index.ts @@ -1,3 +1,15 @@ +import type { ComponentProps, FC, ReactNode } from 'react' +import type { + ImageSourcePropType, + ImageStyle, + PressableProps, + TextInputProps as RNTextInputProps, + TextProps as RNTextProps, + TextStyle, + ViewProps, + ViewStyle, +} from 'react-native' + export * from './native' export namespace DiscordModules { @@ -98,4 +110,312 @@ export namespace DiscordModules { verbose(...args: unknown[]): void } } + + export namespace Styles { + export type TextType = 'heading' | 'text' + export type BasicTextSize = 'sm' | 'md' | 'lg' + export type BasicTextSizeWithExtraLarges = BasicTextSize | 'xl' | 'xxl' + export type TextSize = BasicTextSizeWithExtraLarges | 'xs' | 'xxs' + export type TextWeight = 'normal' | 'medium' | 'semibold' | 'bold' + export type TextWeightWithExtraBold = TextWeight | 'extrabold' + export type RedesignTextCategory = 'message-preview' | 'channel-title' + + export type TextVariant = + | `heading-${BasicTextSizeWithExtraLarges}/${TextWeightWithExtraBold}` + | `text-${TextSize}/${TextWeight}` + | `display-${BasicTextSize}` + | `redesign/${RedesignTextCategory}/${TextWeight}` + | 'redesign/heading-18/bold' + | 'eyebrow' + + export type TextStyleSheet = Record + export type CreateStylesFn = >( + styles: S, + ) => () => S + } + + export namespace Components { + export interface BaseButtonProps extends PressableProps { + disabled?: boolean + size?: ButtonSize + variant?: + | 'primary' + | 'secondary' + | 'tertiary' + | 'destructive' + | 'active' + | 'primary-overlay' + | 'secondary-overlay' + loading?: boolean + grow?: boolean + scaleAmountInPx?: number + } + + export interface ButtonProps extends BaseButtonProps { + icon?: number + loading?: boolean + iconPosition?: 'start' | 'end' + renderIcon?(): ReactNode + renderRightIcon?(): ReactNode + renderShine?(): ReactNode + renderLinearGradient?(): ReactNode + cornerRadius?: number + textStyle?: TextStyle + loadingColorLight?: string + loadingColorDark?: string + text: string + } + + // Buttons + export type ButtonSize = 'sm' | 'md' | 'lg' + export type Button = FC + + export interface IconButtonProps extends BaseButtonProps { + icon: number + label?: string + } + + export type IconButton = FC + + export interface ImageButtonProps extends BaseButtonProps { + image: ImageSourcePropType + } + + export type ImageButton = FC + + export interface StackProps extends ViewProps { + spacing?: number + direction?: 'vertical' | 'horizontal' + } + + export type Stack = FC + + export interface CardProps extends ViewProps { + start?: boolean + end?: boolean + variant?: 'primary' | 'secondary' + border?: 'faint' | 'normal' | 'strong' | 'subtle' + // TODO + shadow?: 'none' + children: ReactNode + } + + export type Card = FC + + // Inputs + export interface TextFieldProps { + onChange?: (value: string) => void + onBlur?: () => void + onFocus?: () => void + + leadingIcon?: () => JSX.Element + trailingIcon?: () => JSX.Element + leadingText?: string + trailingText?: string + description?: string + errorMessage?: string + + isDisabled?: boolean + focusable?: boolean + editable?: boolean + status?: TextFieldStatus + + defaultValue?: string + value?: string + + placeholder?: string + placeholderTextColor?: string + + maxLength?: number + multiline?: boolean + + autoFocus?: boolean + secureTextEntry?: boolean + returnKeyType?: RNTextInputProps['returnKeyType'] + isClearable?: boolean + + size?: TextFieldSize + style?: ViewStyle | ViewStyle[] + } + + export type TextFieldSize = 'sm' | 'md' | 'lg' + + export type TextFieldStatus = 'default' | 'error' + + export interface TextInputProps extends TextFieldProps { + isRound?: boolean + label?: string + } + + export interface TextAreaProps extends Omit {} + + export type TextInput = FC + export type TextField = FC + export type TextArea = FC + + export interface FormSwitchProps extends ViewProps { + value: boolean + onValueChange(value: boolean): void + disabled?: boolean + } + + export type FormSwitch = FC + // TODO + // export type FormRadio = FC + // export type FormCheckbox = FC + + export interface ActionSheetProps { + children: ReactNode + } + + export type ActionSheet = FC + + export interface ActionSheetCloseButtonProps extends Pick, 'variant' | 'onPress'> {} + + export type ActionSheetCloseButton = FC + + export type ActionSheetRow = TableRow + export type ActionSheetRowIcon = TableRowIcon + export type ActionSheetRowGroup = TableRowGroup + export type ActionSheetSwitchRow = TableSwitchRow + // TODO + // export type ActionSheetIconHeader = FC + // export type ActionSheetHeaderBar = FC + // export type BottomSheetTitleHeader = FC + + export type IconSize = + | 'extraSmall10' + | 'extraSmall' + | 'small' + | 'small20' + | 'medium' + | 'large' + | 'custom' + | 'refreshSmall16' + | 'small14' + + export type TableRowVariant = 'default' | 'danger' + + export interface TableRowProps { + label: string + subLabel?: string + icon?: ReactNode + trailing?: ReactNode + arrow?: boolean + onPress?: PressableProps['onPress'] + disabled?: boolean + draggable?: boolean + dragHandlePressableProps?: PressableProps + labelLineClamp?: number + subLabelLineClamp?: number + // TODO + start?: unknown + end?: unknown + variant?: TableRowVariant + } + + export type TableRow = FC & { + Arrow: FC + Icon: TableRowIcon + } + + export interface TableSwitchRowProps extends Omit { + accessibilityHint?: string + value: boolean + onValueChange(value: boolean): void + } + + export type TableSwitchRow = FC + + export interface TableRowGroupProps { + title?: string + description?: string + hasIcons?: boolean + accessibilityLabel?: string + accessibilityRole?: string + children: ReactNode + } + + export type TableRowGroup = FC + + export interface TableRowGroupTitleProps { + title: string + } + + export type TableRowGroupTitle = FC + + export type TableRowIconVariant = + | 'default' + | 'blurple' + | 'boosting-pink' + | 'status-online' + | 'status-idle' + | 'status-dnd' + | 'status-offline' + | 'xbox' + | 'playstation' + | 'danger' + | 'secondary' + | 'translucent' + + export interface TableRowIconProps { + source: ImageSourcePropType + variant?: TableRowIconVariant + } + + export type TableRowIcon = FC + + export interface TableRowTrailingTextProps { + text: string + } + + export type TableRowTrailingText = FC + + export interface AlertModalProps { + title: string + content?: ReactNode + extraContent?: ReactNode + actions?: ReactNode + } + + export type AlertModal = FC + + export type AlertActionButton = Button + + export interface ContextMenuProps { + title: ReactNode + triggerOnLongPress?: boolean + items: Array + align?: 'left' | 'right' | 'above' | 'below' + children: (props: Partial) => ReactNode + } + + export type ContextMenu = FC + + export interface ContextMenuItem { + label: string + IconComponent?: FC + variant?: 'default' | 'destructive' + action(): void + } + + export interface TextProps extends RNTextProps { + variant?: DiscordModules.Styles.TextVariant + color?: string + style?: TextStyle + lineClamp?: number + ellipsizeMode?: 'head' | 'middle' | 'tail' | 'clip' + tabularNumbers?: boolean + children?: ReactNode + } + + export type Text = FC + + export interface IntlLinkProps { + target: string + children?: ReactNode + } + + export type IntlLink = FC + } }