feat(icons): add category filtering and mobile category sheet - #739
feat(icons): add category filtering and mobile category sheet#739lain9293 wants to merge 12 commits into
Conversation
| })} | ||
| onClick={() => handleSelectCategory('all')} | ||
| > | ||
| <span>All icons</span> |
| contentClassName={b('category-sheet-content')} | ||
| visible={isCategorySheetOpen} | ||
| onClose={() => setIsCategorySheetOpen(false)} | ||
| title="Category" |
| ); | ||
|
|
||
| const selectedCategory = iconCategories.find(({id}) => id === categoryId); | ||
| const resultsTitle = isSearching ? 'All icons' : selectedCategory?.label ?? 'All icons'; |
| !isSearching && selectedCategory | ||
| ? t(`icons:categories.${selectedCategory.id}`) | ||
| : allIconsTitle; | ||
| const resultsCount = isSearching ? allIcons.length : icons.length; |
| /> | ||
|
|
||
| {isMobile && ( | ||
| <Sheet |
There was a problem hiding this comment.
Lets add allowHideOnContentScroll={false}, so sheet is not closing on touch from top to bottom on touch devices
vvtimofeev
left a comment
There was a problem hiding this comment.
Ran this branch locally and checked the layout with Playwright across a range of viewports. The feature works as described on desktop — labels, counts and the disabled-while-searching state are fine, RU/EN translations resolve, typecheck/eslint/stylelint/madge are clean — but a few layout issues show up that CI can't catch (there is no e2e coverage for /icons):
- The fixed 150px grid collapses to a single column at 360px, and to two columns at 769–816px next to the sidebar.
- The sticky category sidebar is sized with
100vhinside a scroll container that is 81px shorter, so its last rows ("Food") are unreachable on screens under ~1300px tall. - Switching to a smaller category deep in the page leaves the user at the footer.
- The tile hover colour sets the wrong CSS variable, so hover shows a translucent grey instead of
#3c2e3a. - The sheet re-opens by itself after crossing the 768px breakpoint while open, and the inner scroller in the sheet is what made
allowHideOnContentScroll={false}necessary.
Details and suggested fixes are inline. The rest are smaller cleanups (Sheet styling hooks, duplicated button markup, the hardcoded category list, memoization, one RU label).
🤖 Review assisted by Claude Code; measurements are from a local Playwright run against this branch.
| justify-content: center; | ||
| gap: pcVariables.$indentXS; | ||
| display: grid; | ||
| grid-template-columns: repeat(auto-fill, 150px); |
There was a problem hiding this comment.
repeat(auto-fill, 150px) has no minmax() and no mobile override, so the number of tiles per row collapses on narrow content boxes. Measured on this branch (Playwright):
| viewport | tiles per row | empty space on the right |
|---|---|---|
| 320px | 1 | 122px |
| 360px (most common Android width) | 1 | 162px |
| 375–430px | 2 | 7–62px |
| 578px | 2 | 162px |
| 769–816px (desktop layout, sidebar shown) | 2 | 121–168px |
| 1280px | 5 | 122px |
At 360px "All icons" becomes a single column of 799 rows. The previous 62px flex-wrap layout filled any width and centered the rows. Something like repeat(auto-fill, minmax(…, 1fr)) / auto-fit, or a smaller tile below md, would avoid this.
| position: sticky; | ||
| top: 76px; | ||
| flex: 0 0 200px; | ||
| max-height: calc(100vh - 96px); |
There was a problem hiding this comment.
The page doesn't scroll the window — it scrolls the OverlayScrollbars viewport below the sticky menu (81px tall), so 100vh overshoots the scrollport by the menu height. Measured at 1280×800, 1440×900 and 1920×1080: after scrolling, the aside sticks at y=157 and its bottom ends up 61px below the scrollport bottom. Its content is 1052px (28 rows) so it scrolls internally, but the last ~1.7 rows can never be fully shown — at 800px tall, "Food" sits at 825–861px and is unreachable. It only fits at viewport heights ≥ ~1300px.
top: 76px / 96px also hardcode the sticky search row height (8 + 44 + 8). Suggest deriving both from one variable and subtracting the header height (or sizing relative to the scroll container instead of 100vh).
| : allIconsTitle; | ||
| const resultsCount = icons.length; | ||
|
|
||
| const handleSelectCategory = React.useCallback((nextCategoryId: string) => { |
There was a problem hiding this comment.
Switching categories doesn't re-anchor the scroll position. When the list shrinks from ~27,000px ("All icons") to a few rows, the scroll container clamps scrollTop and the user lands on the footer. Reproduced at 1280×800: scroll 12,000px down, click "Weather" in the sticky sidebar → scrollTop clamps to 350, the "Weather" heading is at −57px and the footer is on screen. Same on mobile after picking in the sheet (the trigger showing the new category name ends up off-screen).
handleClickToKeyword already does pageTitleRef.current?.scrollIntoView(...); the same is needed here (e.g. scroll to the results section).
|
|
||
| &:hover { | ||
| --g-button-background-color: rgba(255, 255, 255, 0.16); | ||
| --g-button-background-color: #3c2e3a; |
There was a problem hiding this comment.
This never applies: uikit paints the hover state from --g-button-background-color-hover (.g-button:hover::before { background-color: var(--g-button-background-color-hover, var(--_--background-color-hover)) }), not from --g-button-background-color. Measured: the hovered tile's ::before background is rgba(255, 255, 255, 0.1) (the flat-view fallback), so on hover the solid #281a26 fill is replaced by a translucent grey instead of #3c2e3a. The same mistake existed before this PR, but with a transparent base it was invisible.
| --g-button-background-color: #3c2e3a; | |
| --g-button-background-color-hover: #3c2e3a; |
Cleaner still: put --g-button-background-color-hover in the &.g-button block next to --g-button-background-color and drop the &:hover block (its specificity is also below the :has() selector above).
| } | ||
|
|
||
| &__category-sheet-list { | ||
| max-height: calc(100vh - 168px); |
There was a problem hiding this comment.
This adds a second scroll container inside the Sheet, which already scrolls its own .g-sheet__sheet-scroll-container (capped at 0.9 × innerHeight − 20). The magic 168 (= 0.1·844 + 20 + 48 + 16) only matches a 390×844 viewport. Measured outer overflow: 412×892 → 5px, 430×932 → 9px, 768×1024 (iPad portrait, still the mobile layout) → 18px — after the inner list ends, scrolling chains to the outer container and the "Category" title jitters under the top bar. On iOS Safari (100vh = 844 while innerHeight = 664 with the toolbars) it becomes two full scroll layers.
It's also why allowHideOnContentScroll={false} became necessary: with the inner scroller the Sheet's own scrollTop stays 0, so any downward drag on the list was treated as a dismiss gesture. Dropping max-height/overflow-y here (and the prop) lets the Sheet scroll natively and restores pull-to-close; maxContentHeightCoefficient is available if the default height is too tall.
|
|
||
| const categoryOptions = ( | ||
| <React.Fragment> | ||
| <button |
There was a problem hiding this comment.
This button is a copy of the mapped one below with 'all' in place of category.id, and !isSearching && categoryId === X is spelled out four times (plus the ternaries at lines 150 and 159). Something like
const activeCategoryId = isSearching ? 'all' : categoryId;and a single ['all', ...ids].map(...) (with counts.all = allIcons.length) removes the duplication; selectedCategory (a find() whose only use is tautological), allIconsTitle and resultsCount then go away too.
Minor, same PR: gap: 24px under display: block (Icons.scss:235) and width: 100% on the block-level <section> (Icons.scss:277) are no-ops.
| margin-top: 20px; | ||
| } | ||
|
|
||
| &__category { |
There was a problem hiding this comment.
The list re-implements hover/selected/disabled with literal colours (rgba(255,255,255,0.06), rgba(255,190,92,0.08)), while uikit Button has selected/disabled on theme tokens (--g-color-base-selection is already overridden globally in styles.scss) — Tags.tsx uses exactly that "All + filters" pattern, and NavigationLayout/SectionBlock.scss is the existing left-column list (active rgba(255,190,92,0.1)). Also, src/mixins.scss has window-breakpoint('md') for the md - 1 media query, and the spacing literals (32/20/16/12px) sit next to pcVariables.$indent* used elsewhere in this file. If the Figma look needs custom styling, tokens/variables would at least keep it in sync with the rest of the site.
| <span className={b('results-count')}>{resultsCount}</span> | ||
| </div> | ||
| {icons.length ? ( | ||
| <IconCollection icons={icons} onSelectIcon={handleSelectIcon} /> |
There was a problem hiding this comment.
IconCollection/IconButton aren't memoized, so every sheet open/close (and, pre-existing, every icon dialog open/close) re-renders all 799 uikit Button + Icon trees even though icons and handleSelectIcon are referentially stable — and the Sheet only appears after that pass. A same-shape tree measured 1.4–6.4ms per toggle on a fast desktop vs 0.1–0.3ms with React.memo; on a mid-range phone that's roughly 10–30ms of blocking per tap. export const IconCollection = React.memo(...) is enough (or keep the sheet state in a small child component).
| const resultsCount = icons.length; | ||
|
|
||
| const handleSelectCategory = React.useCallback((nextCategoryId: string) => { | ||
| setCategoryId(nextCategoryId); |
There was a problem hiding this comment.
Nit: no analytics on category selection. The page already sends icon_download via sendAnalyticsEvent, and #747 just instrumented the theme gallery filters — one icon_category_select event with the category id (and the source: sidebar/sheet) would make it possible to see which categories actually get used.
vvtimofeev
left a comment
There was a problem hiding this comment.
Requesting changes for the layout issues from my review above — mainly the single-column grid at 360px (IconCollection.scss:7), the sticky sidebar clipped inside the scroll container so "Food" is unreachable (Icons.scss:118), the scroll position not being restored on category change (Icons.tsx:164), the hover colour set on the wrong CSS variable (IconButton.scss:19), and the sheet re-opening on its own / nested scroller (Icons.tsx:310, Icons.scss:216). Details and suggested fixes are in the inline comments; the rest are non-blocking cleanups.
Co-authored-by: vvtimofeev <108340247+vvtimofeev@users.noreply.github.com>
0cdde40

Description
Adds category-based navigation to the Icons page.
Changes
@gravity-ui/icons.@gravity-ui/iconsto2.22.0.Testing