Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Alert Collapse/Expand #288

Merged
merged 45 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
8bbbaed
Add expanded component state, collapse functionality, collapse icon
narin Apr 15, 2024
f64e30d
Add collapsible story
narin Apr 15, 2024
721c440
Remove commented haptics and scrollTo code. Fixed left margin for col…
narin Apr 15, 2024
91ccb43
Change story header text
narin Apr 15, 2024
3e09d1d
Refactor to make entire header area pressable
narin Apr 15, 2024
b5f67f3
Clean up unneeded imports
narin Apr 15, 2024
bed831d
Clean up prop comments
narin Apr 15, 2024
10bcdb6
Sort internal functions by display order
narin Apr 15, 2024
f7c17bd
Remove unneeded collapsed variable
narin Apr 15, 2024
64c104e
Clean up
narin Apr 15, 2024
b08aada
Version bump: components-v0.12.1-beta.2
va-mobile-automation-robot Apr 15, 2024
cf5c3f4
Export Alert
narin Apr 15, 2024
eafac84
Version bump: components-v0.12.1-beta.3
va-mobile-automation-robot Apr 15, 2024
09c4d9c
Add initialCollapsedState prop
narin Apr 15, 2024
a6219ff
Version bump: components-v0.12.1-beta.4
va-mobile-automation-robot Apr 15, 2024
887b01a
Merge branch 'main' into feature/246-narin-alert-collapsible
narin Apr 15, 2024
edccedf
Clean up header text logic
narin Apr 15, 2024
5c357f9
Remove initial collapsed state from story
narin Apr 15, 2024
28eb3f1
Use expandable terminology instead of collapsible to match VADS. Add …
narin Apr 15, 2024
66942fc
Rename story
narin Apr 15, 2024
c07fdd5
Reorder initialExpandedState possible values
narin Apr 15, 2024
aefcb0a
Add comments. Remove unneeded View
narin Apr 15, 2024
0f787b8
Add comments
narin Apr 15, 2024
1f33742
Remove unneeded flex
narin Apr 15, 2024
19bfa38
Rename initialExpandedState to initializeExpanded and made boolean fo…
narin Apr 17, 2024
e42ceee
Add TS prop rules
narin Apr 17, 2024
49cd8bc
Remove description logic from _header()
narin Apr 17, 2024
a2b5e9f
Update expandable comment
narin Apr 17, 2024
4e4d294
Update initializeExpanded comment
narin Apr 17, 2024
a28c2e1
Merge branch 'main' into feature/246-narin-alert-collapsible
narin Apr 17, 2024
99f1a6f
Add font scaling for icons
narin Apr 18, 2024
a604070
Add expandable and header comments for default prop type
narin Apr 18, 2024
e2ebc33
Set preventScaling on variant icon. Set maxWidth on chevron icon to 2…
narin Apr 18, 2024
fb28c2c
Merge branch 'main' into feature/246-narin-alert-collapsible
narin Apr 18, 2024
217fcec
Expand touchable area with hitSlop
narin Apr 19, 2024
6d1726d
Clean up hitSlop and spacer
narin Apr 19, 2024
58f6da8
Remove unneeded sizing token
narin Apr 19, 2024
cec6f69
Cleanup
narin Apr 19, 2024
5670a3e
Fix comment typo
narin Apr 19, 2024
cdf451f
Add preventScaling to alert icon, clean up header logic, change space…
narin Apr 23, 2024
0392eaa
Merge branch 'main' into feature/246-narin-alert-collapsible
narin Apr 23, 2024
27b88ca
Replace accessibilityRole with role
narin Apr 23, 2024
bd0fef9
Replace accessibilityRole with role
narin Apr 23, 2024
631ae25
Replace accessibilityState with aria-expanded
narin Apr 23, 2024
d18b496
Fix aria-expanded value
narin Apr 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions packages/components/src/components/Alert/Alert.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,25 @@ export const Info: Story = {
},
},
}

export const Expandable: Story = {
args: {
variant: 'info',
header: 'Header',
description: 'Description',
children: children,
expandable: true,
primaryButton: {
label: 'Button Text',
onPress: () => {
null
},
},
secondaryButton: {
label: 'Button Text',
onPress: () => {
null
},
},
},
}
203 changes: 131 additions & 72 deletions packages/components/src/components/Alert/Alert.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Colors } from '@department-of-veterans-affairs/mobile-tokens'
// import { HapticFeedbackTypes } from 'react-native-haptic-feedback'
import { Text, TextStyle, View, ViewStyle } from 'react-native'
// ^ ScrollView,
import React, { FC } from 'react'
// ^ , RefObject, useEffect, useState

