Skip to content

Commit

Permalink
Start Icon component and sb story
Browse files Browse the repository at this point in the history
  • Loading branch information
narin committed Jan 5, 2024
1 parent 89250ad commit 1f1ae6b
Show file tree
Hide file tree
Showing 3 changed files with 363 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ try {
const getStories = () => {
return {
"./src/components/Button/Button.stories.tsx": require("../../src/components/Button/Button.stories.tsx"),
"./src/components/Icon/Icon.stories.tsx": require("../../src/components/Icon/Icon.stories.tsx"),
"./src/components/SegmentedControl/SegmentedControl.stories.tsx": require("../../src/components/SegmentedControl/SegmentedControl.stories.tsx"),
};
};
Expand Down
45 changes: 45 additions & 0 deletions packages/components/src/components/Icon/Icon.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Icon, IconProps, IconVariants } from './Icon'
import { Meta, StoryObj } from '@storybook/react-native'
import { View } from 'react-native'
import { generateDocs } from '../../utils/storybook'
import React from 'react'

const meta: Meta<IconProps> = {
title: 'Icon',
component: Icon,
parameters: {
docs: generateDocs({
name: 'Icon',
docUrl:
'https://department-of-veterans-affairs.github.io/va-mobile-app/design/Components/Icons%20and%20links/Icon',
}),
},
decorators: [
(Story) => (
<View
style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}}>
<Story />
</View>
),
],
}

export default meta

type Story = StoryObj<IconProps>

export const _Primary: Story = {
storyName: 'Primary',
args: {
width: 100,
height: 100,
fill: '#1e90ff',
name: 'HomeSelected',
preventScaling: true,
paddingTop: 50,
},
}
317 changes: 317 additions & 0 deletions packages/components/src/components/Icon/Icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
import { SvgProps } from 'react-native-svg'
import React, { FC } from 'react'

// import { AccessibilityState } from 'store/slices'
// import { Box, BoxProps } from 'components'
// import { RootState } from 'store'
// import { IconColors, VATextColors } from 'styles/theme'
// import { updateFontScale } from 'utils/accessibility'
// import { useAppDispatch, useFontScale, useTheme } from 'utils/hooks'
// import { useSelector } from 'react-redux'

// See Icon function documentation below for guidance on adding new SVGs

// Navigation
import { View } from 'react-native'
import BenefitsSelected from '@department-of-veterans-affairs/mobile-assets/svgs/navIcon/BenefitsSelected.svg'
import BenefitsUnselected from '@department-of-veterans-affairs/mobile-assets/svgs/navIcon/BenefitsUnselected.svg'
import HealthSelected from '@department-of-veterans-affairs/mobile-assets/svgs/navIcon/HealthSelected.svg'
import HealthUnselected from '@department-of-veterans-affairs/mobile-assets/svgs/navIcon/HealthUnselected.svg'
import HomeSelected from '@department-of-veterans-affairs/mobile-assets/svgs/navIcon/HomeSelected.svg'
import HomeUnselected from '@department-of-veterans-affairs/mobile-assets/svgs/navIcon/HomeUnselected.svg'
import PaymentsSelected from '@department-of-veterans-affairs/mobile-assets/svgs/navIcon/PaymentsSelected.svg'
import PaymentsUnselected from '@department-of-veterans-affairs/mobile-assets/svgs/navIcon/PaymentsUnselected.svg'
import ProfileSelected from '@department-of-veterans-affairs/mobile-assets/svgs/navIcon/ProfileSelected.svg'

// Chevrons
import ChevronDown from '@department-of-veterans-affairs/mobile-assets/svgs/ChevronDown.svg'
import ChevronLeft from '@department-of-veterans-affairs/mobile-assets/svgs/ChevronLeft.svg'
import ChevronRight from '@department-of-veterans-affairs/mobile-assets/svgs/ChevronRight.svg'
import ChevronUp from '@department-of-veterans-affairs/mobile-assets/svgs/ChevronUp.svg'

// Branch icons
import AirForce from '@department-of-veterans-affairs/mobile-assets/svgs/dodBranch/AirForce.svg'
import Army from '@department-of-veterans-affairs/mobile-assets/svgs/dodBranch/Army.svg'
import CoastGuard from '@department-of-veterans-affairs/mobile-assets/svgs/dodBranch/CoastGuard.svg'
import MarineCorps from '@department-of-veterans-affairs/mobile-assets/svgs/dodBranch/MarineCorps.svg'
import Navy from '@department-of-veterans-affairs/mobile-assets/svgs/dodBranch/Navy.svg'

