-
Notifications
You must be signed in to change notification settings - Fork 30
chore(components): Refactor Page to be built from composed parts #2867
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
base: CLEANUP/page-refactor-visual
Are you sure you want to change the base?
chore(components): Refactor Page to be built from composed parts #2867
Conversation
Deploying atlantis with
|
| Latest commit: |
cbd194a
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://4a42df2c.atlantis.pages.dev |
| Branch Preview URL: | https://cleanup-page-refactor-compou.atlantis.pages.dev |
| @@ -1 +1,2 @@ | |||
| export { Page, type PageProps } from "./Page"; | |||
| export { Page } from "./Page"; | |||
| export type { ButtonActionProps, PageProps } from "./types"; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this is the first time we're exporting ButtonActionProps, right?
I wonder if we should take the opportunity to rename that. It's a little unclear it's related to the Page component, so maybe there's a better name for it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason behind that thought is that it's a type exposed to the global namespace from our barrel file. Thinking about how it might confuse LLMs and people 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great feedback! I'll rename to be prefixed with Page so it's clear where it's used.
| @@ -1,94 +1,18 @@ | |||
| import type { ReactNode } from "react"; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just throwing this comment here: I saw you mentioned the visual tests pass which is great! I was thinking we should still include new tests for the new composable version. Ideally they'd be a separate visual test.. so introducing new screenshots rather than updating the existing ones on this PR.
Not a blocker, just something I think we try to do for new compound components :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great feedback! I'll write some new ones and get those added.
| <Page.TitleMeta | ||
| title={title} | ||
| titleMetaData={titleMetaData} | ||
| subtitle={subtitle} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a reason we'd want to stop at this level rather than continuing to a point where we have: Page.Subtitle, Page.Title, and Page.TitleMetaData?
all 3 of those feel like pieces that people would reasonably want to customize since 2/3 are already ReactNodes.
so we'd have something like
<Page.Wrapper>
</Page.Wrapper>
actually, do we even need a Page.Wrapper? or would it be possible to just have Page as the top level container?
either way, going back to what I was about to say!
<Page.Wrapper>
<Page.Header>
<Page.TitleBar>
<Page.Title>
// this is the default, you can give it different content but it'll be the predefined component
<Page.TitleLabel><Trans>Hello friend</Trans></Page.TitleLabel>
// this is a fully custom value that may get out of sync
<Heading level=3><Trans>Hola Amigo</Trans></Heading>
</Page.Title>
<Page.TitleMetaData>
// doesn't seem like we have a default? so it's just a slot really
<SomeComponent/>
</Page.TitleMetaData>
<Page.Subtitle>
// this is the default
<Page.SubtitleLabel><Trans>This is my subtitle that gets the default appearance</Page.Subtitle>
// this is for fully custom
<Text><Trans>I want my subtitle to look entirely different for some reason</Trans></Text>
</Page.Subtitle>
</Page.TitleBar>
// other parts
</Page.Header>
</Page.Wrapper>
I'm not 100% on the "label" naming but that's what I did in Menu so just went with the same idea.
basically we are providing the slot, that way you can customize heavily, but we also provide the default if all you want is a declarative style but are otherwise happy with what we provide
| ref={primaryAction?.ref} | ||
| visible={!!primaryAction} | ||
| > | ||
| <Button {...getActionProps(primaryAction)} fullWidth /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this getActionProps feels a bit clunky. I wonder if we couldn't find a way to abstract that or avoid people having to use this on their instances.
| <Page.ActionGroup visible={!!showActionGroup}> | ||
| <Page.PrimaryAction | ||
| ref={primaryAction?.ref} | ||
| visible={!!primaryAction} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like there's gotta be an alternate approach here.
having a visible prop with a declarative style feels redundant.
ie. if I don't want it to be visible, then wouldn't I simply not render it?
{isAdmin && <Page.PrimaryAction ...>}
| > | ||
| <Button {...getActionProps(primaryAction)} fullWidth /> | ||
| </Page.PrimaryAction> | ||
| <Page.ActionButton |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
curious how this would work now. previously we would limit you to a primary action and a single secondary.
looking at how it works now, I assume I would be able to add as many ActionButtons as I want. maybe that's fine, but if we have opinions on how many buttons a Page should have, that would be worth documenting.
| type="secondary" | ||
| /> | ||
| </Page.ActionButton> | ||
| <Page.ActionButton visible={!!showMenu}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same idea for the ActionButtons, a visible prop feels odd to me. I wonder if we can avoid that and leverage the declarative style.
also kinda coming back to the topic of having N ActionButtons, it seems to me these are quite different.
one is a regular Button, and one is a Menu. while yes it will look the same at a glance, it has very different behavior.
how would someone know the default configuration? and how to build it?
I'm wondering about maybe a Page.PrimaryAction Page.SecondaryAction and Page.TertiaryAction or Page.ActionMenu
that way our opinions on how many elements should be there, in what order, and what they are is easy to understand and implement.
like with some other pieces, we'd still need to do a bit more work to create good simple defaults for people to use, and then also offer a "slot" where heavy customization can be applied - but they don't have to sort out the order/position/layout of these 3 actions.
| ...rest | ||
| }: { | ||
| readonly title: ReactNode; | ||
| readonly titleMetaData: ReactNode; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
currently titleMetaData is optional. let's stick to that unless there's a compelling reason to make it mandatory.
https://github.com/GetJobber/atlantis/blob/master/packages/components/src/Page/Page.tsx#L37
|
after trying to build with these pieces, I would say there are a handful of things we should strongly consider changing and a few others that would also be worthwhile improvements. that said, this is a great start! one final piece of FUD I have is what we are asking of consumers through all of this. if someone has an existing Page instance, and all they want is to apply data attributes to their actions - there's no way for them to only replace that module. they are going to have to fully refactor their entire that's a pretty big chunk of work for a relatively small addition. something to keep in mind. |
| import { type SectionProps } from "../Menu"; | ||
|
|
||
| export type ButtonActionProps = ButtonProps & { | ||
| ref?: React.RefObject<HTMLDivElement | null>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ugh, if we get that ref support on Button done, this can go away. I was wondering why we had these extra refs that we are stripping from the props with that extra helper function.
|
whoops, I forgot to try the entire point of all this 😅 I feel like giving the data attributes to a bit to sort out with the defaults cases, but for the fully custom where it's acting as a slot you'd just do another argument for more atomic pieces for defaults that we receive the props for and can abstract with a very thin layer. oh also |

Motivations
We chatted in a recent meeting about refactoring Page to be built from composed parts + using data-attributes.
Using visual regression tests as a refactor helper, I was able to refactor page to keep the exact same structure as it does today while exposing a variety of new compound pieces to build in a compositional way if required.
Each compound piece accepts data-attributes, so in the edge case where someone needs very specific data-attributes on pieces of Page, they can now accomplish that! :)
Changes
Code Notes
We could prooooobably break down that ternary statement in PageTitleMeta that's deciding what to render. That still feels a bit smelly to me.
Testing
Page should work completely 100% as-is with no changes by consumers.
However, you should also now be able to build composed pages for advanced use cases as well!
Changes can be
tested via Pre-release
In Atlantis we use Github's built in pull request reviews.