patternfly-yew / patternfly-yew Goto Github PK
View Code? Open in Web Editor NEWPatternFly components for Yew
Home Page: https://patternfly-yew.github.io/patternfly-yew-quickstart/
License: Apache License 2.0
PatternFly components for Yew
Home Page: https://patternfly-yew.github.io/patternfly-yew-quickstart/
License: Apache License 2.0
You can currently create a click callback on cards with the onclick
prop. However, it isn't communicated to the user. The Patternfly docs show that the cards are dimmed when hovering them and that the cursor becomes a pointer.
I tried adding pf-m-clickable
to the classes but that doesn't seem to do anything by itself.
Am using pf-m-selectable-raised
as a workaround for now.
Some compenent have a callback prop named onchange
e.g TextInput
and some other have on_change
e.g. Switch
Having cohesive names would be nice :)
For end-to-end testing, there needs to be a good way to select a component. I think the best way to do this would be to add a data-test
property to components.
This means in particular that in components that use composition, there should be meaningful names given to the data-test
attribute.
...
// let onset_per_page =
let limit_callback ={
use_callback(move |number, limit|{
limit.set(number)
}, limit.clone())
};
let nav = use_state(|| Navigation::First);
let nav_callback = {
use_callback(move |_page: Navigation, nav|{
gloo::console::log!("clicked next page");
nav.set(Navigation::Page(2));
}, nav.clone())
};
html!{
<div>
<Pagination
total_entries={Some(raw_elemets.len() as u32)}
offset={offset.deref()}
entries_per_page_choices={vec![5, 10, 25,50,100]}
selected_choice={*limit.deref()}
limit_callback={limit_callback}
navigation_callback={nav_callback}
/>
<Table<SharedTableModel<NumbersTable>>
header={table_headers}
entries={entries}
mode={TableMode::Compact}/>
</div>
}
}
so I would click the arrows to indicate the next page but I wouldn't go to the next page
https://github.com/finnbear/yew_icons seems like a great source for icons. I think it makes sense keeping what we have, but also allowing to integrate with yew-icons
.
= help: the trait yew::Component
is implemented for ContextProvider<T>
= note: required for Bullseye
to implement BaseComponent
use patternfly_yew::prelude::*;
use yew::prelude::*;
#[function_component]
fn App() -> Html {
html! {
<Bullseye>
<div>{"Item #1"}</div>
</Bullseye>
}
}
fn main() {
yew::Renderer::<App>::new().render();
}
I am getting the error "the trait From<VNode>
is not implemented for SelectChildVariant<std::string::String>
" when I try to do the following: What could I be missing?
#[function_component(AddItem)]
pub fn _view() -> Html{
let brand_vec = vec!["Nike".to_string(), "Puma".to_string(), "Sonny".to_string(), "Adidas".to_string()];
let nike = &brand_vec[0];
let puma = &brand_vec[1];
let adidas = &brand_vec[3];
let options = brand_vec.iter().map(|brand| {
let brand = brand.clone();
html!{
<select class="form-control">
<option value="{&brand}">{brand}</option>
</select>
}
}).collect::<Html>();
html!{
<>
<div>
<Select<String> placeholder="Select Brand">
// <SelectOption<String> value={nike.clone()} />
// <SelectOption<String> value={puma.clone()} />
// <SelectOption<String> value={adidas.clone()} />
{options}
</Select<String>>
</div>
</>
}
}
I want to make a tree with checkboxes as part of a form. I used the tree code from the quickstart (no checkboxes for now) and realized that collapsing part of the tree briefly looks correct but then crashes. Moving the component outside of the form lets everything work correctly:
Example code:
#[function_component(AddChannelForm)]
pub fn add_channel() -> Html {
html! {
<Form>
<FormGroup label="Name">
<next::TextInput
required=true/>
</FormGroup>
<FormGroup>
<ComponentSelector/>
</FormGroup>
<ActionGroup>
<Button variant={ButtonVariant::Primary} label="Submit" r#type={ButtonType::Submit}/>
</ActionGroup>
</Form>
}
}
// !!!! This is just the tree example code from the quickstart
#[function_component(ComponentSelector)]
pub fn component_selector() -> Html {
let header = html_nested! {
<TreeTableHeader>
<TableColumn label="foo" width={ColumnWidth::Percent(40)}/>
<TableColumn label="L1" width={ColumnWidth::Percent(20)}/>
<TableColumn label="L2" width={ColumnWidth::Percent(20)}/>
<TableColumn label="L3" width={ColumnWidth::Percent(20)}/>
</TreeTableHeader>
};
#[derive(PartialEq)]
struct Root {
root: Rc<Node>,
}
#[derive(PartialEq)]
enum Node {
Branch(Vec<String>, Vec<Rc<Node>>),
Leaf(Vec<String>)
}
impl TreeTableModel for Root {
fn children(&self) -> Vec<Rc<dyn TreeNode>> {
self.root.children()
}
}
impl TreeNode for Node {
fn render_main(&self) -> Cell {
match self {
Self::Branch(name, _) => html!({name.join(" / ")}),
Self::Leaf(name) => html!({name.join(" / ")}),
}.into()
}
fn render_cell(&self, context: CellContext) -> Cell {
let name = match self {
Self::Branch(name, _) | Self::Leaf(name) => name,
};
// quick alternative to: match context.column { 0=> {}, … }
name.get(context.column)
.map(Html::from)
.unwrap_or_default()
.into()
}
fn children(&self) -> Vec<Rc<dyn TreeNode>> {
match self {
Self::Branch(_, children) => children.iter().map(|c|c.clone() as Rc<dyn TreeNode>).collect(),
Self::Leaf(_) => vec![],
}
}
}
let mut root = vec![];
for a in ["I", "II", "III"] {
let mut folders = vec![];
for b in [1,2,3] {
let mut leaves = vec![];
for c in ["a", "b", "c"] {
leaves.push(Rc::new(Node::Leaf(vec![a.to_string(), b.to_string(), c.to_string()])));
}
folders.push(Rc::new(Node::Branch(vec![a.to_string(), b.to_string()], leaves)));
}
root.push(Rc::new(Node::Branch(vec![a.to_string()], folders)));
}
let root = Rc::new(Root {root: Rc::new(Node::Branch(vec![], root))});
html!{
<TreeTable<Root>
mode={TreeTableMode::Compact}
header={header}
model={root}
/>
}
}
If using a tree here isn't intended, then there should maybe be some trait bounds, so it isn't usable here.
This issue is for tracking the upgrade to PF5.
Components:
Layouts:
Misc:
AB Comparison:
TableModel
has a generic Key
type that can be used for supplying better keys but MemoizedTableModel
and UseStateTableModel
simply assign usize
and use the index in the entry vector.
This causes issues for when the data changes as the indices don't match the data anymore.
One example is if you render a cell with state inside, the state will actually be cached from the entry that was previously in that position leading to some surprising behaviour.
Since keys should really always be used for lists, I would propose letting the user assign keys and I've come up with two options that would work.
Option<Callback<EntryType, KeyType>>
which is called on every row. If no callback is specified, then we can fall back to using the index.
The good thing about this approach is that it wouldn't even be a breaking change.
TableEntryRenderer
This would be a breaking change but it would force users to come up with proper keys and is a lot nicer to read in the code.
The only component that has a class / className prop is the Button. Would be helpful for additional classes.
I can throw up a PR for this tomorrow.
If you put a ContextSelector in a Form, it will refresh the page when you click on it.
<Form>
<ContextSelector>
</ContextSelector>
</Form>
Currently callbacks carry the actual information, but not the original event. Like:
onchange: Callback<bool>
PR #86 added the event information to that:
onchange: Callback<(Event, bool)>
The led to a number of quite verbose callback definitions:
let cb = Callback::from(|(_, state) : (Event, CheckboxState)| { … })
I think we need a better way. Let's gather ideas.
patternfly-react uses svg-based icons whereas we use font-based icons. This causes some differences in behavior.
patternfly-react's svg images have a fixed width and height of 1em (link). Our fonts have variable widths between characters so lists of icons don't appear as uniform.
Example - dual list selector
The single angle brackets at the top and bottom aren't aligned because they have a width of 46px whereas the double angle brackets have a width of 48px
width of all icons set to 1em using debugger:
The icons all have the exact same width causing the single angle bracket to be nicely centered.
<i>
by adding style props?Related: #53
With a select component, clicking away chips with the X, makes the menu open and close.
The click event of the close button must not bubble up to the actual component (if handled by the click).
I'm not sure if I'm forgetting a property somewhere or if it's really not working, but I can't make selectable Cards working.
The simplest example I can reproduce is:
use {patternfly_yew::prelude::*, yew::prelude::*};
#[function_component(App)]
fn app() -> Html {
html! {
<>
<Card title={html!{"1"}} selectable=true>
<CardBody>
{"card 1"}
</CardBody>
</ Card>
<Card title={html!{"2"}} selectable=true>
<CardBody>
{"card 2"}
</CardBody>
</ Card>
<Card title={html!{"3"}} selectable=true>
<CardBody>
{"card 3"}
</CardBody>
</ Card>
</>
}
}
fn main() {
yew::Renderer::<App>::new().render();
}
Clicking on any card always toggle the first one.
[ Please note, this is an information issue only, not a real roadmap ]
This is based on components only (no charts, and other patternfly features):
We should be able to set the aria attributes on the Breadcrumb component, but cannot in all variants.
See the breadcrumb_router_item
function in components/breadcrumb/router.rs
We should investigate if this is an issue elsewhere.
The following code:
<List r#type={ListType::Inline}>
{"Using DividerType::Li"}
<Divider r#type={DividerType::Li} orientation={DividerOrientation::Vertical.all()} />
{"Creates a list item divider."}
</List>
Generates this html:
<ul class="pf-c-list pf-m-inline">
<li>Using DividerType::Li</li>
<li><li role="separator" class="pf-c-divider pf-m-vertical"></li></li>
<li>Creates a list item divider.</li>
</ul>
FlexChild does not implement From
Using a version of this example https://patternfly-react-v5.surge.sh/components/divider#vertical-in-flex-layout-inset-at-various-breakpoints :
<Flex>
<FlexItem>{"first item"}</FlexItem>
<Divider orientation={DividerOrientation::Vertical.lg()}/>
<FlexItem>{"second item"}</FlexItem>
</Flex>
Doesn't compile and gives this error!
error[E0277]: the trait bound `FlexChild: From<DividerProperties>` is not satisfied
--> src/components/divider/mod.rs:33:14
|
33 | <Divider orientation={DividerOrientation::Vertical.lg()}/>
| ^^^^^^^ the trait `From<DividerProperties>` is not implemented for `FlexChild`
The about dialog does not trap focus. This is a accessability issue.
PF React appears to use a React wrapper around https://www.npmjs.com/package/focus-trap . I could not find a pure rust solution for this, we should probably write bindings for this.
There is a new, upcoming, major release of PatternFly called v5.
I think right now it is too early to migrate, but we should track it and be ready for it at some point.
The question is on how to go forward. There are a few upcoming (next) breaking things in the main branch. And I do believe that migrating to PatternFly 5 would mean a breaking version change.
So my proposal would be the keep the current main (with the "next" stuff) and merge this all into main
(without next stuff), migrating to PF5 in the process. Releasing this as 0.5 once it's ready. Keeping 0.4 around for PF4, but focusing on PF5 from then on.
Having a new (PF5/FC based) menu, it is currently not possible to close the menu by clicking again on the menu toggle.
The reason is that that this is an "outside" click, which should (and does) close the drop down content. However, the event
cannot be canceled, and so it also counts as a "toggle" action, which will then re-open the menu.
I would have liked a horizontal navigation page as described in Patternfly but I see that I can't eliminate the bars button.
From what I could see in the code there is no way to do this with the Page component. Or is there a way?
Marco
The current Table
implementation is convenient but limiting in a few ways. I would like to discuss expectations we have for a more ergonomic/powerful implementation so we can gather requirements and see how it works.
I think the biggest issue is that Table
only gives control at the level of an entire Vec
of entries. There is a lot happening at the level of individual rows so for me it would make sense to move the abstraction to the row level.
I could imagine an interface similar to the following:
enum Columns {
First,
Second,
Third,
}
struct Entry {
id: String,
}
#[function_component(MyTable)]
fn table() -> Html {
let entries = use_memo((), |_| get_entries());
let header = html_nested!(<TableHeader<Columns>> ...); // same as before
html! {
<Table<Columns> {header}>
<TableBody>
{
for entries.iter().map(|entry| {
html!(<MyRow key={entry.id.clone()} entry={entry.clone()}/>)
}
}
</TableBody>
</Table<Columns>>
}
}
#[derive(Debug, Clone, PartialEq, Properties)]
struct MyRowProperties {
entry: Entry,
}
#[function_component(MyRow)]
fn row(props: &MyRowProperties) -> Html {
let count = use_state(|| 0);
let onclick = use_callback(count.clone(), |_, count| count.set((*count) + 1));
let render_cell = use_callback(
(count.clone(), onclick.clone(), props.entry.clone()),
|col, (count, onclick, entry)| {
match col {
Columns::First => html!({entry.id.clone()}),
Columns::Second => html!({*count}),
Columns::Third => html!(<Button label="Increment" {onclick} />),
}
}
);
let expand_content = use_memo((), |_| html!({"foo"}));
let onrowexpand = use_callback(expand_content.clone(), |_, expand_content| (*expand_content).clone());
// Similar callback for oncolumnexpand that matches by column and returns OptionalHtml
// Callback for onrowclick
// Selected is just a prop
html! {
<TableRow<Columns> {render_cell} {onrowexpand} />
}
}
TableRow<Column>
is used within the TableBody
. Trying to use any sort of ChildrenWithProps
would be much too painful if the goal is to let users have state within the row.ComposableTable
handle those casesWhen clicking on the item of a dropdown menu the expectation is that the menu closes again. Currently it doesn't.
This is a regression coming from the PF5/Menu migration.
I think the proper solution is to:
MenuContext
I can't figure out what I'm doing wrong, but the compiler keeps complaining about a not implemented trait (Target), when I thought I had indeed implemented it using the derive macro. I've also tried to replace the derive macro with the generated code (using cargo macro expand), but the issue is exactly the same.
I'm using
yew = { version = "0.21", features = ["csr"] }
yew-nested-router = "0.6.1"
patternfly-yew = { version = "0.5.5" }
My minimum reproducible example is:
use {
patternfly_yew::prelude::*,
yew::prelude::*,
yew_nested_router::{Router, Switch as RouterSwitch, Target},
};
fn main() {
yew::Renderer::<App>::new().render();
}
#[function_component(App)]
pub(crate) fn app() -> Html {
html! {
<Router<AppRoutes> default={AppRoutes::Home}>
<Page
sidebar={
html_nested! {
<PageSidebar>
<Nav>
<NavRouterItem<AppRoutes> to={AppRoutes::Home}>{"Home"}</NavRouterItem<AppRoutes>>
</Nav>
</PageSidebar>
}
}
>
<RouterSwitch<AppRoutes> render={switch}/>
</Page>
</Router<AppRoutes>>
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Target)]
pub enum AppRoutes {
#[default]
Home,
}
pub(crate) fn switch(routes: AppRoutes) -> Html {
match routes {
AppRoutes::Home => {
html! {<h1>{ "Home" }</h1>}
}
}
}
Any help would be greatly appreciated ! Thanks
PF sometimes uses the Font Awesome fixed width class (fa-fw
) on icons. This should be added to the Icon component
I have a form component like the following:
#[function_component(FillBookmark)]
fn fill_bookmark() -> Html {
let tags = use_state(|| vec![]);
let title = use_state_eq(|| String::default());
let set_title = use_callback(title.clone(), |new_title, title| title.set(new_title));
let description = use_state_eq(|| String::default());
let set_description = use_callback(description.clone(), |new_desc, desc| desc.set(new_desc));
let notes = use_state_eq(|| String::default());
let set_notes = use_callback(notes.clone(), |new_notes, notes| notes.set(new_notes));
html! {
<>
<Form>
<FormGroup required=true label="Title">
<TextInput
required=true
autofocus=true
onchange={set_title}
value={(*title).clone()}
/>
</FormGroup>
<FormGroup label="Description">
<TextArea onchange={set_description} value={(*description).clone()} />
</FormGroup>
<FormGroup label="Notes">
<TextArea onchange={set_notes} value={(*notes).clone()} />
</FormGroup>
</Form>
</>
}
}
This results in a pretty form! But whenever I enter any character in the "description" or "notes" fields, the component gets re-rendered and input focus jumps back into the "title" field.
Is there a way to prevent this from happening, outside making a state value for autofocus that gets reset when any non-title value gets edited?
I'm trying to make an infinitely-scrolling gallery, of this kind of shape:
use patternfly_yew::prelude::*;
use yew::prelude::*;
#[derive(Properties, PartialEq, Clone, Debug)]
pub struct GalleryTestProps {}
#[function_component(GalleryTest)]
pub fn gallery_test(GalleryTestProps {}: &GalleryTestProps) -> Html {
let items = use_state(|| 50);
let add_items = Callback::from({
let items = items.clone();
move |_| {
items.set(*items + 50);
}
});
html! {
<>
<Gallery>
{ for (0..(*items)).map(|number|
html!{<ExampleItem key={number} {number}/>}) }
</Gallery>
<Button onclick={add_items}>{ "moar" }</Button>
</>
}
}
#[derive(Properties, PartialEq, Clone, Debug)]
struct ExampleItemProps {
number: usize,
}
#[function_component(ExampleItem)]
fn example_item(ExampleItemProps { number }: &ExampleItemProps) -> Html {
tracing::info!(?number, "rendering");
html! {
<Card>
<CardTitle>{ number }</CardTitle>
{ "This is item " }
{ number }
</Card>
}
}
(added to a page using <GalleryTest />
)
Clicking the button adds 50 more items to the gallery, but two unexpected things happen:
ExampleItem
gets re-rendered, as though the gallery was emptied out and recreated from scratch....
let onchange_category = {
let state = state.clone();
use_callback(move|category: Vec<String>, state|{
gloo::console::log!(category.clone().join(","));
let mut data = state.deref().clone();
data.categories = category.join(",");
state.set(data)
}, state)
};
html!{
....
<FormSection>
<FormGroup label="Select Categories">
<Select<String> chip={ChipVariant::Values} variant={SelectVariant::Multiple(onchange_category)}>
{
for categories_vec.iter().map(|category|{
html_nested!{<SelectOption<String> value={category.to_string()}/>}
})
}
</Select<String>>
</FormGroup>
....
}
As you can see every time, an item is selected, it is treated as SubmitEvent
and a request will be sent to the backend.
I want to fetch data from the backend in a component. Pressing a button inside the component will open a backdrop which shows some more of the fetched data.
Ideally, I would like to have a UseStateHandle
for the data, so I can check in the backdrop if all the data is already loaded.
However, once the data is there, it only reloads the data in the outside component (which you can sort of see in the background) and the component inside backdrop doesn't change.
Closing and reopening the backdrop view shows the correct data.
You can view a minimal example here.
It allows to add these components inside a modal for example. I'll maybe add it if I've time.
I think we have a problem with the generated IDs that we must fix.
this is when using <FormSelect<String> variant={SelectVariant::Multiple(onclick_change_attributes)}>
I have noticed several components using the aria-labelledby
attribute.
The aria-labelledby property enables authors to reference other elements on the page to define an accessible name. - aria-labelledby - Accessibility | MDN
I see a lot of the following in the PF5 docs (in this case I made up an example):
<Component id="foo" aria-labelledby="foo-title">
<div id="foo-title">Title</div>
<p>Some content</p>
</Component>
It seems that the id
for foo-title
is derived from the id
of the main component + title
. The problem is that we seem to follow the HTML pattern that the id
is optional, but if we follow this, and several instanced of the above component are created then we would end up with several elements with the same id
for example <div id="-title">
this may cause accessibility issues.
As I see it we have two options here:
My choice would be 2. We could create our own IdRequired
type which implement a Default using something like Nano ID if an id is not provided.
I my not quite sure, but I don't see the ability to get a reference to a patternfly-yew::Select<K>
eg. there is no ref
/ r#ref
or something similar to used with use_node()
hook.
setup is something like:
#[derive(Debug, Clone, PartialEq)]
struct Foobar {
id: u32,
name: String,
}
impl Display for Foobar {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
}
}
#[function_component(Edit)]
pub fn edit(props: &Props) -> Html {
let onsubmit ={
Callback::from(move |e: SubmitEvent| {
// get selected element
}
}
}
html! {
<Form>
<Select<Foobar>>
....
</Select<Foobar>>
</Form>
}
}
how can I access the data from the Select
component?
Continuing from patternfly-yew/patternfly-yew-quickstart#36 (comment):
Looks good to me, it might be worth adding a comment in the docs saying that the truncate doesn't necessarily truncate num grapheme clusters but rather rounds down to the next unicode codepoint after num bytes.
I'm not sure if adding any extra complexity to the code is worth it though. At best you could maybe trim off num unicode codepoints by using s.char_indices().rev().nth(num) which will still look weird in a lot of cases and anything better than that will require an extra dependency. So a comment would probably be good, just so people aren't surprised that there are only 5 characters visible when they wanted to keep 10.
A lot of components should navigable using arrow keys or similar.
You might find inspiration here: https://github.com/patternfly/patternfly-react/blob/main/packages/react-core/src/helpers/KeyboardHandler.tsx
Components using such handling can be found easily by looking for usages of the supplied functions from the above links in the patternfly-react code.
Currently we do have a full state of the table. Toggling a table entry forces the full table to be re-drawn. This should be more efficient and only toggled rows should be re-rendered.
I'm working on the search input and have the key functionality down but need some feedback on how the advanced search should work.
There doesn't seem to be much specification. The component documentation merely states:
The values used in the attribute-value form fields may contain spaces. The values containing spaces should be wrapped with quotes inside the summary search string in the input field.
I checked the TS code and you can find it here.
It uses an undocumented regex and does some operations without really specifying what the aims are, or the limitations.
I've played around with it a bit and it exhibits the following behavior (the delimiter is variable in the component but I'm just using :
here for now):
foo bar => [foo, bar]
"foo bar" => ["foo bar"]
foo:bar => [(foo, bar)]
foo:"bar baz" => [(foo, bar baz)]
"foo bar":"a b" => [(foo bar, a b)]
"foo:bar" => [(foo, bar)]
Thoughts:
If we have these rules it's pretty easy to build an iterator that just returns slices of the input string. No extra dependencies, no time for compiling a regex (and then either cache it or recompile everytime the input changes), no heap allocations, every character visited exactly once.
Let me know your thoughts
I want to show the toasts at the bottom right corner. Is it possible?
match result {
Ok(_) => {
toaster.toast(Toast {
title: "Success".into(),
body: "Exercise created".into(),
timeout: Some(Duration::from_millis(2000)),
..Default::default()
});
navigator.back()
}
Err(_) => {
toaster.toast(Toast {
title: "Error".into(),
body: "All of fields are necessary".into(),
timeout: Some(Duration::from_millis(2000)),
r#type: patternfly_yew::Type::Danger,
..Default::default()
});
}
}
Tracking issue for ToDo's before a 0.5.0 release … and I come up empty.
So time to speak up now!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.