// Links
import Calendar from '@department-of-veterans-affairs/mobile-assets/svgs/links/Calendar.svg'
import Chat from '@department-of-veterans-affairs/mobile-assets/svgs/links/Chat.svg'
import CircleExternalLink from '@department-of-veterans-affairs/mobile-assets/svgs/links/CircleExternalLink.svg'
import CirclePhone from '@department-of-veterans-affairs/mobile-assets/svgs/links/CirclePhone.svg'
import Directions from '@department-of-veterans-affairs/mobile-assets/svgs/links/Directions.svg'
import PhoneTTY from '@department-of-veterans-affairs/mobile-assets/svgs/links/PhoneTTY.svg'
import RightArrowInCircle from '@department-of-veterans-affairs/mobile-assets/svgs/links/right-arrow-blue-circle.svg'
import Text from '@department-of-veterans-affairs/mobile-assets/svgs/links/Text.svg'

// VASelector
import CheckBoxEmpty from '@department-of-veterans-affairs/mobile-assets/svgs/checkbox/CheckBoxEmpty.svg'
import CheckBoxError from '@department-of-veterans-affairs/mobile-assets/svgs/checkbox/CheckBoxError.svg'
import CheckBoxFilled from '@department-of-veterans-affairs/mobile-assets/svgs/checkbox/CheckBoxFilled.svg'
import CheckBoxIntermediate from '@department-of-veterans-affairs/mobile-assets/svgs/checkbox/CheckBoxIntermediate.svg'
import RadioEmpty from '@department-of-veterans-affairs/mobile-assets/svgs/radio/RadioEmpty.svg'
import RadioFilled from '@department-of-veterans-affairs/mobile-assets/svgs/radio/RadioFilled.svg'

// Misc
import Add from '@department-of-veterans-affairs/mobile-assets/svgs/Add.svg'
import Building from '@department-of-veterans-affairs/mobile-assets/svgs/Building.svg'
import Bullet from '@department-of-veterans-affairs/mobile-assets/svgs/Bullet.svg'
import CheckMark from '@department-of-veterans-affairs/mobile-assets/svgs/CheckMark.svg'
import CircleCheckMark from '@department-of-veterans-affairs/mobile-assets/svgs/CircleCheckMark.svg'
import Compose from '@department-of-veterans-affairs/mobile-assets/svgs/Compose.svg'
import Ellipsis from '@department-of-veterans-affairs/mobile-assets/svgs/Ellipsis.svg'
import ExclamationTriangle from '@department-of-veterans-affairs/mobile-assets/svgs/ExclamationTriangle.svg'
import ExternalLink from '@department-of-veterans-affairs/mobile-assets/svgs/ExternalLink.svg'
import Folder from '@department-of-veterans-affairs/mobile-assets/svgs/Folder.svg'
import Inbox from '@department-of-veterans-affairs/mobile-assets/svgs/Inbox.svg'
import Info from '@department-of-veterans-affairs/mobile-assets/svgs/Info.svg'
import Lock from '@department-of-veterans-affairs/mobile-assets/svgs/Lock.svg'
import Logo from '@department-of-veterans-affairs/mobile-assets/svgs/vaParentLogo/Logo.svg'
import Minus from '@department-of-veterans-affairs/mobile-assets/svgs/Minus.svg'
import PaperClip from '@department-of-veterans-affairs/mobile-assets/svgs/PaperClip.svg'
import Phone from '@department-of-veterans-affairs/mobile-assets/svgs/Phone.svg'
import QuestionMark from '@department-of-veterans-affairs/mobile-assets/svgs/QuestionMark.svg'
import Redo from '@department-of-veterans-affairs/mobile-assets/svgs/Redo.svg'
import Remove from '@department-of-veterans-affairs/mobile-assets/svgs/Remove.svg'
import Reply from '@department-of-veterans-affairs/mobile-assets/svgs/Reply.svg'
import Sort from '@department-of-veterans-affairs/mobile-assets/svgs/Sort.svg'
import Trash from '@department-of-veterans-affairs/mobile-assets/svgs/Trash.svg'
import Truck from '@department-of-veterans-affairs/mobile-assets/svgs/Truck.svg'
import Unread from '@department-of-veterans-affairs/mobile-assets/svgs/Unread.svg'
import UploadPhoto from '@department-of-veterans-affairs/mobile-assets/svgs/UploadPhoto.svg'
import VideoCamera from '@department-of-veterans-affairs/mobile-assets/svgs/VideoCamera.svg'

