diff --git a/packages/components/package.json b/packages/components/package.json index ebaa6d90..638983f3 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@department-of-veterans-affairs/mobile-component-library", - "version": "0.27.0", + "version": "0.27.1-alpha.2", "description": "VA Design System Mobile Component Library", "main": "src/index.tsx", "scripts": { @@ -48,7 +48,7 @@ }, "peerDependencies": { "@department-of-veterans-affairs/mobile-assets": "^0.14.0", - "@department-of-veterans-affairs/mobile-tokens": "^0.18.0", + "@department-of-veterans-affairs/mobile-tokens": "^0.20.0", "react": "^18.2.0", "react-native": ">=0.71.7", "react-native-gesture-handler": "^2.12.0", @@ -60,7 +60,7 @@ "@babel/preset-env": "^7.24.8", "@babel/preset-typescript": "^7.24.7", "@department-of-veterans-affairs/mobile-assets": "0.14.0", - "@department-of-veterans-affairs/mobile-tokens": "0.18.0", + "@department-of-veterans-affairs/mobile-tokens": "0.20.0", "@expo/metro-runtime": "~3.2.1", "@expo/webpack-config": "~19.0.1", "@react-native-async-storage/async-storage": "1.23.1", diff --git a/packages/components/src/components/Spacer/Spacer.tsx b/packages/components/src/components/Spacer/Spacer.tsx index bfe007d1..54b0ebf5 100644 --- a/packages/components/src/components/Spacer/Spacer.tsx +++ b/packages/components/src/components/Spacer/Spacer.tsx @@ -1,7 +1,7 @@ import { View } from 'react-native' import React, { FC } from 'react' -import { spacing } from '@department-of-veterans-affairs/mobile-tokens' +import { getSpacingToken } from '../../utils' /** * Represents the size of a spacer component. Corresponds to spacing tokens. @@ -18,7 +18,7 @@ import { spacing } from '@department-of-veterans-affairs/mobile-tokens' * - '5xl': 40 * - '6xl': 44 */ -type SpacerSize = +export type SpacerSize = | 'none' | '2xs' | 'xs' @@ -53,8 +53,7 @@ export const Spacer: FC = ({ size = 'sm', horizontal = false, }) => { - const key = `vadsSpace${size[0].toUpperCase()}${size.slice(1)}` - const spacerSize: number = spacing[key as keyof typeof spacing] + const spacerSize = getSpacingToken(size) return ( = { + title: 'Text', + component: Text, + decorators: [ + (Story) => ( + + + + ), + ], + parameters: { + docs: generateDocs({ + name: 'Text', + // docUrl: + // 'https://department-of-veterans-affairs.github.io/va-mobile-app/design/Components/Alerts%20and%20Progress/Text', + }), + }, +} + +export default meta + +type Story = StoryObj + +export const Body: Story = { + render: (props: TextProps) => ( + <> + + + + ), + args: { + children: + 'Lorem ipsum odor amet, consectetuer adipiscing elit. Ex ultricies auctor per eros et nec mauris. Ut nibh risus ligula vivamus est nascetur class auctor. Faucibus facilisis integer hac ullamcorper vulputate.', + variant: 'body', + size: 'md', + }, +} + +const children = 'Lorem ipsum dolor sit amet.' + +export const _Heading: Story = { + args: { + children, + variant: 'heading', + size: 'md', + }, +} + +export const __Display: Story = { + args: { + children, + variant: 'display', + }, +} diff --git a/packages/components/src/components/Text/Text.tsx b/packages/components/src/components/Text/Text.tsx new file mode 100644 index 00000000..0d33e5a9 --- /dev/null +++ b/packages/components/src/components/Text/Text.tsx @@ -0,0 +1,118 @@ +import { + Text as RNText, + TextProps as RNTextProps, + TextStyle, +} from 'react-native' +import { font } from '@department-of-veterans-affairs/mobile-tokens' +import React, { FC } from 'react' + +import { SpacerSize } from '../Spacer/Spacer' +import { getSpacingToken, useTheme } from '../../utils' + +type TextSizes = 'xs' | 'sm' | 'md' | 'lg' +type BaseTones = 'default' | 'subtle' | 'inverse' +type BodyTones = BaseTones | 'error' + +type BodyProps = { + /** Size: xs, sm, md, or lg. Defaults to 'md' for body and heading */ + size?: TextSizes + /** Text color: default, subtle, inverse, error. Defaults to vadsColorForegroundDefault. */ + tone?: BodyTones + /** Variant: body, heading, or display */ + variant?: 'body' +} + +type HeadingProps = { + /** Size: xs, sm, md, or lg. Defaults to 'md' for body and heading */ + size?: TextSizes + /** Text color: default, subtle, inverse. Defaults to vadsColorForegroundDefault. */ + tone?: BaseTones + /** Variant: body, heading, or display */ + variant?: 'heading' +} + +type DisplayProps = { + size?: never + /** Text color: default, subtle, inverse. Defaults to vadsColorForegroundDefault. */ + tone?: BaseTones + /** Variant: body, heading, or display */ + variant?: 'display' +} + +export type TextProps = { + children: React.ReactNode + /** AccessibilityLabel for the text */ + a11yLabel?: string + /** + * Optional bottom spacing if typography style default isn't desired. + * @see {@link SpacerSize} for possible values + **/ + bottomSpacing?: SpacerSize +} & (BodyProps | HeadingProps | DisplayProps) + +export const Text: FC = ({ + a11yLabel, + bottomSpacing, + children, + size = 'md', + tone = 'default', + variant = 'body', +}) => { + const theme = useTheme() + const { typography } = font + let typographyKey, color + + const prefix = 'vadsFont' + + const sizeMap = { + xs: 'Xsmall', + sm: 'Small', + md: 'Medium', + lg: 'Large', + } + + /** Build typography key based on variant and size props */ + switch (variant) { + case 'display': + typographyKey = `${prefix}Display` + break + case 'heading': + typographyKey = `${prefix}Heading${sizeMap[size]}` + break + default: + typographyKey = `${prefix}Body${sizeMap[size]}` + } + + /** Set color based on tone */ + switch (tone) { + case 'subtle': + color = theme.vadsColorForegroundSubtle + break + case 'inverse': + color = theme.vadsColorForegroundInverse + break + case 'error': + color = theme.vadsColorForegroundError + break + default: + color = theme.vadsColorForegroundDefault + } + + const style: TextStyle = { + ...typography[typographyKey as keyof typeof typography], + color, + } + + /** Set bottom margin to custom bottomSpacing if provided */ + if (bottomSpacing) { + style.marginBottom = getSpacingToken(bottomSpacing) + } + + const textProps: RNTextProps = { + accessibilityLabel: a11yLabel, + style, + role: variant === 'heading' ? 'heading' : undefined, + } + + return {children} +} diff --git a/packages/components/src/index.tsx b/packages/components/src/index.tsx index 4b1dfcd9..6bd2d9b6 100644 --- a/packages/components/src/index.tsx +++ b/packages/components/src/index.tsx @@ -20,6 +20,7 @@ export { SnackbarProviderWithSafeArea, } from './components/Snackbar/SnackbarProvider' export { Spacer } from './components/Spacer/Spacer' +export { Text } from './components/Text/Text' // Export consumer available utilities here so they are exported through npm export { useIsScreenReaderEnabled, useTheme } from './utils' diff --git a/packages/components/src/utils/index.ts b/packages/components/src/utils/index.ts index c595f094..a913efff 100644 --- a/packages/components/src/utils/index.ts +++ b/packages/components/src/utils/index.ts @@ -1,3 +1,4 @@ export * from './accessibility' export * from './OSfunctions' export * from './style' +export * from './tokens' diff --git a/packages/components/src/utils/tokens.ts b/packages/components/src/utils/tokens.ts new file mode 100644 index 00000000..6c78f653 --- /dev/null +++ b/packages/components/src/utils/tokens.ts @@ -0,0 +1,12 @@ +import { spacing } from '@department-of-veterans-affairs/mobile-tokens' + +import { SpacerSize } from '../components/Spacer/Spacer' + +/** + * Convenience function that accepts a spacing size abbreviation and returns the corresponding + * spacing token + */ +export function getSpacingToken(size: SpacerSize): number { + const key = `vadsSpace${size[0].toUpperCase()}${size.slice(1)}` + return spacing[key as keyof typeof spacing] +} diff --git a/yarn.lock b/yarn.lock index a0e3c660..a1416584 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3128,7 +3128,7 @@ __metadata: "@babel/preset-env": "npm:^7.24.8" "@babel/preset-typescript": "npm:^7.24.7" "@department-of-veterans-affairs/mobile-assets": "npm:0.14.0" - "@department-of-veterans-affairs/mobile-tokens": "npm:0.18.0" + "@department-of-veterans-affairs/mobile-tokens": "npm:0.20.0" "@expo/metro-runtime": "npm:~3.2.1" "@expo/webpack-config": "npm:~19.0.1" "@os-team/i18next-react-native-language-detector": "npm:^1.0.34" @@ -3191,7 +3191,7 @@ __metadata: typescript: "npm:~5.3.3" peerDependencies: "@department-of-veterans-affairs/mobile-assets": ^0.14.0 - "@department-of-veterans-affairs/mobile-tokens": ^0.18.0 + "@department-of-veterans-affairs/mobile-tokens": ^0.20.0 react: ^18.2.0 react-native: ">=0.71.7" react-native-gesture-handler: ^2.12.0 @@ -3199,10 +3199,10 @@ __metadata: languageName: unknown linkType: soft -"@department-of-veterans-affairs/mobile-tokens@npm:0.18.0": - version: 0.18.0 - resolution: "@department-of-veterans-affairs/mobile-tokens@npm:0.18.0" - checksum: 02a08403bce3d108a467c5636f31f012661dd89f2ffaa3cb905fea45edecd1360b7116ba51738fd67a4571398b5375584dcdff8fe6e4735eaa866ce7ed084751 +"@department-of-veterans-affairs/mobile-tokens@npm:0.20.0": + version: 0.20.0 + resolution: "@department-of-veterans-affairs/mobile-tokens@npm:0.20.0" + checksum: dab73c152a771b3b89ebcee1bdeb43781770c793c463a953eb507c1dc2001ee257bc54efd36b639e1b02ce1986831f112f5cb9e31fff40a8e512bdd1c0f67397 languageName: node linkType: hard