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

[BETA] Card grid and checkboxes #748

Closed
wants to merge 15 commits into from

Conversation

MisRob
Copy link
Member

@MisRob MisRob commented Aug 23, 2024

Description

Introduces KCardGrid and KCardGridItem that enable grid and checkboxes functionality for KCard. Public APIs can be subject to change - besides providing functional prototype in support of 0.18, this PR serves as a proposal to collect feedback.

What works

  • Grids
  • Visual layout of checkboxes

To be done

In this PR as well as some upcoming PRs.

  • (1) Checkboxes a11y
    • Now only implemented my best guess, expectations to be discussed soon
  • (2) We will know more after (1), however first experiments revealed that the addition of checkboxes totally blows away the reason for having BaseCard since the card itself can't be li anymore, therefore its primary task of enforcing the markup structure is gone. Taking into account that BaseCard made some styling updates complex in the past and now adds even more hassle in several places with the addition of checkboxes, it would make sense to discuss its possible merging with KCard. We could use other means of ensuring the correct structure, such as warnings, documentation guidance, and building larger test suite. This should also help with maintainability and most importantly, I believe it would remove the need for KCardGridItem that was added because of the checkboxes. So public API would be simpler <KCardGrid><KCard /></KCardGrid> or with checkboxes <KCardGrid><KCard><template #checkbox><KCheckbox></template></KCardGrid>.
  • (3) Loading skeletons
  • (4) Failing tests
    • Expected failures that do not affect users, however point to (2)
  • (5) Few new tests for new tricky places
  • (6) Documentation pages
  • (7) Performance testing and optimizations
  • (8) RTL
  • (9) Dev warning if KCard not rendered withing KCardGrid

References

Usage

Base grid layouts

KCardGrid offers two base layouts, '1-2' and '1-2-3', where '1-2' is the default. These are currently the only multi-column layouts needed.

Besides the number of columns explained below, they set column and row gap to 30px, and ensure the default row height behavior when it automatically adjusts to cards' content (do not miss the "Note on row height" below)

'1-2' layout

<KCardGrid layout="1-2">
  <KCardGridItem v-for="i in 8" >
    <KCard />
 </KCardGridItem>
</KCardGrid>

renders a grid with 1 column on smaller screens, and 2 columns on medium and larger screens. Specifically

Level 0 Level 1 Level 2 Level 3 Level 4 Level 5 Level 6 Level 7
Columns 1 1 2 2 2 2 2 2

'1-2-3' layout

<KCardGrid layout="1-2-3">
  <KCardGridItem v-for="i in 8" >
    <KCard />
 </KCardGridItem>
</KCardGrid>

renders a grid with 1 column on smaller screens, 2 columns on medium screens, and 3 columns on larger screens.

Specifically

Level 0 Level 1 Level 2 Level 3 Level 4 Level 5 Level 6 Level 7
Columns 1 1 2 2 3 3 3 3

Note on row height

KCardGrid provides sufficiently reasonable default behavior for all KCards even when the row height is not specified. However, to make some of the cards sections' heights follow the designs as closely as possible, KCardGrid's consumers are expected to set the row height explicitly so that calculations can be made more accurately. One example would be the height of the thumbnail area in vertical layouts that can't be precisely calculated unless the row height is an actual value (auto, fit-content, and similar doesn't count) The row height is typically specified in designs, or it can be estimated after providing the card with all information. See "Layout customization" on how to achieve that.

Fixed section heights

KCard's sections will often need to have fixed heights to allow for consistent horizontal alignment of sections on a grid row. This can be achieved by regular CSS styling applied on KCard's slots:

<KCard>
  <template #belowTitle>
    <div :style="{ height: '24px', overflow: 'hidden' }">
      Below title below title below title below
    </div>
  </template>
</KCard>

It is a consumer's task to ensure that slots content can fit the chosen height, for example by text truncation.

Relatedly, if it's expected that some cards will have no content in a section that has content on another cards in the same grid, it is possible to preserve the space occupied by a corresponding slot via KCard's preserve... props.

List of cards with checkboxes

