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

Create a theming React context provider integration for applications #108972

Closed
chandlerprall opened this issue Aug 17, 2021 · 10 comments
Closed
Assignees
Labels
impact:high Addressing this issue will have a high level of impact on the quality/strength of our product. loe:large Large Level of Effort Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc

Comments

@chandlerprall
Copy link
Contributor

EUI is close to merging our theming layer (elastic/eui#4511) which relies on React's context to give applications means of overriding theme values for the application and at specific nodes/branches in its component tree. When this has landed in EUI (with emotion functionality coming later), we'll be ready for the application mount points in Kibana to be updated to include this new context provider.

@chandlerprall chandlerprall added the Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc label Aug 17, 2021
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-core (Team:Core)

@pgayvallet
Copy link
Contributor

pgayvallet commented Aug 18, 2021

To summarize our previous sync discussions:

  • We ideally wanted to inject the context provider at the root level of Kibana's react tree to be consumable by all applications, however due to how the agnostic applications mounting works, this will have to be done at the application level, as core's react app and the underlying application react app are distinct trees (using distinct reactDOM.render calls). (FWIW this is a problem similar to what we encountered when trying to implement the RedirectAppLinks feature globally at the root level. Distinct react trees just cannot interact in a nested way or share providers)

  • The most realistic compromise we found was to expose utility functions/component from the kibana_react plugin for plugin owners to use it when mounting their app or 'subparts' of their tree using toMountPoint (such as modals)

  • The wrapping utilities will need to have access to some data provided by core (at the moment that's only the darkMode uiSetting but things like the color palette could be added mid/long term). As there will potentially be more data required to be propagated at some point, the consensus was to expose a new theme service from core that would provide a ready-to-use object containing all the info required by the provider. This seems the best approach in term of design and future-proofing. To handle the scenario where the user would potentially be able to change the darkMode dynamically from the UI (or later real-time color palette change), this new theme core API will be observable-based.

  • Application / plugin owners will have to manually adapt their code to use these new utilities that will be introduced.

  • We also need to handle lower-level react trees such as the modals receiving a MountPoint and mostly using the toMountPoint utility already exposed by kibana_react

An example of using the theme context provider when mounting an app could look like:

public setup(core: CoreSetup, deps: {}) {
    core.application.register({
      id: 'foo',
      title: 'Foo',
      mount: async ({ element }: AppMountParameters) => {
        const [{ theme }] = await core.getStartServices()
        ReactDOM.render(wrapWithTheme(<MyApp {...myParameters} />, theme.getTheme$()), element);
        return () => unmountComponentAtNode(element);
      },
    });
}

Ideally, we could even expose this theme information from the AppMountParameters themselves, to avoid using getStartServices for it. I did not investigate the feasibility though (but the uiSettings service is setup/started before the application one, so I think it should be fine).

If we can't, we may want to investigate the option to expose this theme$ observable from the theme service's setup contract instead (even if this kinda go against the setup/start separation).

Note: If we choose to expose this API from the AppMountParameters, we would still need to expose a theme service to retrieve the same data for 'nested' mountpoints such as management sections and similar extension patterns anyway.

In that case, the API would look like:

public setup(core: CoreSetup, deps: {}) {
    core.application.register({
      id: 'foo',
      title: 'Foo',
      mount: async ({ element, theme$ }: AppMountParameters) => {
        ReactDOM.render(wrapWithTheme(<MyApp {...myParameters} />, theme$), element);
        return () => unmountComponentAtNode(element);
      },
    });
}

The context provider component could also directly be exposed from kibana_react (not sure if having multiple ways to do the same thing is really a good practice though?)

public setup(core: CoreSetup, deps: {}) {
    core.application.register({
      id: 'foo',
      title: 'Foo',
      mount: async ({ element, theme$ }: AppMountParameters) => {
        ReactDOM.render(<KibanaThemeContextProvider theme={theme$}><MyApp {...myParameters} /></>, element);
        return () => unmountComponentAtNode(element);
      },
    });
}

Also, some parts of the applications rely on toMountPoint to be rendered, such as modal opened via overlays.openModal. Here too, we won't be able to automatically inject the context provider, as toMountPoint is agnostic.

The best we can do would be to add a new options parameter receiving the theme:

const toMountPoint = (node: React.ReactNode): MountPoint

would become

const toMountPoint = (node: React.ReactNode, options?: { theme$: Obvervable<CoreTheme>} ): MountPoint

when the option is provided, we would wrap the provided react node with the new <KibanaThemeContextProvider> component.

EDIT: After thinking about it, I'm not sure the toMountPoint modification should receive the core theme, as we probably want to preserve any potential theme changes performed by the owning application. Maybe it should receive the EUI theme instead, and we would wrap the modal directly with an EUI theme context provider instead of our own?

cc @chandlerprall @thompsongl @cchaos Does that seems alright? any comment or addition?

@chandlerprall
Copy link
Contributor Author

Maybe it should receive the EUI theme instead, and we would wrap the modal directly with an EUI theme context provider instead of our own?

We'll have the ability to ask a context for a complete representation/snapshot of its themed values, maybe that can be forwarded to the new mount point as the starting point for that subsequent mounted context?

@pgayvallet
Copy link
Contributor

pgayvallet commented Aug 19, 2021

We'll have the ability to ask a context for a complete representation/snapshot of its themed values, maybe that can be forwarded to the new mount point as the starting point for that subsequent mounted context?

Yea, that's what I was thinking about. But in that case, we'll be working with the 'raw' EUI context, not the CoreTheme object that the root KibanaThemeContextProvider should receive.

const toMountPoint = (node: React.ReactNode, options?: { theme$: Obvervable<CoreTheme>} ): MountPoint

should probably instead be

const toMountPoint = (node: React.ReactNode, options?: { theme: EuiTheme} ): MountPoint

or it could even accept both. The implementation is fairly trivial either way, I guess we'll see with the first usages.

@pgayvallet
Copy link
Contributor

#110998 will be the EUI bump that will expose the new EuiThemeProvider

@pgayvallet
Copy link
Contributor

#110998 has been merged, we should be ready to start this one.

@thompsongl
Copy link
Contributor

@pgayvallet Let us know if the EUI team can help in any way here!

@pgayvallet
Copy link
Contributor

@thompsongl thanks, will do. FYI, this is planned for our next sprint. If we have extra capacity, we'll try to fit it into the current.

@lukeelmers lukeelmers added impact:high Addressing this issue will have a high level of impact on the quality/strength of our product. loe:medium Medium Level of Effort labels Oct 29, 2021
@exalate-issue-sync exalate-issue-sync bot added loe:large Large Level of Effort and removed loe:medium Medium Level of Effort labels Nov 2, 2021
@pgayvallet
Copy link
Contributor

I created #118866 to track the integration of the KibanaThemeProvider

@pgayvallet
Copy link
Contributor

Closed because core's work on the issue is done. Meta issue to track each team's usage of the new provider is here: #118866

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
impact:high Addressing this issue will have a high level of impact on the quality/strength of our product. loe:large Large Level of Effort Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc
Projects
None yet
Development

No branches or pull requests

5 participants