const IconMap = {
Add,
AirForce,
Army,
BenefitsSelected,
BenefitsUnselected,
Building,
Bullet,
Calendar,
Chat,
CheckBoxEmpty,
CheckBoxError,
CheckBoxFilled,
CheckBoxIntermediate,
CheckMark,
ChevronDown,
ChevronLeft,
ChevronRight,
ChevronUp,
CircleCheckMark,
CircleExternalLink,
CirclePhone,
CoastGuard,
Compose,
Directions,
Ellipsis,
ExclamationTriangle,
ExternalLink,
Folder,
HealthSelected,
HealthUnselected,
HomeSelected,
HomeUnselected,
Inbox,
Info,
Lock,
Logo,
MarineCorps,
Minus,
Navy,
PaperClip,
PaymentsSelected,
PaymentsUnselected,
Phone,
PhoneTTY,
ProfileSelected,
QuestionMark,
RadioEmpty, // Also used for RadioDisabled content--same icon, different colors
RadioFilled,
Redo,
Remove,
Reply,
RightArrowInCircle, // TODO: Ticket 3402 (or separate implementation ticket) to remove this icon
Sort,
Text,
Trash,
Truck,
Unread,
UploadPhoto,
VideoCamera,
}

export type IconVariants = keyof typeof IconMap

/**
* Props that need to be passed in to {@link Icon}
*/
export type IconProps = {
/** enum name of the icon to use {@link IconMap} **/
name: keyof typeof IconMap

/** Fill color for the icon */
fill?: string // keyof IconColors | keyof VATextColors | string

/** Secondary fill color for duotone icons--fills icons inside main fill, defaults white */
fill2?: string

/** Stroke color of the icon */
stroke?: string

/** optional number use to set the width; otherwise defaults to svg's width */
width?: number

/** optional number use to set the height; otherwise defaults to svg's height */
height?: number

/** optional maximum width when scaled (requires width and height props) */
maxWidth?: number

/** if true, prevents icon from being scaled (requires width and height props) */
preventScaling?: boolean

/** Optional TestID */
testID?: string

/** optional top margin */
marginTop?: number

/** optional bottom margin */
marginBottom?: number

/** optional right margin */
marginRight?: number

/** optional left margin */
marginLeft?: number

/** optional top padding */
paddingTop?: number

/** optional bottom padding */
paddingBottom?: number

/** optional right padding */
paddingRight?: number

/** optional left padding */
paddingLeft?: number
}

/**
* A common component to display assets (SVGs).
*
* For all icons in the SVG definitions, on the primary/only path:
* - Set `fill` to `#000` to inherit Icon's fill color prop
* If the SVG icon is duotone, additionally:
* - Set `color` to `#fff` on the top level svg (not path)
* - Set `fill` to `currentColor` on the secondary path to inherit Icon's fill2 color prop
* If the SVG icon uses stroke, additionally:
* - Set `stroke` to `#00F` to inherit Icon's stroke color prop
*
* Example icons of each classification:
* - One layer: HomeSelected.svg
* - Duotone: CircleCheckMark.svg
* - Stroke: RadioEmpty.svg
*
* @returns Icon component
*/
export const Icon: FC<IconProps> = ({
name,
width,
height,
fill,
fill2,
stroke,
maxWidth,
preventScaling,
testID,
marginTop = 0,
marginBottom = 0,
marginLeft = 0,
marginRight = 0,
paddingTop = 0,
paddingBottom = 0,
paddingLeft = 0,
paddingRight = 0,
}) => {
// const fs: (val: number) => number = useFontScale()
const fs: (val: number) => number = () => 1 // useFontScale()
// const dispatch = useAppDispatch()
// const { fontScale } = useSelector<RootState, AccessibilityState>(
// (state) => state.accessibility,
// )

let iconProps = {
name,
width,
height,
preventScaling,
}

if (fill) {
iconProps = Object.assign({}, iconProps, { fill: fill })
}

if (fill2) {
iconProps = Object.assign({}, iconProps, { color: fill2 })
}

if (stroke) {
iconProps = Object.assign({}, iconProps, { stroke: stroke })
}

const viewProps = {
marginTop,
marginBottom,
marginLeft,
marginRight,
paddingTop,
paddingBottom,
paddingLeft,
paddingRight,
}

// useEffect(() => {
// // Listener for the current app state, updates the font scale when app state is active and the font scale has changed
// const sub = AppState.addEventListener(
// 'change',
// (newState: AppStateStatus): void =>
// updateFontScale(newState, fontScale, dispatch),
// )
// return (): void => sub?.remove()
// }, [dispatch, fontScale])

const VAIcon: FC<SvgProps> | undefined = IconMap[name]

if (!VAIcon) {
return <></>
}

if (width && height) {
if (preventScaling) {
iconProps = { ...iconProps, width, height }
} else if (maxWidth && fs(width) > maxWidth) {
iconProps = {
...iconProps,
width: maxWidth,
height: (maxWidth / width) * height,
}
} else {
iconProps = { ...iconProps, width: fs(width), height: fs(height) }
}
}

console.log('iconProps', iconProps)

return (
<View testID={testID} style={viewProps}>
<VAIcon {...iconProps} />
</View>
)
}

0 comments on commit 1f1ae6b

Please sign in to comment.