// import { triggerHaptic } from 'utils/haptics'
// import { useAutoScrollToElement } from 'utils/hooks'
import {
Insets,
Pressable,
Text,
TextStyle,
View,
ViewStyle,
useWindowDimensions,
} from 'react-native'
import React, { FC, useState } from 'react'

import { BaseColor, Spacer, useColorScheme } from '../../utils'
import { Button, ButtonProps, ButtonVariants } from '../Button/Button'
Expand All @@ -18,8 +20,6 @@ export const AlertContentColor = BaseColor
export type AlertProps = {
/** Alert variant */
variant: 'info' | 'success' | 'warning' | 'error'
/** Optional header text */
header?: string
/** Optional a11y override for header */
headerA11yLabel?: string
/** Optional description text */
Expand All @@ -33,13 +33,25 @@ export type AlertProps = {
primaryButton?: ButtonProps
/** Optional secondary action button */
secondaryButton?: ButtonProps
/** Optional boolean for determining when to focus on error alert boxes (e.g. onSaveClicked). */
// focusOnError?: boolean
/** Optional ref for the parent scroll view. Used for scrolling to error alert boxes. */
// scrollViewRef?: RefObject<ScrollView>
/** optional testID */
/** Optional testID */
testId?: string
}
} & (
| {
/** True to make the Alert expandable */
expandable: true
/** Header text. Required when Alert is expandable */
header: string
/** True if Alert should start expanded. Defaults to false */
initializeExpanded?: boolean
}
| {
/** True to make the Alert expandable */
expandable?: false
narin marked this conversation as resolved.
Show resolved Hide resolved
/** Header text. Optional when Alert is not expandable */
header?: string
initializeExpanded?: never
}
)

/**
* Work in progress:
Expand All @@ -52,42 +64,27 @@ export const Alert: FC<AlertProps> = ({
description,
descriptionA11yLabel,
children,
expandable,
initializeExpanded,
primaryButton,
secondaryButton,
// focusOnError = true,
// scrollViewRef,
testId,
}) => {
const colorScheme = useColorScheme()
const fontScale = useWindowDimensions().fontScale
const isDarkMode = colorScheme === 'dark'
// const [scrollRef, viewRef, scrollToAlert] = useAutoScrollToElement()
// const [shouldFocus, setShouldFocus] = useState(true)

// useEffect(() => {
// if (
// variant === 'error' &&
// scrollViewRef?.current &&
// (header || description)
// ) {
// scrollRef.current = scrollViewRef.current
// scrollToAlert(-boxPadding)
// }
// setShouldFocus(focusOnError)
// }, [
// variant,
// header,
// description,
// focusOnError,
// scrollRef,
// scrollToAlert,
// scrollViewRef,
// ])
const [expanded, setExpanded] = useState(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't feel strongly since maybe it's clearer this way, but wouldn't this work just as well only using let expanded = ... and flipping the variable instead of useState?

expandable ? initializeExpanded : true,
)

// TODO: Replace with sizing/dimension tokens
const Sizing = {
_8: 8,
_10: 10,
_12: 12,
_16: 16,
_20: 20,
_24: 24,
_30: 30,
}
const contentColor = AlertContentColor()
Expand Down Expand Up @@ -151,14 +148,14 @@ export const Alert: FC<AlertProps> = ({
borderLeftWidth: Sizing._8,
padding: Sizing._20,
paddingLeft: Sizing._12, // Adds with borderLeftWidth for 20
width: '100%' // Ensure Alert fills horizontal space, regardless of flexing content
width: '100%', // Ensure Alert fills horizontal space, regardless of flexing content
}

const iconViewStyle: ViewStyle = {
flexDirection: 'row',
// Below keeps icon aligned with first row of text, centered, and scalable
alignSelf: 'flex-start',
minHeight: Sizing._30,
minHeight: Sizing._30 * fontScale,
alignItems: 'center',
justifyContent: 'center',
}
Expand All @@ -170,13 +167,33 @@ export const Alert: FC<AlertProps> = ({
</View>
narin marked this conversation as resolved.
Show resolved Hide resolved
)

// const vibrate = (): void => {
// if (variant === 'error') {
// triggerHaptic(HapticFeedbackTypes.notificationError)
// } else if (variant === 'warning') {
// triggerHaptic(HapticFeedbackTypes.notificationWarning)
// }
// }
const expandIconProps: IconProps = {
fill: contentColor,
width: Sizing._16,
height: Sizing._16,
maxWidth: Sizing._24,
name: expanded ? 'ChevronUp' : 'ChevronDown',
}

const expandableIcon = (
<View style={iconViewStyle}>
<Spacer horizontal />
<Icon {...expandIconProps} />
</View>
)

/**
* When an alert is expandable, the content should have additional padding on
* the right to appear within the expandable icon. Since the expandable icon
* has a maxWidth, this hidden icon matches the spacing of the icon insteading
* instead of adding a <Spacer /> with a calculated value.
*/
const spacerIcon = (
<View style={iconViewStyle} aria-hidden>
<Spacer horizontal />
<Icon {...expandIconProps} fill={backgroundColor} />
narin marked this conversation as resolved.
Show resolved Hide resolved
</View>
)

