-
Notifications
You must be signed in to change notification settings - Fork 6
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
feat(#5): Add Button component #11
Conversation
550fda4
to
c7749ac
Compare
docs/components/button/index.md
Outdated
<div class="flex gap-x-4"> | ||
{{#each (array "primary" "secondary" "destructive" "link" "quiet" "bare") as |variant|}} | ||
<Button @variant={{variant}} @isLoading={{true}}> | ||
<:loading> | ||
<svg class='h-4 w-4 animate-spin' xmlns="http://www.w3.org/2000/svg" width="24" height="24" stroke="currentColor" viewBox="0 0 24 24"><path d="M5.95 5.7L7 6.75 8.05 7.8m8.4 8.4l.95.95.95.95m.2-12.4L17.5 6.75 16.45 7.8M6.35 12h-3.1m17.5 0h-2.6m-5.9 9v-3.1m0-14.9v3.1" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" /></svg> | ||
</:loading> | ||
|
||
<:default> | ||
{{variant}} | ||
</:default> | ||
</Button> | ||
{{/each}} | ||
|
||
</div> |
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.
Similar note here as above
```hbs | ||
<Button @isDisabled={{true}}> | ||
<:disabled> | ||
<svg |
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 icon I used in this example is from an MIT-licensed open source library I built. We can replace it at any time, as we see fit.
```hbs | ||
<Button @isLoading={{true}}> | ||
<:loading> | ||
<svg |
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 comment as above in regards to icons here
@@ -0,0 +1,15 @@ | |||
```hbs template | |||
<Button @onClick={{this.onClick}} @variant='secondary'>Button</Button> |
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 Button instance actually has a collision with the Button component from @crowdstrike/ember-oss-docs
. For now, we can continue to write docs, but be aware you may hit this if there are components named the same there.
The eventual goal is to:
- Release this as a package
- Update
ember-oss-docs
to use this repo
A bit of a 🐔 and 🥚 problem
@@ -0,0 +1,148 @@ | |||
# Button | |||
|
|||
Buttons are clickable elements used primarily for actions. Button content expresses what action will occur when the user interacts with 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.
I am not a writer, so appreciate any feedback in this entire document. I did my best tho
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.
sounds good to me 🙃
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.
Unlike our internal docs, which focus on all disciplines at once, these "core" docs, are primarily focused on developers.
c7749ac
to
d736e0b
Compare
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.
Nice work! 👍
I do have a question about the role of the component though, see my inline comment...
{{#if @isLoading}} | ||
{{yield to="loading"}} | ||
<span class="sr-only">{{yield}}</span> | ||
{{else if @isDisabled}} | ||
<span class="flex items-center gap-x-2"> | ||
{{yield}} | ||
{{yield to="disabled"}} | ||
</span> | ||
{{else}} | ||
{{yield}} | ||
{{/if}} |
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 might be missing context here, like it could be that this was designed exactly like implemented here for good reasons, but I am not sure I fully understand the design choices here, in terms of what assumptions about the passed content this component needs to make, and basically how opinionated it wants to be...
Like do we have strong opinions here (compared to the consumer side) about e.g. how a button in loading state should look like? Like you could have a strong opinion by mandating that a loading button always need to have a specific (according to our design system) loading spinner icon. Which we don't have here. Or you could be very un-opinionated by delegating all content choices to the consumer. But this is somewhat in the middle, by delegating a good amount of things to the consumer by yielding, but still having a strong opinion that the default button content should be in DOM, but invisible (to sighted users).
For example. this could be how an un-opinionated button could work:
{{#if @isLoading}} | |
{{yield to="loading"}} | |
<span class="sr-only">{{yield}}</span> | |
{{else if @isDisabled}} | |
<span class="flex items-center gap-x-2"> | |
{{yield}} | |
{{yield to="disabled"}} | |
</span> | |
{{else}} | |
{{yield}} | |
{{/if}} | |
{{#if @isLoading}} | |
{{yield to="loading"}} | |
{{else if @isDisabled}} | |
{{yield to="disabled"}} | |
{{else}} | |
{{yield}} | |
{{/if}} |
I am not necessarily advocating for that, I am just basically about the role this button component should play, and how opinionated it should be? Like you could as well argue to bake more opinions into it (that's what a design system does), like in the example mentioned above about having a loading spinner baked into it.
Also, I think the use of multiple {{yield}}
could get confusing here. Like I probably wouldn't have expected that the content of a disabled button is the combination of both <:default>
and <:disabled>
blocks, and (again an opinion here) that the disabled block comes after the default one.
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 think the opinionatedness is good, because Buttons tend to be a bit complicated, and providing accurate styling for them (especially when it comes to the position of an icon), as non trivial, and consumers of Buttons often get it wrong.
So, whenever we have the possibility for a default icon to be present, I think it's good that we have a flex gap-x-2 wrapper in there.
This also forces the consumer of the Button to be mindful of their own design language, in the Toucan design system does not want icons on the right-hand-side of buttons, because that is used exclusively for "status" (disabled, loading, etc).
For the first block, where we also have an sr-only
class, I think is a good idea, because people are not good at accessibility by default, so the more we can do for them, the better.
These conventions are part of the design system as a whole, and less so "just things we do", it's very much in the same spirit of how Material handles the label and underline on their inputs -- the features they describe are required, otherwise (and the same applies here), we'd only be providing "light styled basic elements".
What will be important to mention in our docs for this button, however, is these behaviors happening via yield
, with lots of examples.
We need to, in our docs for this Button, explain every use case, else, I agree folks won't understand -- but we want them to understand, and documentation can help with the context around these button decisions.
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 think the opinionatedness is good
So, as I tried to explain above, I was not arguing pro or contra opinionatedness, I was mostly asking how opinionated we want to be. And I feel this is currently somewhere in the middle of both worlds, and not sure if it is really well balanced this way...
For the first block, where we also have an sr-only class, I think is a good idea, because people are not good at accessibility by default, so the more we can do for them, the better.
To take this as an example: yes, the component now has an opinion about buttons in a loading state, in that the default label is now hidden for sighted users. But it does not have an opinion on what to show to sighted users instead, as it delegates that to the consumer by {{yield to="loading"}}
.
So what happens when I do <Button @loading={{true}}>Save</Button>
?
AFAICT it would render an empty button, no (visual) text, no icon, right? Is that expected?
So if we want to be opinionated, shouldn't we bake the looks of a loading button into the component, as it is part of a design system that probably has an opinion on how a loading button should look like, e.g. by implicitly showing a toucan loading spinner as a replacement for the default text?
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.
But it does not have an opinion on what to show to sighted users instead
this is, in part, because we allow any icon set to be used.
Internally we show an icon only, but a consumer may want to use their own icon, or their own text.
I would like to tighten this up in a major / breaking change release, but we need to split our icon set before we can do that.
<Button @loading={{true}}>Save</Button>?
AFAICT it would render an empty button, no (visual) text, no icon, right? Is that expected?
yeah, for this reason, we'd want to either:
- provide lints that go along with this library (I'm doing the same for ember-resources for anti-footgun stuff, here: Create eslint plugin NullVoxPopuli/ember-resources#709)
- open source our base icons and force their usage (this would mean to build a full app, folks would have a mix of icon sets)
- our base icon set is not presently open sourced due to scope-explosion in how everyone wanted to automated how icon exports occur from Figma (automatically creating v2 addon packages based on the Figma "Frame") -- it's a lot of "cron" work, but still probably worth doing
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.
provide lints that go along with this library (I'm doing the same for ember-resources for anti-footgun stuff, here: NullVoxPopuli/ember-resources#709)
I really love this idea 😄
re the outstanding questions:
I don't know that it necessarily needs to be a fractal page object, but it's probably a good idea to standardize on those.
I personally would. I very much do not like working in the old format of tests these days 🙃 |
4e805d7
to
bfbd63e
Compare
To be able to run glint, we need the dist directory. So we need to build first so we have our artifacts and then we can lint, knowing we have everything we need to not get type errors.
bfbd63e
to
125db0a
Compare
Thanks y'all for the patience and assists! I believe I've covered all of the feedback and issues and this should be safe to re-review now! 🙇 |
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.
Approved for my part, but please have a look at the comment, and see if that needs to be addressed.
Nice work!
@@ -8,3 +8,6 @@ runs: | |||
with: | |||
name: dist | |||
path: ${{ env.dist }} | |||
- name: 'Install dependencies' | |||
shell: 'bash' | |||
run: pnpm install --force |
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 haven't really dived deep into the more special CI setup you and @NullVoxPopuli have in use for GH repos here (forms is still on blueprint defaults), so leaving the final assessment to you guys. But it seems to me if the intend was to do less by reusing build artefacts across jobs, then we loose much (all?) of this benefit when forcing to install all dependencies again, don't we?
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.
Good point. Some background:
I was talking to @NullVoxPopuli about this yesterday as I was hitting an issue where the lint step (which runs glint
) was failing for the test-app
. We essentially need the dist directory to be able to successfully run lint, or else we get type errors when running glint. Here's an example: https://github.com/CrowdStrike/ember-toucan-core/actions/runs/4009103591
Talking to @NullVoxPopuli , the reason we need to re-install dependencies is due to pnpm. We need to re-run pnpm i
to re-sync the injected links. We do the same thing over in ember-headless-table
(https://github.com/CrowdStrike/ember-headless-table/blob/f08ff968d862d2267a3b2f6269116806549d51b0/.github/actions/download-built-package/action.yml#L11).
I do agree with you though, that this feels like we add additional time to each step using this action by forcing dependencies to re-install. Maybe there's an optimization we can make in the future though? I'm also completely new to pnpm
, so I don't have much context other than these convos! 🙇
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.
Oh, I see. I guess this is this inject thing that Preston told me also about...
Yeah, ok. Unfortunate, but hopefully this will be sorted out in the future somehow...
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.
Yeah, hoping we can work out a plan of attack with @zkochan (pnpm owner) with making injected auto-sync.
My current best idea is to analyze package.json#files/exports for folders, and symlink those, so that files automatically sync, and we don't need the extra pnpm i
.
Related:
🚀 Description
This PR introduces the
Button
component.Closes #5
❓ Outstanding Questions
.gts
in a follow up PR or pair with someone next week🔬 How to Test
CI
CI (Actions) should all be green 🟢 . CI tests linting and tests!
Docs App
Pull down this repo and run the following:
pnpm i cd docs-app pnpm start
Go to the docs page and behold all of their glory.
📸 Images/Videos of Functionality
Quick scroll-through of the docs-app page
demo.mov
Image of docs-app page