This is a common case of more advanced grid configurations that are demonstrated below. For now, just know that you can use columns to override the number of columns of the base grid layouts on their all breakpoints levels.

<KCardGrid :columns="1">
  <KCardGridItem v-for="i in 8">
    <template #checkbox>
      <KCheckbox />
    </template>
    <KCard />
  </KCardGridItem>
</KCardGrid>

Layout customization

We currently do not have other use-cases than above. However some configuration, such as row height, need to be set either for all breakpoints levels, or on a per breakpoint level basis (there is at least one design that requires nuanced level configuration).

Via shortcut props

The following props override the base layout on all breakpoint levels: columns, rowHeight, rowGap, columnGap.

For example

<KCardGrid
  layout="1-2"
  rowGap="50px"
  columnGap="50px"
  rowHeight="450px"
>
  <KCardGridItem v-for="i in 8" >
    <KCard />
 </KCardGridItem>
</KCardGrid>

uses all configurations of the base '1-2' layout, however row and column gap is overriden from 30px to 50px, and row height is set to 450px on all breakpoint levels.

These props are the same as level attributes of the layoutConfig that offer a simple way to override configuration on all breakpoints.

Via layoutConfig prop

Finally, it is possible to override chosen values of the base grid layouts on chosen breakpoint levels. This can be used to override only some parts of the base layout or to define a completely new layout.

The grid component is built to be easily configurable internally with the same layout configuration object. As an additional benefit, all these configurations can be exposed publicly. If we notice some patterns emerging in consuming apps over time, they can be simply added as a new base layout.

For example:

<KCardGrid
  layout="1-2"
  :rowHeight="300px" // ensure row height set explicitly on all breakpoints levels (see "Note on row height")
  :layoutConfig="layoutConfig"
>
  <KCardGridItem v-for="i in 8" >
    <KCard />
 </KCardGridItem>
</KCardGrid>

where layoutConfig is

{
  'level-0': {
    columns: 2
  },
  'level-1': {
    columns: 2,
    columnGap: '20px',
    rowGap: '20px'
  },
  'level-7': {
    rowHeight: '600px'
  }
}

overrides the number of columns on the breakpoint levels 0 and 1, the column and row gaps on the breakpoint level 1, and the row height on the breakpoint level 7.

Available levels: 'level-0', 'level-1',..., 'level-7'.
Available level configuration: columns, columnGap, rowGap, rowHeight.

to have a suitable location
for playground sub-components
when testing complex scenarios
and integrate with KCard
Helps to preserve auto-generated props documentation
format as intended in the source docstring.
Particularly useful when documenting larger objects
taking more lines in a prop docstrings.
Some work remains, especially when there
are no thumbnails.
in the horizontal layout with small thumbnail.

Fixes the thumbnail overflowing
and aligns this layout more closely
to the designs.
To be able to complete this in a11y way,
KCheckbox will need to be updated to allow
for more flexible label. Due to how KCheckbox
is implemented it seems that currently there
is no way how to set the card title as its checkbox
label.

There may be some more a11y requirements needed,
to be discussed.
@rtibbles
Copy link
Member

My first reaction to the spec is that adding the KCardGridItem seems to add a lot of boilerplate to using KCardGrid - if a KCard is inside a KCardGrid it should just act accordingly, requiring it to be wrapped in an additional component that doesn't have an API of its own seems like extra boilerplate for a developer.

My second is that having the low level API in pixels is useful sometimes, but that it might be useful to have a higher level API, similar to what we have done in Kolibri where we specify the dimensions of the grid in cards, rather than pixels: https://github.com/learningequality/kolibri/blob/2faa5ea3f0abb5bbaa180d184935a5e9e462d44b/kolibri/plugins/learn/assets/src/views/ExploreLibrariesPage/index.vue#L134

Having some utilities like this as a composable that works with the grid will be important to ensure that we show the correct number of cards for a specific layout (without having a 3 in one row, then 1 on the next overhang, for example).

@MisRob
Copy link
Member Author

MisRob commented Aug 28, 2024