// TODO: Replace with typography tokens
const headerFont: TextStyle = {
Expand All @@ -194,6 +211,44 @@ export const Alert: FC<AlertProps> = ({
lineHeight: 30,
}

const _header = () => {
const headerText = header ? <Text style={headerFont}>{header}</Text> : null
narin marked this conversation as resolved.
Show resolved Hide resolved
const a11yLabel = header ? headerA11yLabel || header : ''
const hitSlop: Insets = {
// left border + left padding + spacer + icon width
left: Sizing._8 + Sizing._12 + Sizing._10 + Sizing._24,
top: Sizing._20,
// bottom spacing changes depending on expanded state
bottom: expanded ? Sizing._10 : Sizing._20,
right: Sizing._20,
}

/**
* Wrap header text and expand icon in Pressable if the Alert is expandable
* Otherwise wrap in View with accessibility props
*/
if (expandable) {
return (
<Pressable
onPress={() => setExpanded(!expanded)}
accessibilityRole="tab"
narin marked this conversation as resolved.
Show resolved Hide resolved
narin marked this conversation as resolved.
Show resolved Hide resolved
accessibilityState={{ expanded }}
narin marked this conversation as resolved.
Show resolved Hide resolved
aria-label={a11yLabel}
hitSlop={hitSlop}
style={{ flexDirection: 'row' }}>
<View style={{ flex: 1 }}>{headerText}</View>
{expandableIcon}
</Pressable>
)
}

return (
<View accessible={true} aria-label={a11yLabel} role="heading">
{headerText}
</View>
)
}

const _primaryButton = () => {
if (!primaryButton) return null

Expand Down Expand Up @@ -225,35 +280,39 @@ export const Alert: FC<AlertProps> = ({
}

return (
<View style={contentBox} testID={testId}>
<View
style={contentBox}
testID={testId}
accessibilityRole={expandable ? 'tablist' : 'none'}>
narin marked this conversation as resolved.
Show resolved Hide resolved
<View style={{ flexDirection: 'row' }}>
{iconDisplay}
narin marked this conversation as resolved.
Show resolved Hide resolved
<View style={{ flex: 1 }}>
{header ? (
<View
// ref={viewRef}
accessible={true}
aria-label={headerA11yLabel || header}
role="heading">
<Text style={headerFont}>{header}</Text>
</View>
) : null}
{header && (description || children) ? <Spacer /> : null}
{description ? (
<View
// ref={!header ? viewRef : undefined}
accessible={true}
aria-label={descriptionA11yLabel || description}>
<Text style={descriptionFont}>{description}</Text>
{_header()}
{expanded && (
<View style={{ flexDirection: 'row' }}>
<View style={{ flex: 1 }}>
{header && (description || children) ? <Spacer /> : null}
{description ? (
<View
accessible={true}
aria-label={descriptionA11yLabel || description}>
<Text style={descriptionFont}>{description}</Text>
</View>
) : null}
{description && children ? <Spacer /> : null}
{children}
</View>
{expandable && spacerIcon}
</View>
) : null}
{description && children ? <Spacer /> : null}
{children}
{/* {shouldFocus && vibrate()} */}
)}
</View>
</View>
{_primaryButton()}
{_secondaryButton()}
{expanded && (
<>
{_primaryButton()}
{_secondaryButton()}
</>
)}
</View>
)
}
1 change: 1 addition & 0 deletions packages/components/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ if (expoApp && App.initiateExpo) {
}

// Export components here so they are exported through npm
export { Alert } from './components/Alert/Alert'
export { Button, ButtonVariants } from './components/Button/Button'
export { Icon } from './components/Icon/Icon'
export { Link } from './components/Link/Link'
Expand Down
Loading