department-of-veterans-affairs / va-mobile-library

https://department-of-veterans-affairs.github.io/va-mobile-library/
ISC License
0 stars 0 forks source link

Spike - Explore ability for consumers to set global values for components #256

Closed TimRoe closed 3 weeks ago

TimRoe commented 3 months ago

Description

In response to a Slack thread asking about the design system establishing a maxWidth for icons, the team agreed that it does not make sense from a design system perspective to do so.

However, this would cause complications for consumers of our components library. It would require consuming apps to create a wrapper of our Icon component to consistently pass the maxWidth property, create a common baseline of props to send to components (e.g. Link) for Icon customization, and/or add a patch to our library (that would need to be updated each time they updated their package). Additionally, only the last option would work to apply an icon maxWidth for components like Alerts that do not expose icon customization because the component itself does not allow it.

After discussing with Narin, this spike ticket has been created to research setting up global values that can be easily set by consuming apps to override such behavior without needing complicated workarounds. More specifically this ticket is to explore a good mechanism for doing so including options such as: setting environmental variables (e.g. process.env.mobile-component-library.Icon.maxWidth which is by default not set, but apps could set to globally override unless they override that on a case-by-case basis) or having a global state wrapper for our components like calling the following during initialization of the app:

VADSMobile.setDefault({
  Icon: { maxWidth: 200 }
})

While the focus of this spike is the mechanism, two specific cases to explore with this spike:

Acceptance Criteria

- [ ] Research and determine a preferred mechanism for allowing global overrides - [ ] Either implement or create separate ticket to (list that can be added to if further known cases are determined prior to spike being picked up): - Establish a global maxWidth for Icons as one known use case - Establish light/dark mode override ability separate from relying on RN's global setting--preferably with a mechanism to override on a per-screen basis if needed
narin commented 3 weeks ago

Findings

After some research and experimentation, I've found that React's built-in Context hook would be a good approach for allowing us to maintain a set of global settings that can be set from a consuming app and passed down to our components. Context is good for handling global state that does not change often, avoids prop drilling, and doesn't require actions like reducers patterns such as redux.

High-level approach

In va-mobile-library, we would:

In the consuming app:

I've created a POC branch that has already tested that all of this is working. For any follow-up tickets, we would just need to clean it up using proper TS typings and decide whether we want to allow for updates to the settings from the consuming app beyond the initial settings.

Unknowns

My proof of concept has not tested any updating of the settings from the external app. This should be possible by calling the update functions from the consuming app but I have not tested this. We should decide whether we want to make this possible as these global settings should most likely be settings that are not updated frequently.

Follow-up tickets

A majority of the work has can be adapted from the POC branch

Code snippets

va-mobile-library

SettingsProvider.tsx (new file)

import React, { createContext, useState } from 'react'

const SettingsContext = createContext({
  allowDarkMode: true,
  Icon: {
    maxWidth: undefined,
  },
})

function SettingsProvider({ children, initialSettings }) {
  const [settings, updateSettings] = useState(initialSettings)

  const updateSetting = (property, value) =>
    updateSettings({
      ...settings,
      [property]: value,
    })

  const updateComponentSetting = (componentName: string, property, value) => {
    updateSettings({
      ...settings,
      [componentName]: {
        ...settings[componentName],
        [property]: value,
      },
    })
  }

  return (
    <SettingsContext.Provider
      value={{ ...settings, updateSetting, updateComponentSetting }}>
      {children}
    </SettingsContext.Provider>
  )
}

export { SettingsContext, SettingsProvider }

Icon.tsx (example of accessing global variable)

...
  const { allowDarkMode, Icon: IconSettings } = useContext(SettingsContext)
  const globalMaxWidth = IconSettings.maxWidth || 24
  const isDarkMode = allowDarkMode ? colorScheme === 'dark' : false
...

index.jsx (exporting our SettingsProvider)

...
export { SettingsProvider as ComponentSettingsProvider } from './SettingsProvider'
...

va-mobile-app

App.tsx

...
import { ComponentsSettingsProvider } from '@department-of-veterans-affairs/mobile-component-library'
...
  const componentSettings = {
    allowDarkMode: false,
    Icon: {
      maxWidth: 100,
    },
  }
...
  return (
    <>
      <QueryClientProvider client={queryClient}>
        <ActionSheetProvider>
          <ThemeProvider theme={currentTheme}>
            <Provider store={store}>
              <ComponentsSettingsProvider initialSettings={componentSettings}>
                <I18nextProvider i18n={i18n}>
                  <NavigationContainer
                    ref={navigationRef}
                    linking={linking}
                    onReady={navOnReady}
                    onStateChange={onNavStateChange}>
                    <NotificationManager>
                      <SafeAreaProvider>
                        <StatusBar
                          barStyle={theme.mode === 'dark' ? 'light-content' : 'dark-content'}
                          backgroundColor={currentTheme.colors.background.main}
                        />
                        <AuthGuard />
                      </SafeAreaProvider>
                    </NotificationManager>
                  </NavigationContainer>
                </I18nextProvider>
              </ComponentsSettingsProvider>
            </Provider>
          </ThemeProvider>
        </ActionSheetProvider>
      </QueryClientProvider>
    </>
  )
...
narin commented 3 weeks ago

Created follow-up tickets linking back to the spike. Closing out.