@rtibbles Thanks for looking at it, Richard.

My first reaction to the spec is that adding the KCardGridItem seems to add a lot of boilerplate to using KCardGrid - if a KCard is inside a KCardGrid it should just act accordingly, requiring it to be wrapped in an additional component that doesn't have an API of its own seems like extra boilerplate for a developer.

👍

The primary reason for KCardGridItem were some a11y nuances in combination with a technical limitation caused by the decision I made early in writing the specification where I wasn't aware of the checkboxes use-case. I'd like to suggest an internal update that should allow KCardGridItem to be removed, just want to confirm with developers involved in this work.

My second is that having the low level API in pixels is useful sometimes, but that it might be useful to have a higher level API, similar to what we have done in Kolibri where we specify the dimensions of the grid in cards, rather than pixels: https://github.com/learningequality/kolibri/blob/2faa5ea3f0abb5bbaa180d184935a5e9e462d44b/kolibri/plugins/learn/assets/src/views/ExploreLibrariesPage/index.vue#L134

I don't quite understand yet - what pixels do you mean here exactly?

Card's width is not specified in pixels, we just tell KCardGrid what's the number of columns via either those two default layouts, or alternatively via columns prop or columns attribute in the layoutConfig. KCardGrid then calculates the card's width.

Row's height and row/column gaps are specified in pixels because that seems to be a pretty clear design requirement (designs).

we show the correct number of cards for a specific layout (without having a 3 in one row, then 1 on the next overhang, for example)

I think I will need to understand this better too because in the new designs for grids we should always show a specific number of columns on a given breakpoint, and 'one in the next overhang' is commonly present there - so the emphasis is on consistency of columns number on certain breakpoints.

Regarding integration makeComputedCardCount(1, 3) that should work it seems - we can just set columns to that number. I don't mind considering some utilities in the form of the Composition API at all, I just don't yet get what specifically would need to happen and what are the use-cases to target for. Some concrete examples would help.

@MisRob
Copy link
Member Author

MisRob commented Aug 28, 2024

@rtibbles I'd like to have those questions clarified, but wanted to mention that generally I can see a point having a composable as an alternative approach to the layoutConfig where we'd instead setColumns(<number of columns>, <for level>). This could feel more ergonomic and open to adding new utilities.

I think there'd be a bit more of internal complexity around it that would need to be put into place for situations when there's more KCardGrids in one component that calls this composable - we'd need to save configurations for each of them, therefore there'd be a need for some grid id. Also, a composable will be one more import. I don't think these are big deals at all - just something to consider on this opportunity. If it seems that the composable is a preferred way to configure the grid, I think it could be interesting. Considered it myself as a composition API fan :), just starting simple in the first version.

@MisRob
Copy link
Member Author

MisRob commented Sep 4, 2024

So I found some time to think through the feedback and conversations in calls, and I think in the next step would be:

  • Remove KCardGrid (cc @AlexVelezLl @AllanOXDi)
  • Add useCardGrid composable and use its functions instead of layoutConfig (cc @rtibbles) I also realized this will help with skeleton loaders configuration - without it, we'd need another config object.

@rtibbles I don't yet have the exact idea about the useCardGrid utilities since I don't understand all the details of use-cases mentioned in #748 (comment), but I think I can understand the direction at least and create some. If you had something more concrete in mind, It'd be nice to have all feedback by the end of this week - planning to make these updates next week.

Thanks all, hopefully the second version will turn out nicely thanks to the above :)

@MisRob MisRob added the WIP label Sep 4, 2024
@MisRob
Copy link
Member Author

MisRob commented Sep 4, 2024

@AllanOXDi it's not ready for review yet - there will be lots of code changes. I will make sure to invite all people involved in cards as soon as it's time to move it out of the draft stage.

@rtibbles
Copy link
Member

rtibbles commented Sep 4, 2024

I don't quite understand yet - what pixels do you mean here exactly?

Card's width is not specified in pixels, we just tell KCardGrid what's the number of columns via either those two default layouts, or alternatively via columns prop or columns attribute in the layoutConfig. KCardGrid then calculates the card's width.

