Further align web `List` with `FlatList`, add `contain` mode to web list implementation (#3867)
* add `onStartReached` to web list * fix `rootMargin` * Add `contain`, handle scroll events * improve types, fix typo * simplify * adjust `scrollToTop` and `scrollToOffset` to support `contain`, add `scrollToEnd` * rename `handleWindowScroll` to `handleScroll` * support basic `maintainVisibleContentPosition` * rename `contain` to `containWeb` * remove unnecessary `flex: 1` * add missing props * add root prop to `Visibility` * add root prop to `Visibility` * revert adding `maintainVisibleContentPosition` * oops * always apply `flex: 1` to styles when contained * add a contained list to storybook * make `onScroll` a worklet in storybook * revert test code * add scrolling to storybook * simplify getting scrollable node * nit: extra whitespace * nit: random comment * foolproof the logic * typecheckzio/stable
parent
594b40c3ae
commit
bc07019911
|
@ -25,6 +25,7 @@ export type ListProps<ItemT> = Omit<
|
||||||
headerOffset?: number
|
headerOffset?: number
|
||||||
refreshing?: boolean
|
refreshing?: boolean
|
||||||
onRefresh?: () => void
|
onRefresh?: () => void
|
||||||
|
containWeb?: boolean
|
||||||
}
|
}
|
||||||
export type ListRef = React.MutableRefObject<FlatList_INTERNAL | null>
|
export type ListRef = React.MutableRefObject<FlatList_INTERNAL | null>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React, {isValidElement, memo, startTransition, useRef} from 'react'
|
import React, {isValidElement, memo, startTransition, useRef} from 'react'
|
||||||
import {FlatListProps, StyleSheet, View, ViewProps} from 'react-native'
|
import {FlatListProps, StyleSheet, View, ViewProps} from 'react-native'
|
||||||
|
import {ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/reanimated2/hook/commonTypes'
|
||||||
|
|
||||||
import {batchedUpdates} from '#/lib/batchedUpdates'
|
import {batchedUpdates} from '#/lib/batchedUpdates'
|
||||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||||
|
@ -20,6 +21,7 @@ export type ListProps<ItemT> = Omit<
|
||||||
refreshing?: boolean
|
refreshing?: boolean
|
||||||
onRefresh?: () => void
|
onRefresh?: () => void
|
||||||
desktopFixedHeight: any // TODO: Better types.
|
desktopFixedHeight: any // TODO: Better types.
|
||||||
|
containWeb?: boolean
|
||||||
}
|
}
|
||||||
export type ListRef = React.MutableRefObject<any | null> // TODO: Better types.
|
export type ListRef = React.MutableRefObject<any | null> // TODO: Better types.
|
||||||
|
|
||||||
|
@ -27,6 +29,7 @@ function ListImpl<ItemT>(
|
||||||
{
|
{
|
||||||
ListHeaderComponent,
|
ListHeaderComponent,
|
||||||
ListFooterComponent,
|
ListFooterComponent,
|
||||||
|
containWeb,
|
||||||
contentContainerStyle,
|
contentContainerStyle,
|
||||||
data,
|
data,
|
||||||
desktopFixedHeight,
|
desktopFixedHeight,
|
||||||
|
@ -83,13 +86,62 @@ function ListImpl<ItemT>(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getScrollableNode = React.useCallback(() => {
|
||||||
|
if (containWeb) {
|
||||||
|
const element = nativeRef.current as HTMLDivElement | null
|
||||||
|
if (!element) return
|
||||||
|
|
||||||
|
return {
|
||||||
|
scrollWidth: element.scrollWidth,
|
||||||
|
scrollHeight: element.scrollHeight,
|
||||||
|
clientWidth: element.clientWidth,
|
||||||
|
clientHeight: element.clientHeight,
|
||||||
|
scrollY: element.scrollTop,
|
||||||
|
scrollX: element.scrollLeft,
|
||||||
|
scrollTo(options?: ScrollToOptions) {
|
||||||
|
element.scrollTo(options)
|
||||||
|
},
|
||||||
|
scrollBy(options: ScrollToOptions) {
|
||||||
|
element.scrollBy(options)
|
||||||
|
},
|
||||||
|
addEventListener(event: string, handler: any) {
|
||||||
|
element.addEventListener(event, handler)
|
||||||
|
},
|
||||||
|
removeEventListener(event: string, handler: any) {
|
||||||
|
element.removeEventListener(event, handler)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
scrollWidth: document.documentElement.scrollWidth,
|
||||||
|
scrollHeight: document.documentElement.scrollHeight,
|
||||||
|
clientWidth: window.innerWidth,
|
||||||
|
clientHeight: window.innerHeight,
|
||||||
|
scrollY: window.scrollY,
|
||||||
|
scrollX: window.scrollX,
|
||||||
|
scrollTo(options: ScrollToOptions) {
|
||||||
|
window.scrollTo(options)
|
||||||
|
},
|
||||||
|
scrollBy(options: ScrollToOptions) {
|
||||||
|
window.scrollBy(options)
|
||||||
|
},
|
||||||
|
addEventListener(event: string, handler: any) {
|
||||||
|
window.addEventListener(event, handler)
|
||||||
|
},
|
||||||
|
removeEventListener(event: string, handler: any) {
|
||||||
|
window.removeEventListener(event, handler)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [containWeb])
|
||||||
|
|
||||||
const nativeRef = React.useRef(null)
|
const nativeRef = React.useRef(null)
|
||||||
React.useImperativeHandle(
|
React.useImperativeHandle(
|
||||||
ref,
|
ref,
|
||||||
() =>
|
() =>
|
||||||
({
|
({
|
||||||
scrollToTop() {
|
scrollToTop() {
|
||||||
window.scrollTo({top: 0})
|
getScrollableNode()?.scrollTo({top: 0})
|
||||||
},
|
},
|
||||||
scrollToOffset({
|
scrollToOffset({
|
||||||
animated,
|
animated,
|
||||||
|
@ -98,46 +150,74 @@ function ListImpl<ItemT>(
|
||||||
animated: boolean
|
animated: boolean
|
||||||
offset: number
|
offset: number
|
||||||
}) {
|
}) {
|
||||||
window.scrollTo({
|
getScrollableNode()?.scrollTo({
|
||||||
left: 0,
|
left: 0,
|
||||||
top: offset,
|
top: offset,
|
||||||
behavior: animated ? 'smooth' : 'instant',
|
behavior: animated ? 'smooth' : 'instant',
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
scrollToEnd({animated = true}: {animated?: boolean}) {
|
||||||
|
const element = getScrollableNode()
|
||||||
|
element?.scrollTo({
|
||||||
|
left: 0,
|
||||||
|
top: element.scrollHeight,
|
||||||
|
behavior: animated ? 'smooth' : 'instant',
|
||||||
|
})
|
||||||
|
},
|
||||||
} as any), // TODO: Better types.
|
} as any), // TODO: Better types.
|
||||||
[],
|
[getScrollableNode],
|
||||||
)
|
)
|
||||||
|
|
||||||
// --- onContentSizeChange ---
|
// --- onContentSizeChange, maintainVisibleContentPosition ---
|
||||||
const containerRef = useRef(null)
|
const containerRef = useRef(null)
|
||||||
useResizeObserver(containerRef, onContentSizeChange)
|
useResizeObserver(containerRef, onContentSizeChange)
|
||||||
|
|
||||||
// --- onScroll ---
|
// --- onScroll ---
|
||||||
const [isInsideVisibleTree, setIsInsideVisibleTree] = React.useState(false)
|
const [isInsideVisibleTree, setIsInsideVisibleTree] = React.useState(false)
|
||||||
const handleWindowScroll = useNonReactiveCallback(() => {
|
const handleScroll = useNonReactiveCallback(() => {
|
||||||
if (isInsideVisibleTree) {
|
if (!isInsideVisibleTree) return
|
||||||
contextScrollHandlers.onScroll?.(
|
|
||||||
{
|
const element = getScrollableNode()
|
||||||
contentOffset: {
|
contextScrollHandlers.onScroll?.(
|
||||||
x: Math.max(0, window.scrollX),
|
{
|
||||||
y: Math.max(0, window.scrollY),
|
contentOffset: {
|
||||||
},
|
x: Math.max(0, element?.scrollX ?? 0),
|
||||||
} as any, // TODO: Better types.
|
y: Math.max(0, element?.scrollY ?? 0),
|
||||||
null as any,
|
},
|
||||||
)
|
layoutMeasurement: {
|
||||||
}
|
width: element?.clientWidth,
|
||||||
|
height: element?.clientHeight,
|
||||||
|
},
|
||||||
|
contentSize: {
|
||||||
|
width: element?.scrollWidth,
|
||||||
|
height: element?.scrollHeight,
|
||||||
|
},
|
||||||
|
} as Exclude<
|
||||||
|
ReanimatedScrollEvent,
|
||||||
|
| 'velocity'
|
||||||
|
| 'eventName'
|
||||||
|
| 'zoomScale'
|
||||||
|
| 'targetContentOffset'
|
||||||
|
| 'contentInset'
|
||||||
|
>,
|
||||||
|
null as any,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!isInsideVisibleTree) {
|
if (!isInsideVisibleTree) {
|
||||||
// Prevents hidden tabs from firing scroll events.
|
// Prevents hidden tabs from firing scroll events.
|
||||||
// Only one list is expected to be firing these at a time.
|
// Only one list is expected to be firing these at a time.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
window.addEventListener('scroll', handleWindowScroll)
|
|
||||||
|
const element = getScrollableNode()
|
||||||
|
|
||||||
|
element?.addEventListener('scroll', handleScroll)
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('scroll', handleWindowScroll)
|
element?.removeEventListener('scroll', handleScroll)
|
||||||
}
|
}
|
||||||
}, [isInsideVisibleTree, handleWindowScroll])
|
}, [isInsideVisibleTree, handleScroll, containWeb, getScrollableNode])
|
||||||
|
|
||||||
// --- onScrolledDownChange ---
|
// --- onScrolledDownChange ---
|
||||||
const isScrolledDown = useRef(false)
|
const isScrolledDown = useRef(false)
|
||||||
|
@ -174,7 +254,11 @@ function ListImpl<ItemT>(
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View {...props} style={style} ref={nativeRef}>
|
<View
|
||||||
|
{...props}
|
||||||
|
// @ts-ignore web only
|
||||||
|
style={[style, containWeb && {flex: 1, 'overflow-y': 'scroll'}]}
|
||||||
|
ref={nativeRef}>
|
||||||
<Visibility
|
<Visibility
|
||||||
onVisibleChange={setIsInsideVisibleTree}
|
onVisibleChange={setIsInsideVisibleTree}
|
||||||
style={
|
style={
|
||||||
|
@ -192,11 +276,13 @@ function ListImpl<ItemT>(
|
||||||
pal.border,
|
pal.border,
|
||||||
]}>
|
]}>
|
||||||
<Visibility
|
<Visibility
|
||||||
|
root={containWeb ? nativeRef.current : null}
|
||||||
onVisibleChange={handleAboveTheFoldVisibleChange}
|
onVisibleChange={handleAboveTheFoldVisibleChange}
|
||||||
style={[styles.aboveTheFoldDetector, {height: headerOffset}]}
|
style={[styles.aboveTheFoldDetector, {height: headerOffset}]}
|
||||||
/>
|
/>
|
||||||
{onStartReached && (
|
{onStartReached && (
|
||||||
<Visibility
|
<Visibility
|
||||||
|
root={containWeb ? nativeRef.current : null}
|
||||||
onVisibleChange={onHeadVisibilityChange}
|
onVisibleChange={onHeadVisibilityChange}
|
||||||
topMargin={(onStartReachedThreshold ?? 0) * 100 + '%'}
|
topMargin={(onStartReachedThreshold ?? 0) * 100 + '%'}
|
||||||
/>
|
/>
|
||||||
|
@ -213,6 +299,7 @@ function ListImpl<ItemT>(
|
||||||
))}
|
))}
|
||||||
{onEndReached && (
|
{onEndReached && (
|
||||||
<Visibility
|
<Visibility
|
||||||
|
root={containWeb ? nativeRef.current : null}
|
||||||
onVisibleChange={onTailVisibilityChange}
|
onVisibleChange={onTailVisibilityChange}
|
||||||
bottomMargin={(onEndReachedThreshold ?? 0) * 100 + '%'}
|
bottomMargin={(onEndReachedThreshold ?? 0) * 100 + '%'}
|
||||||
/>
|
/>
|
||||||
|
@ -275,11 +362,13 @@ let Row = function RowImpl<ItemT>({
|
||||||
Row = React.memo(Row)
|
Row = React.memo(Row)
|
||||||
|
|
||||||
let Visibility = ({
|
let Visibility = ({
|
||||||
|
root = null,
|
||||||
topMargin = '0px',
|
topMargin = '0px',
|
||||||
bottomMargin = '0px',
|
bottomMargin = '0px',
|
||||||
onVisibleChange,
|
onVisibleChange,
|
||||||
style,
|
style,
|
||||||
}: {
|
}: {
|
||||||
|
root?: Element | null
|
||||||
topMargin?: string
|
topMargin?: string
|
||||||
bottomMargin?: string
|
bottomMargin?: string
|
||||||
onVisibleChange: (isVisible: boolean) => void
|
onVisibleChange: (isVisible: boolean) => void
|
||||||
|
@ -303,6 +392,7 @@ let Visibility = ({
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const observer = new IntersectionObserver(handleIntersection, {
|
const observer = new IntersectionObserver(handleIntersection, {
|
||||||
|
root,
|
||||||
rootMargin: `${topMargin} 0px ${bottomMargin} 0px`,
|
rootMargin: `${topMargin} 0px ${bottomMargin} 0px`,
|
||||||
})
|
})
|
||||||
const tail: Element | null = tailRef.current!
|
const tail: Element | null = tailRef.current!
|
||||||
|
@ -310,7 +400,7 @@ let Visibility = ({
|
||||||
return () => {
|
return () => {
|
||||||
observer.unobserve(tail)
|
observer.unobserve(tail)
|
||||||
}
|
}
|
||||||
}, [bottomMargin, handleIntersection, topMargin])
|
}, [bottomMargin, handleIntersection, topMargin, root])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View ref={tailRef} style={addStyle(styles.visibilityDetector, style)} />
|
<View ref={tailRef} style={addStyle(styles.visibilityDetector, style)} />
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
import React from 'react'
|
||||||
|
import {FlatList, View} from 'react-native'
|
||||||
|
|
||||||
|
import {ScrollProvider} from 'lib/ScrollContext'
|
||||||
|
import {List} from 'view/com/util/List'
|
||||||
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
|
export function ListContained() {
|
||||||
|
const [animated, setAnimated] = React.useState(false)
|
||||||
|
const ref = React.useRef<FlatList>(null)
|
||||||
|
|
||||||
|
const data = React.useMemo(() => {
|
||||||
|
return Array.from({length: 100}, (_, i) => ({
|
||||||
|
id: i,
|
||||||
|
text: `Message ${i}`,
|
||||||
|
}))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<View style={{width: '100%', height: 300}}>
|
||||||
|
<ScrollProvider
|
||||||
|
onScroll={() => {
|
||||||
|
'worklet'
|
||||||
|
console.log('onScroll')
|
||||||
|
}}>
|
||||||
|
<List
|
||||||
|
data={data}
|
||||||
|
renderItem={item => {
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
padding: 10,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: 'rgba(0,0,0,0.1)',
|
||||||
|
}}>
|
||||||
|
<Text>{item.item.text}</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
keyExtractor={item => item.id.toString()}
|
||||||
|
containWeb={true}
|
||||||
|
style={{flex: 1}}
|
||||||
|
onStartReached={() => {
|
||||||
|
console.log('Start Reached')
|
||||||
|
}}
|
||||||
|
onEndReached={() => {
|
||||||
|
console.log('End Reached (threshold of 2)')
|
||||||
|
}}
|
||||||
|
onEndReachedThreshold={2}
|
||||||
|
ref={ref}
|
||||||
|
disableVirtualization={true}
|
||||||
|
/>
|
||||||
|
</ScrollProvider>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={{flexDirection: 'row', gap: 10, alignItems: 'center'}}>
|
||||||
|
<Toggle.Item
|
||||||
|
name="a"
|
||||||
|
label="Click me"
|
||||||
|
value={animated}
|
||||||
|
onChange={() => setAnimated(prev => !prev)}>
|
||||||
|
<Toggle.Checkbox />
|
||||||
|
<Toggle.LabelText>Animated Scrolling</Toggle.LabelText>
|
||||||
|
</Toggle.Item>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
label="Scroll to End"
|
||||||
|
onPress={() => ref.current?.scrollToOffset({animated, offset: 0})}>
|
||||||
|
<ButtonText>Scroll to Top</ButtonText>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
label="Scroll to End"
|
||||||
|
onPress={() => ref.current?.scrollToEnd({animated})}>
|
||||||
|
<ButtonText>Scroll to End</ButtonText>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
label="Scroll to Offset 100"
|
||||||
|
onPress={() => ref.current?.scrollToOffset({animated, offset: 500})}>
|
||||||
|
<ButtonText>Scroll to Offset 500</ButtonText>
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {View} from 'react-native'
|
import {ScrollView, View} from 'react-native'
|
||||||
|
|
||||||
import {useSetThemePrefs} from '#/state/shell'
|
import {useSetThemePrefs} from '#/state/shell'
|
||||||
import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
import {isWeb} from 'platform/detection'
|
||||||
|
import {CenteredView} from '#/view/com/util/Views'
|
||||||
|
import {ListContained} from 'view/screens/Storybook/ListContained'
|
||||||
import {atoms as a, ThemeProvider, useTheme} from '#/alf'
|
import {atoms as a, ThemeProvider, useTheme} from '#/alf'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import {Breakpoints} from './Breakpoints'
|
import {Breakpoints} from './Breakpoints'
|
||||||
|
@ -18,77 +20,111 @@ import {Theming} from './Theming'
|
||||||
import {Typography} from './Typography'
|
import {Typography} from './Typography'
|
||||||
|
|
||||||
export function Storybook() {
|
export function Storybook() {
|
||||||
const t = useTheme()
|
if (isWeb) return <StorybookInner />
|
||||||
const {setColorMode, setDarkTheme} = useSetThemePrefs()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
<CenteredView style={[t.atoms.bg]}>
|
<StorybookInner />
|
||||||
<View style={[a.p_xl, a.gap_5xl, {paddingBottom: 200}]}>
|
|
||||||
<View style={[a.flex_row, a.align_start, a.gap_md]}>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
color="primary"
|
|
||||||
size="small"
|
|
||||||
label='Set theme to "system"'
|
|
||||||
onPress={() => setColorMode('system')}>
|
|
||||||
<ButtonText>System</ButtonText>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="solid"
|
|
||||||
color="secondary"
|
|
||||||
size="small"
|
|
||||||
label='Set theme to "light"'
|
|
||||||
onPress={() => setColorMode('light')}>
|
|
||||||
<ButtonText>Light</ButtonText>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="solid"
|
|
||||||
color="secondary"
|
|
||||||
size="small"
|
|
||||||
label='Set theme to "dim"'
|
|
||||||
onPress={() => {
|
|
||||||
setColorMode('dark')
|
|
||||||
setDarkTheme('dim')
|
|
||||||
}}>
|
|
||||||
<ButtonText>Dim</ButtonText>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="solid"
|
|
||||||
color="secondary"
|
|
||||||
size="small"
|
|
||||||
label='Set theme to "dark"'
|
|
||||||
onPress={() => {
|
|
||||||
setColorMode('dark')
|
|
||||||
setDarkTheme('dark')
|
|
||||||
}}>
|
|
||||||
<ButtonText>Dark</ButtonText>
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Dialogs />
|
|
||||||
<ThemeProvider theme="light">
|
|
||||||
<Theming />
|
|
||||||
</ThemeProvider>
|
|
||||||
<ThemeProvider theme="dim">
|
|
||||||
<Theming />
|
|
||||||
</ThemeProvider>
|
|
||||||
<ThemeProvider theme="dark">
|
|
||||||
<Theming />
|
|
||||||
</ThemeProvider>
|
|
||||||
|
|
||||||
<Typography />
|
|
||||||
<Spacing />
|
|
||||||
<Shadows />
|
|
||||||
<Buttons />
|
|
||||||
<Icons />
|
|
||||||
<Links />
|
|
||||||
<Forms />
|
|
||||||
<Dialogs />
|
|
||||||
<Menus />
|
|
||||||
<Breakpoints />
|
|
||||||
</View>
|
|
||||||
</CenteredView>
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function StorybookInner() {
|
||||||
|
const t = useTheme()
|
||||||
|
const {setColorMode, setDarkTheme} = useSetThemePrefs()
|
||||||
|
const [showContainedList, setShowContainedList] = React.useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CenteredView style={[t.atoms.bg]}>
|
||||||
|
<View style={[a.p_xl, a.gap_5xl, {paddingBottom: 200}]}>
|
||||||
|
{!showContainedList ? (
|
||||||
|
<>
|
||||||
|
<View style={[a.flex_row, a.align_start, a.gap_md]}>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
color="primary"
|
||||||
|
size="small"
|
||||||
|
label='Set theme to "system"'
|
||||||
|
onPress={() => setColorMode('system')}>
|
||||||
|
<ButtonText>System</ButtonText>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
color="secondary"
|
||||||
|
size="small"
|
||||||
|
label='Set theme to "light"'
|
||||||
|
onPress={() => setColorMode('light')}>
|
||||||
|
<ButtonText>Light</ButtonText>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
color="secondary"
|
||||||
|
size="small"
|
||||||
|
label='Set theme to "dim"'
|
||||||
|
onPress={() => {
|
||||||
|
setColorMode('dark')
|
||||||
|
setDarkTheme('dim')
|
||||||
|
}}>
|
||||||
|
<ButtonText>Dim</ButtonText>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
color="secondary"
|
||||||
|
size="small"
|
||||||
|
label='Set theme to "dark"'
|
||||||
|
onPress={() => {
|
||||||
|
setColorMode('dark')
|
||||||
|
setDarkTheme('dark')
|
||||||
|
}}>
|
||||||
|
<ButtonText>Dark</ButtonText>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Dialogs />
|
||||||
|
<ThemeProvider theme="light">
|
||||||
|
<Theming />
|
||||||
|
</ThemeProvider>
|
||||||
|
<ThemeProvider theme="dim">
|
||||||
|
<Theming />
|
||||||
|
</ThemeProvider>
|
||||||
|
<ThemeProvider theme="dark">
|
||||||
|
<Theming />
|
||||||
|
</ThemeProvider>
|
||||||
|
|
||||||
|
<Typography />
|
||||||
|
<Spacing />
|
||||||
|
<Shadows />
|
||||||
|
<Buttons />
|
||||||
|
<Icons />
|
||||||
|
<Links />
|
||||||
|
<Forms />
|
||||||
|
<Dialogs />
|
||||||
|
<Menus />
|
||||||
|
<Breakpoints />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
label="Switch to Contained List"
|
||||||
|
onPress={() => setShowContainedList(true)}>
|
||||||
|
<ButtonText>Switch to Contained List</ButtonText>
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
label="Switch to Storybook"
|
||||||
|
onPress={() => setShowContainedList(false)}>
|
||||||
|
<ButtonText>Switch to Storybook</ButtonText>
|
||||||
|
</Button>
|
||||||
|
<ListContained />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</CenteredView>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue