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

feat(#5): Add Button component #11

Merged
merged 14 commits into from Jan 26, 2023
Merged

Conversation

ynotdraw
Copy link
Contributor

@ynotdraw ynotdraw commented Jan 20, 2023

🚀 Description

This PR introduces the Button component.

Closes #5


❓ Outstanding Questions

  • Should we provide a fractal page object for the Button component?
  • Add template tag and convert the test to .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

demo-shot

@ynotdraw ynotdraw self-assigned this Jan 20, 2023
@ynotdraw ynotdraw linked an issue Jan 20, 2023 that may be closed by this pull request
@ynotdraw ynotdraw force-pushed the 5-add-the-toucan-button-component branch from 550fda4 to c7749ac Compare January 21, 2023 00:14
Comment on lines 135 to 148
<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>
Copy link
Contributor Author

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
Copy link
Contributor Author

@ynotdraw ynotdraw Jan 21, 2023

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
Copy link
Contributor Author

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

@ynotdraw ynotdraw changed the title feat: Add Button component feat(#5): Add Button component Jan 21, 2023
@@ -0,0 +1,15 @@
```hbs template
<Button @onClick={{this.onClick}} @variant='secondary'>Button</Button>
Copy link
Contributor Author

@ynotdraw ynotdraw Jan 21, 2023

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.
Copy link
Contributor Author

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 ☺️

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good to me 🙃

Copy link
Contributor

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.

@ynotdraw ynotdraw force-pushed the 5-add-the-toucan-button-component branch from c7749ac to d736e0b Compare January 21, 2023 00:51
@ynotdraw ynotdraw marked this pull request as ready for review January 21, 2023 00:56
Copy link
Contributor

@simonihmig simonihmig left a 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...

docs-app/package.json Outdated Show resolved Hide resolved
docs/components/button/index.md Outdated Show resolved Hide resolved
Comment on lines 8 to 18
{{#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}}
Copy link
Contributor

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:

Suggested change
{{#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.

Copy link
Contributor

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.

Copy link
Contributor

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?

Copy link
Contributor

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

Copy link
Contributor Author

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 😄

ember-toucan-core/src/components/button/index.ts Outdated Show resolved Hide resolved
test-app/tests/integration/components/button-test.ts Outdated Show resolved Hide resolved
test-app/tests/integration/components/button-test.ts Outdated Show resolved Hide resolved
@NullVoxPopuli
Copy link
Contributor

NullVoxPopuli commented Jan 23, 2023

re the outstanding questions:

Should we provide a fractal page object for the Button component?

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.
fractal page object solves the problem of nesting helpers and querying elements, which a Button doesn't really have that problem -- future components certainly would though.

Add template tag and convert the test to .gts in a follow up PR or pair with someone next week

I personally would. I very much do not like working in the old format of tests these days 🙃
We also need a total of 200 repos using this format before GitHub will consider a PR that adds native syntax highlighting support to the Web UI. We're def still in early adopter territory -- but only because "the format", does not have a single location for docs, and linting has a bug or two, etc.

@ynotdraw ynotdraw marked this pull request as draft January 24, 2023 18:09
@ynotdraw ynotdraw force-pushed the 5-add-the-toucan-button-component branch from 4e805d7 to bfbd63e Compare January 25, 2023 23:37
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.
@ynotdraw ynotdraw force-pushed the 5-add-the-toucan-button-component branch from bfbd63e to 125db0a Compare January 26, 2023 00:03
@ynotdraw ynotdraw marked this pull request as ready for review January 26, 2023 00:07
@ynotdraw
Copy link
Contributor Author

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! 🙇

Copy link
Contributor

@simonihmig simonihmig left a 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!

test-app/.template-lintrc.js Show resolved Hide resolved
@@ -8,3 +8,6 @@ runs:
with:
name: dist
path: ${{ env.dist }}
- name: 'Install dependencies'
shell: 'bash'
run: pnpm install --force
Copy link
Contributor

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?

Copy link
Contributor Author

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! 🙇

Copy link
Contributor

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...

Copy link
Contributor

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:

@ynotdraw ynotdraw merged commit 3e84d7c into main Jan 26, 2023
@ynotdraw ynotdraw deleted the 5-add-the-toucan-button-component branch January 26, 2023 19:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add the Toucan Button component
3 participants