Row's height and row/column gaps are specified in pixels because that seems to be a pretty clear design requirement (designs).

The width of the grid was the main thing I was concerned with here (and maybe I misread the spec) - but I recall us having a lot of issues with having variable width cards within a breakpoint (min-width and max-width behaviours defined) but then a fixed width grid as a whole. So, I guess the main takeaway here is that if we are defining the size of the cards, we should only define the number of columns we want, and not the exact width of the grid.

I think I will need to understand this better too because in the new designs for grids we should always show a specific number of columns on a given breakpoint, and 'one in the next overhang' is commonly present there - so the emphasis is on consistency of columns number on certain breakpoints.

If that's the case, that's a big flaw in the design - it causes a huge amount of wasted visual space, especially on smaller screens where it is most needed. Nothing I am proposing involves any changes to consistency in number of columns, just an emphasis on showing full rows where ever possible, to avoid wasting screen real estate.

@MisRob
Copy link
Member Author

MisRob commented Sep 4, 2024

@rtibbles thanks a lot, I think I can understand better what you were trying to say.

So, I guess the main takeaway here is that if we are defining the size of the cards, we should only define the number of columns we want, and not the exact width of the grid.

Ah okay, I think that makes sense and should be satisfied. The mechanism now is that you put in the number of columns, KCardGrid will then take the available space and divide it by that number (plus reserves some space for gaps).

Nothing I am proposing involves any changes to consistency in number of columns, just an emphasis on showing full rows where ever possible, to avoid wasting screen real estate.

Ah so basically you mean that when we have two columns, that is two cards are displayed in one row, then those cards should occupy that space fully, right (versus having fixed max height and leaving blank space in the other half of the row)?

I think that's what is suggested on designs. I interpreted your first note differently and though you might be suggesting we change number of columns in such a case.

@MisRob
Copy link
Member Author

MisRob commented Sep 4, 2024

@rtibbles Technically the cards don't have any width settings on themselves, they always take all the available width that is given to them by the grid. We can still control their min width and max width, but that would rather be by limiting the space available for grid and configuring the number of columns. I will wait for the confirmation that this behavior is aligned well with your thinking about overcoming the trouble you mentioned.

This discussion makes me think I should mention this in the doc, because typically the first thought would be to set min and max height directly on cards, which we're trying to prevent.

@MisRob
Copy link
Member Author

MisRob commented Sep 4, 2024

@rtibbles that said, this can still happen, but in the new designs the cards are bigger overall and space seems reasonably distributed to me:

Screenshot from 2024-09-04 17-18-47

Screenshot from 2024-09-04 17-29-09

If you see some improvements to do here, we can discuss with designers.

Technically, I think I have a good idea about possible behaviors and can adjust the way we configure it to be flexible enough to allow to tweak these things :)

@MisRob MisRob changed the title [WIP, DRAFT API PROPOSAL] Card grid and checkboxes [BETA] Card grid and checkboxes Sep 6, 2024
@marcellamaki
Copy link
Member

I think the key takeaway @rtibbles was suggesting is that we work to have a number of cards by default that corresponds to the number of columns. There was quite a lot of effort over the last year to not have "remainder" if not needed, and to have the default number of items == the number of columns on each view, with it changing dynamically. For example, in Kolibri currently we show 3 items by default on a 3-col row. but the same display, on a tablet screen, would have 4 items displayed, in 2 rows, 2 columns. The only time the overhanging item should appear is if it is the last item of the total (i.e. you get to the end of "show more" with a single item remaining to go in the row). Or, if you are looking at a folder that has only one item in it, etc. But the default display should otherwise be matching the number of items to the number of columns.

I am not totally clear whether this is the responsibility of the grid managing that dynamically, or if it really has to do with the places that we make use of it. It seems potentially more useful to have the grid manage this itself, but also possibly more complicated. We can discuss more and I think it would be fine to do this in follow up if needed. As long as we make sure we're maintaining that behavior when we're using the new grid for the kolibri-common components for 0.18, I think Richard and I will both be happy :) Just want to be sure that isn't unintentionally lost in the course of this refactoring

@MisRob
Copy link
Member Author

MisRob commented Sep 10, 2024

@marcellamaki

For example, in Kolibri currently we show 3 items by default on a 3-col row. but the same display, on a tablet screen, would have 4 items displayed, in 2 rows, 2 columns. The only time the overhanging item should appear is if it is the last item of the total (i.e. you get to the end of "show more" with a single item remaining to go in the row).

I think it's how it works already, but I am not sure if I haven't missed something else. I didn't really consider this explicitly but rather worked from thinking through the designs linked and all sorts of things they imply. Would you have a look at "Default '1-2-3' grid, vertical cards, small thumbnail" and "Layout configuration overriden via props" sections in the playground https://deploy-preview-748--kolibri-design-system.netlify.app/playground/ and confirmed this is what you mean? There you can see the number of columns changing dynamically. For each breakpoint, we are able to specify the number of columns/cards. Is this what we need or is there more to it?

In KCardGrid, when I talk about the number of columns - that is equivalent to the number of cards, basically. In this regard it is different from KGrid and KFixedGrid.

@MisRob
Copy link
Member Author

MisRob commented Sep 10, 2024

@marcellamaki You can also see code for the playground here https://github.com/learningequality/kolibri-design-system/pull/748/files#diff-8d9f8ad2e4bcdbda8e50460f2774ee0b97a52274cd1870a9842e52dba7f6676f if it helps to evaluate if this approach resolves what you're trying to say.

I think in the code that @rtibbles referenced earlier, the computation was about determining span for KGrid/KFixedGrid so after reading your comment I think I am starting to connect dots on where was it coming from. It's a different approach - here we're not setting span on columns, these two concepts don't apply. In KCardGrid we're rather simply saying "on this breakpoint, display this number of cards (=columns) on one row", and that's it. Then there are some most common default layouts available, but with the option to override - again via configuring the number of cards for a given breakpoint.

@MisRob
Copy link
Member Author

MisRob commented Sep 10, 2024

@marcellamaki

I am not totally clear whether this is the responsibility of the grid managing that dynamically, or if it really has to do with the places that we make use of it. It seems potentially more useful to have the grid manage this itself, but also possibly more complicated.

From the designs, this differs place to place, but there are only three repeating patterns. So the grid manages it for most common patterns but is still configurable to allow for override. After you've previewed the playground page and code, let me know if this suffices the scenarios you were thinking of.

Further improvement could be to not base this on the window breakpoint but operate based on the space available in a given element - but yeah this would be better to discuss further and have designs for if it that's something we'd want :)

@MisRob
Copy link
Member Author

MisRob commented Sep 10, 2024

@marcellamaki Best to play around with the provided live examples first, and then no problem to chat on a call if it seems we're still not quite aligned :) I think the confusion is clearer now since we were talking from different assumptions about grids, I believe. Let me know what works best. Thanks for offering the follow-up, I'd rather to take care of this sooner than leaving for later since these functionalities, together with a11y, is the main point behind having KCardGrid so better to clarify if we can.

@@ -16,7 +16,6 @@ code {
padding-left: 0.25em;
font-family: 'Source Code Pro', monospace;
font-weight: 400;
white-space: nowrap;
Copy link
Member

Choose a reason for hiding this comment

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

I might be imagining this, but I feel as though I've seen this change in a few PRs lately. I am not sure if/how you would know this, but.... does that seem familiar to you? do you know what's going on with this change?

Copy link
Member Author

Choose a reason for hiding this comment

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

Here's details in the commit message :) f0a1dad

Visually, this change allows preserving nice format for the layoutConfig example here
Screenshot from 2024-09-13 17-47-19

I don't know why it was there originally though, but haven't noticed any issues yet after removing.

Copy link
Member Author

Choose a reason for hiding this comment

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

If I recall, it would show on one line otherwise, which is not readable (we can still achieve one line format by formatting the source docstring accordingly - so this is about more precise transfer of the source string to auto-generated docs)

@MisRob
Copy link
Member Author

MisRob commented Sep 16, 2024

Leaving a note here that with @marcellamaki we confirmed that the grid behavior regarding columns overall satisfies what was being discussed above.

Latest update is that I already transitioned lots of work on base layouts to production version and have another PR to develop nearly ready. Due to that, it is alright to still provide feedback here - however, most likely it'd be logged as future follow-up unless something blocking shows up or it's low-hanging fruit.

@MisRob
Copy link
Member Author

MisRob commented Sep 19, 2024

@rtibbles @AlexVelezLl

I experimented with implementing useKCardGrid for advanced customization of KCardGrid's layout, and due to having to have the grid configured as soon as possible, Composition API approach is becoming so complex I am starting to think it may be better to not use it, unless we figure out some ways to work around the following:

Experiment no.1

<template>
  <KCardGrid gridId='grid-1' layout='1-2-2'>
</template>

<script>
  setup() {
    // overrides the base layout to show 3 cards per row on breakpoints 4,5,6,7
    onMounted(() => {
      setCardsPerRow({ gridId: 'grid-1', cards: 3, breakpoints: [4, 5, 6, 7]}) }
      setColumnGap({ gridId: 'grid-1', columnGap: '50px', breakpoints: [0, 1, 2]}) }
    )
  }
</script>

would result in switching layout on the fly, something I want to definitely as it has negative impact on user experience. So here we call it sooner:

<template>
  <KCardGrid gridId='grid-1' layout='1-2-2'>
</template>

<script>
  setup() {
    setCardsPerRow({ gridId: 'grid-1', baseLayout: '1-2-2', cards: 3, breakpoints: [4, 5, 6, 7]}) }
    setColumnGap({ gridId: 'grid-1', baseLayout: '1-2-2', columnGap: '50px', breakpoints: [0, 1, 2]}) }
  }
</script>

But we'd need to add another information about what's KCardGrid 's base layout because it's not mounted yet so composable can't figure it out internally.

In such approaches we'd also need to add gridId to make sure it all works well when there's more grids on a page, and have more complex implementation on the KDS side that stores all grid configs. You can see this commit to get a sense of all this.

I quite like this API and so far was finding ways to work around these things, but taking into account (1) increasing complexity of public API and struggle with internal implementation, (2) the need for overrides being presumably rare (we don't yet even have use-cases at this point), and (3) I didn't even get to skeletons that will need to have information sooner than as soon as possible :) I lean towards not using Composition API with the purpose of initial configuration and just go with layoutConfig prop (better renamed to layoutOverride) in the proposal.

Sounds good or any other thoughts?

@AlexVelezLl
Copy link
Member

Hi @MisRob! Hmm the first thought I had when I readed the composables idea was to have this as a composable that will be used internally, inside KCardGrid, and making this a composable to be able to reuse this at some point if needed (with skeletons maybe?).

I agree that making this composable part of the public interface of KCardGrid make both the public API and the internal implementation much more complex. My vote will almost always be the path of an easy to use public API unless we have an specific use case where we need to have this more complex api, but I think for now thats not the case.

So in my opinion, I think we could still use composables for some of these calculations like the makeComputedCardCount richard suggested, but I think it would be better to use these internally.

@MisRob
Copy link
Member Author

MisRob commented Sep 19, 2024

Yes, the internal composable is there and will stay. This is a question of public API exploration, rather.

I should have also mentioned that there is whole array of better lifecycle hooks for this purpose, using mounted and setup was to demonstrate opposite poles. That said, execution in setup is fastest.

Side note, 'makeComputedCardCount' won't be needed as card grid handles this already by means of its design, as far as I can say from conversation so far. However if we needed different ways to configure this, we could still hook it to 'layoutOverride'.

@MisRob
Copy link
Member Author

MisRob commented Sep 19, 2024

Reference for the simpler approach I just hacked quickly 61e95ea

@MisRob MisRob closed this in #785 Oct 1, 2024
@MisRob
Copy link
Member Author

MisRob commented Oct 1, 2024

Closed in favor of #785

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
5 participants