-
Notifications
You must be signed in to change notification settings - Fork 92
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
refactor: Migrate NcButton
from render function to template
#6033
base: next
Are you sure you want to change the base?
Conversation
e8fd644
to
1b5c7b5
Compare
ab9aed0
to
04e0282
Compare
NcButton
NcButton
from render function to template
04e0282
to
c0dd0de
Compare
src/components/NcButton/NcButton.vue
Outdated
/** | ||
* By default the icon is set to `aria-hidden="true"` to hide it from the accessibility tree. | ||
* But sometimes this is not desired, e.g. for a loading icon with an accessible name, | ||
* setting this prop will make the icon appear in the accessible tree. | ||
* @default false | ||
* @since 9.0.0 | ||
*/ | ||
noIconAriaHidden: { | ||
type: Boolean, | ||
default: false, | ||
}, |
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 any other cases, except loading? If it's only for loading, I think, it's more useful to provide built-in loading feature.
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.
You could also have other accessible names on icons if you use the icon only variant.
But yes in that case it probably makes sense to set the aria label directly on the 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.
My fear with non-hidden icons with accessible names is that developers will use icon name as an accessible name (like it's done in some icon libraries) while it's usually not correct:
- Icon names are not translated
- Icon name may not represent the action correctly, like
alarm
name forset reminder
orlaptop
foredit locally
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.
Maybe this not needed at all? Imagine its a loading spinner for a dialog button to save a form:
What about this:
<NcButton aria-label="Saving…" title="Saving…"><template #icon><NcLoadingIcon /></template></NcButton>
Meaning put it directly into the aria-label.
So we need no prop at all here.
541f542
to
228fb39
Compare
1. Make it a template component instead of a render function 2. Add a `text` prop to allow passing text content as prop 3. Add `noIconAriaHidden` to allow making the icon appear on the accessible tree Co-authored-by: Ferdinand Thiessen <[email protected]> Co-authored-by: Grigorii K. Shartsev <[email protected]> Signed-off-by: Ferdinand Thiessen <[email protected]>
228fb39
to
562a8b7
Compare
* This can be overwritten by using the *default* slot. | ||
* @since 9.0.0 | ||
*/ | ||
text?: string | ||
|
||
/** | ||
* Specifies the button variant. | ||
* If left empty, the default button style will be applied. | ||
* | ||
* @default 'secondary' | ||
*/ | ||
variant?: EnumOrValue<ButtonVariant> | ||
|
||
/** | ||
* Specifies the button native type | ||
* If left empty, the default "button" type will be used. | ||
* | ||
* @default 'button' | ||
*/ | ||
type?: EnumOrValue<ButtonType> | ||
|
||
/** | ||
* Specifies whether the button should span all the available width. | ||
* By default, buttons span the whole width of the container. | ||
* @default false | ||
*/ | ||
wide?: boolean | ||
|
||
/** | ||
* Always try to provide an aria-label to your button. | ||
* | ||
* Make it more specific than the button's name by provide some more context. | ||
* E.g. if the name of the button is "send" in the Mail app, | ||
* the aria label could be "Send email". | ||
*/ | ||
ariaLabel?: string | ||
|
||
/** | ||
* Providing the href attribute turns the button component into an `a` element. | ||
*/ | ||
href?: string | ||
|
||
/** | ||
* Target for the `a` element if `href` is set. | ||
* @default '_self' | ||
*/ | ||
target?: string | ||
|
||
/** | ||
* When `href` is set this will make the browser try to download the target. | ||
* If a string is passed the browser will use it as the filename. | ||
* | ||
* Note that the browser might adjust it for allowed characters (e.g. '/' or '\'). | ||
* Also this only works with same-origin URLs and `blob:` or `data:` schemas. | ||
* Moreover a `Content-Disposition` header set by the server will override the filename. | ||
*/ | ||
download?: string|true | ||
|
||
/** | ||
* The pressed state of the button if it has a checked state. | ||
* This will add the `aria-pressed` attribute and for the button to have the primary style in checked state. | ||
* | ||
* Note: Pressed state is not supported for links. | ||
*/ | ||
pressed?: boolean | ||
|
||
/** | ||
* Providing the to attribute turns the button component into a `router-link` element. | ||
* | ||
* Note: This takes precedence over the href attribute. | ||
*/ | ||
to?: string | RouteLocation | ||
} | ||
|
||
export interface NcButtonEvents { | ||
/** | ||
* Emitted when the button was clicked. | ||
*/ | ||
click: [e: MouseEvent] | ||
|
||
/** | ||
* If the button is a toggle button (`pressed` state is boolean), | ||
* then this will be emitted if the user toggled the state. | ||
*/ | ||
'update:pressed': [pressed: boolean] | ||
} | ||
|
||
export interface NcButtonSlots { | ||
/** | ||
* Custom button content. | ||
* This can be used for custom formatted content, ensure to not include any interactive elements. | ||
* For plain text it is preferred to use the `text` prop instead. | ||
*/ | ||
default?: Slot | ||
|
||
/** | ||
* Icon (optional) to show within the button | ||
*/ | ||
icon?: Slot | ||
} |
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.
Please let's keep component props/events/slots in the component.
SFC concept is about having everything that is direct component definition in one file. Props are very part of the component, even more important than styles. When a component is developed/debugged, a developer very often needs to see component props and other interfaces.
/** | ||
* Helper to allow either the enum T or its string variant. | ||
* |
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.
Or just remove enum-s as a kind of legacy feature?
:class="[ | ||
'button-vue', |
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.
To separate static values and dynamic binding
:class="[ | |
'button-vue', | |
class="button-vue" | |
:class="[ |
default: false, | ||
}, | ||
]" | ||
:disabled="disabled" |
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.
Vue 3.5 💅✨
:disabled="disabled" | |
:disabled |
import isSlotPopulated from '../../utils/isSlotPopulated' | ||
import logger from '../../utils/logger' |
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.
import isSlotPopulated from '../../utils/isSlotPopulated' | |
import logger from '../../utils/logger' | |
import isSlotPopulated from '../../utils/isSlotPopulated.ts' | |
import logger from '../../utils/logger.ts' |
/** | ||
* The pressed state of the button if it has a checked state | ||
* This will add the `aria-pressed` attribute and for the button to have the primary style in checked state. | ||
* Update the current pressed state of the button (if the `pressed` property was configured) |
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.
Let's keep docs only in interface docs. Then it's compatible with all the tooling
cursor: default; | ||
& * { | ||
:deep(*) { |
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.
Does nothing in Vue 3
:deep(*) { | |
* { |
// Warning but we made sure that the log level is DEBUG above so we do not spam users | ||
logger.warn('You need to fill either the text or the ariaLabel props in the button component.') |
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 works if a developer dynamically removes aria-label
(never happens), but it doesn't work if a developer removes text on mobile (happens often).
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.
And I'm still not fun of rendering content 3 times instead of 1...
- In watch
- In
v-if
- In content render
2 + 3 happens each re-render
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'd consider getting rid of this check at all in flavor of check on e2e test level.
- Developers still successfully ignore this warning :(
- Much effort for only one of many places where we need to worry about a11y
☑️ Resolves
text
prop to allow passing text content as propnoIconAriaHidden
to allow making the icon appear on the accessible tree🏁 Checklist
next
requested with a Vue 3 upgrade