fix(recipe-editor): allow decimal input for quantities and percentages; fix workspace filter alignment and mobile bottom-sheet modal
This commit is contained in:
@@ -214,10 +214,10 @@ const tabs:Array<{type:string;label:string;count:number;kind:"recipe"|"ingredien
|
|||||||
{type:"inventory",label:"Inventory",count:inventoryCounts?.c ?? 0,kind:"inventory",href:"/app/inventory/"},
|
{type:"inventory",label:"Inventory",count:inventoryCounts?.c ?? 0,kind:"inventory",href:"/app/inventory/"},
|
||||||
];
|
];
|
||||||
const allSearchResults=normalizedQuery ? [
|
const allSearchResults=normalizedQuery ? [
|
||||||
...recipes.filter((item)=>`${item.title} ${item.id}`.toLocaleLowerCase().includes(normalizedQuery) && (selectedTags.length === 0 || parseItemTags(item.tags_json, item.categories_json).some(t => selectedTags.includes(t))) && (selectedIngredients.length === 0 || (recipeIngredientsMap.get(item.id) ?? new Set()).some(id => selectedIngredients.includes(id)))).map((item)=>({id:item.id,kind:"recipe" as const,label:"Recipe",name:item.title,detail:`${item.yield_quantity} ${item.yield_unit_id}`,href:`/app/recipes/${item.id}/`})),
|
...recipes.filter((item)=>`${item.title} ${item.id}`.toLocaleLowerCase().includes(normalizedQuery) && (selectedTags.length === 0 || parseItemTags(item.tags_json, item.categories_json).some(t => selectedTags.includes(t))) && (selectedIngredients.length === 0 || Array.from(recipeIngredientsMap.get(item.id) ?? []).some((id: string) => selectedIngredients.includes(id)))).map((item)=>({id:item.id,kind:"recipe" as const,label:"Recipe",name:item.title,detail:`${item.yield_quantity} ${item.yield_unit_id}`,href:`/app/recipes/${item.id}/`})),
|
||||||
...ingredients.filter((item)=>`${item.name} ${item.id}`.toLocaleLowerCase().includes(normalizedQuery) && (selectedTags.length === 0 || parseItemTags(item.tags_json, item.categories_json).some(t => selectedTags.includes(t)))).map((item)=>({id:item.id,kind:"ingredient" as const,label:"Ingredient",name:titleCase(item.name),detail:`Used in ${item.recipe_count} ${item.recipe_count===1?"recipe":"recipes"}`,href:`/app/ingredients/${item.id}/`})),
|
...ingredients.filter((item)=>`${item.name} ${item.id}`.toLocaleLowerCase().includes(normalizedQuery) && (selectedTags.length === 0 || parseItemTags(item.tags_json, item.categories_json).some(t => selectedTags.includes(t)))).map((item)=>({id:item.id,kind:"ingredient" as const,label:"Ingredient",name:titleCase(item.name),detail:`Used in ${item.recipe_count} ${item.recipe_count===1?"recipe":"recipes"}`,href:`/app/ingredients/${item.id}/`})),
|
||||||
...books.filter((item)=>`${item.name} ${item.description??""} ${item.id}`.toLocaleLowerCase().includes(normalizedQuery) && selectedTags.length === 0).map((item)=>({id:item.id,kind:"book" as const,label:"Recipe book",name:item.name,detail:`${item.recipe_count} ${item.recipe_count===1?"recipe":"recipes"}`,href:`/app/recipe-books/${item.id}/`})),
|
...books.filter((item)=>`${item.name} ${item.description??""} ${item.id}`.toLocaleLowerCase().includes(normalizedQuery) && selectedTags.length === 0).map((item)=>({id:item.id,kind:"book" as const,label:"Recipe book",name:item.name,detail:`${item.recipe_count} ${item.recipe_count===1?"recipe":"recipes"}`,href:`/app/recipe-books/${item.id}/`})),
|
||||||
...purchases.filter((item)=>`${item.name} ${item.ingredient_name} ${item.supplier_id??""} ${item.id}`.toLocaleLowerCase().includes(normalizedQuery) && selectedTags.length === 0).map((item)=>({id:item.id,kind:"purchase" as const,label:"Purchase item",name:item.name,detail:titleCase(item.ingredient_name),href:`/app/ingredients/${item.ingredient_id}/#costs`})),
|
...purchases.filter((item)=>`${item.name} ${item.ingredient_name} ${item.supplier_id??""} ${item.id}`.toLocaleLowerCase().includes(normalizedQuery) && selectedTags.length === 0).map((item)=>({id:item.id,kind:"purchase" as const,label:"Purchase item",name:item.name,detail:titleCase(item.ingredient_name),href:`/app/ingredients/${item.ingredient_id}/#costs`})),
|
||||||
].sort((a,b)=>a.name.localeCompare(b.name)):[];
|
].sort((a,b)=>a.name.localeCompare(b.name)):[];
|
||||||
const searchResults=filteringSearchTypes?allSearchResults.filter((result)=>selectedSearchTypes.includes(result.kind)):allSearchResults;
|
const searchResults=filteringSearchTypes?allSearchResults.filter((result)=>selectedSearchTypes.includes(result.kind)):allSearchResults;
|
||||||
const searchRows=searchResults.map(({id,kind,name,href,label,detail})=>({id,name,href,kind,detail:`${label} · ${detail}`}));
|
const searchRows=searchResults.map(({id,kind,name,href,label,detail})=>({id,name,href,kind,detail:`${label} · ${detail}`}));
|
||||||
@@ -255,7 +255,7 @@ const purchaseRows=purchases.map(item=>({id:item.id,name:item.name,href:`/app/in
|
|||||||
{tabs.map((tab)=><a class:list={{active:type===tab.type}} href={tab.href ?? `/app/?type=${tab.type}`}><span class={`workspace-pill-icon ${tab.kind}`}><svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d={TYPE_ICONS[tab.kind]} style={TYPE_ICON_TRANSFORMS[tab.kind]?{transform:TYPE_ICON_TRANSFORMS[tab.kind]}:undefined}/></svg></span><span>{tab.label}</span><small>{tab.count}</small></a>)}
|
{tabs.map((tab)=><a class:list={{active:type===tab.type}} href={tab.href ?? `/app/?type=${tab.type}`}><span class={`workspace-pill-icon ${tab.kind}`}><svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d={TYPE_ICONS[tab.kind]} style={TYPE_ICON_TRANSFORMS[tab.kind]?{transform:TYPE_ICON_TRANSFORMS[tab.kind]}:undefined}/></svg></span><span>{tab.label}</span><small>{tab.count}</small></a>)}
|
||||||
<WorkspaceFilterBar
|
<WorkspaceFilterBar
|
||||||
client:load
|
client:load
|
||||||
type={type}
|
type={type ?? undefined}
|
||||||
query={query}
|
query={query}
|
||||||
recipeCounts={recipeAttentionCounts}
|
recipeCounts={recipeAttentionCounts}
|
||||||
ingredientCounts={ingredientAttentionCounts}
|
ingredientCounts={ingredientAttentionCounts}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect, useRef } from "preact/hooks";
|
import { useState, useEffect, useRef } from "preact/hooks";
|
||||||
|
import { createPortal } from "preact/compat";
|
||||||
|
|
||||||
export interface FilterOption {
|
export interface FilterOption {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -72,19 +73,26 @@ export default function WorkspaceFilterBar({
|
|||||||
const [openChipId, setOpenChipId] = useState<string | null>(null);
|
const [openChipId, setOpenChipId] = useState<string | null>(null);
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
|
||||||
|
// Mobile detection state
|
||||||
|
const [activeMobileCatId, setActiveMobileCatId] = useState<string | null>(null);
|
||||||
|
const [menuAlignRight, setMenuAlignRight] = useState(false);
|
||||||
|
const [chipAlignRight, setChipAlignRight] = useState(false);
|
||||||
|
|
||||||
const menuRef = useRef<HTMLDivElement>(null);
|
const menuRef = useRef<HTMLDivElement>(null);
|
||||||
const popoverRef = useRef<HTMLDivElement>(null);
|
const popoverRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Close menus when clicking outside
|
// Close menus when clicking outside (desktop)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleClickOutside(e: MouseEvent) {
|
function handleClickOutside(e: MouseEvent) {
|
||||||
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
if (window.innerWidth > 980) {
|
||||||
setMenuOpen(false);
|
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
||||||
}
|
setMenuOpen(false);
|
||||||
if (popoverRef.current && !popoverRef.current.contains(e.target as Node)) {
|
}
|
||||||
const target = e.target as HTMLElement;
|
if (popoverRef.current && !popoverRef.current.contains(e.target as Node)) {
|
||||||
if (!target.closest(".filter-rule-chip-main")) {
|
const target = e.target as HTMLElement;
|
||||||
setOpenChipId(null);
|
if (!target.closest(".filter-rule-chip-main")) {
|
||||||
|
setOpenChipId(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,6 +100,15 @@ export default function WorkspaceFilterBar({
|
|||||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleToggleMenu = () => {
|
||||||
|
if (!menuOpen && menuRef.current) {
|
||||||
|
const rect = menuRef.current.getBoundingClientRect();
|
||||||
|
setMenuAlignRight(rect.left + 280 > window.innerWidth - 16);
|
||||||
|
}
|
||||||
|
setActiveMobileCatId(null);
|
||||||
|
setMenuOpen(!menuOpen);
|
||||||
|
};
|
||||||
|
|
||||||
// Parse active filter parameters
|
// Parse active filter parameters
|
||||||
const isRecipe = type === "recipe";
|
const isRecipe = type === "recipe";
|
||||||
const isIngredient = type === "ingredient";
|
const isIngredient = type === "ingredient";
|
||||||
@@ -175,7 +192,7 @@ export default function WorkspaceFilterBar({
|
|||||||
const categoriesWithActiveSelections = categories.filter(c => hasActiveOptions(c));
|
const categoriesWithActiveSelections = categories.filter(c => hasActiveOptions(c));
|
||||||
const totalActiveCount = categoriesWithActiveSelections.length;
|
const totalActiveCount = categoriesWithActiveSelections.length;
|
||||||
|
|
||||||
// The chips visible on screen: categories with active selections + the currently open chip (if user just opened one from the menu)
|
// The chips visible on screen
|
||||||
const visibleCategoryIds = Array.from(
|
const visibleCategoryIds = Array.from(
|
||||||
new Set([
|
new Set([
|
||||||
...categoriesWithActiveSelections.map(c => c.id),
|
...categoriesWithActiveSelections.map(c => c.id),
|
||||||
@@ -188,13 +205,18 @@ export default function WorkspaceFilterBar({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAddCategory = (catId: string) => {
|
const handleAddCategory = (catId: string) => {
|
||||||
setOpenChipId(catId);
|
if (window.innerWidth <= 980) {
|
||||||
setMenuOpen(false);
|
setActiveMobileCatId(catId);
|
||||||
|
} else {
|
||||||
|
setOpenChipId(catId);
|
||||||
|
setMenuOpen(false);
|
||||||
|
}
|
||||||
setSearchTerm("");
|
setSearchTerm("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveCategory = (catId: string) => {
|
const handleRemoveCategory = (catId: string) => {
|
||||||
if (openChipId === catId) setOpenChipId(null);
|
if (openChipId === catId) setOpenChipId(null);
|
||||||
|
if (activeMobileCatId === catId) setActiveMobileCatId(null);
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const cat = categories.find(c => c.id === catId);
|
const cat = categories.find(c => c.id === catId);
|
||||||
@@ -216,6 +238,7 @@ export default function WorkspaceFilterBar({
|
|||||||
const handleClearAll = () => {
|
const handleClearAll = () => {
|
||||||
setOpenChipId(null);
|
setOpenChipId(null);
|
||||||
setMenuOpen(false);
|
setMenuOpen(false);
|
||||||
|
setActiveMobileCatId(null);
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (type) params.set("type", type);
|
if (type) params.set("type", type);
|
||||||
if (query) params.set("q", query);
|
if (query) params.set("q", query);
|
||||||
@@ -258,6 +281,29 @@ export default function WorkspaceFilterBar({
|
|||||||
navigateWithParams(params);
|
navigateWithParams(params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenChip = (catId: string, e?: MouseEvent) => {
|
||||||
|
if (window.innerWidth <= 980) {
|
||||||
|
setActiveMobileCatId(catId);
|
||||||
|
setMenuOpen(true);
|
||||||
|
} else {
|
||||||
|
const isOpening = openChipId !== catId;
|
||||||
|
if (isOpening) {
|
||||||
|
const target = (e?.currentTarget as HTMLElement)?.closest(".filter-rule-item-group");
|
||||||
|
if (target) {
|
||||||
|
const rect = target.getBoundingClientRect();
|
||||||
|
setChipAlignRight(rect.left + 270 > window.innerWidth - 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setOpenChipId(openChipId === catId ? null : catId);
|
||||||
|
}
|
||||||
|
setSearchTerm("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const activeMobileCat = activeMobileCatId ? categories.find(c => c.id === activeMobileCatId) : null;
|
||||||
|
const activeMobileFilteredOptions = activeMobileCat
|
||||||
|
? activeMobileCat.options.filter(opt => opt.name.toLowerCase().includes(searchTerm.toLowerCase()))
|
||||||
|
: [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="workspace-filter-system">
|
<div class="workspace-filter-system">
|
||||||
{/* 1. Main Filter Button Trigger in Pills Row */}
|
{/* 1. Main Filter Button Trigger in Pills Row */}
|
||||||
@@ -265,7 +311,7 @@ export default function WorkspaceFilterBar({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class={`workspace-filter-btn ${totalActiveCount > 0 ? "active" : ""}`}
|
class={`workspace-filter-btn ${totalActiveCount > 0 ? "active" : ""}`}
|
||||||
onClick={() => setMenuOpen(!menuOpen)}
|
onClick={handleToggleMenu}
|
||||||
aria-expanded={menuOpen}
|
aria-expanded={menuOpen}
|
||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
>
|
>
|
||||||
@@ -277,9 +323,9 @@ export default function WorkspaceFilterBar({
|
|||||||
<span>Filter{totalActiveCount > 0 ? ` (${totalActiveCount})` : ""}</span>
|
<span>Filter{totalActiveCount > 0 ? ` (${totalActiveCount})` : ""}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Filter Categories Menu */}
|
{/* Desktop Filter Categories Menu */}
|
||||||
{menuOpen && (
|
{menuOpen && (
|
||||||
<div class="workspace-filter-menu-popover" role="menu">
|
<div class={`workspace-filter-menu-popover desktop-only-filter-popover ${menuAlignRight ? "align-right" : "align-left"}`} role="menu">
|
||||||
<div class="filter-menu-header">
|
<div class="filter-menu-header">
|
||||||
<h4>Filter</h4>
|
<h4>Filter</h4>
|
||||||
</div>
|
</div>
|
||||||
@@ -325,7 +371,153 @@ export default function WorkspaceFilterBar({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 2. Secondary Active Filter Rule Chips Bar */}
|
{/* 2. Mobile Filter & Sort Modal (Meez Parity) */}
|
||||||
|
{menuOpen && typeof document !== "undefined" && createPortal(
|
||||||
|
<div class="mobile-filter-backdrop" role="dialog" aria-modal="true" aria-label="Filter and Sort">
|
||||||
|
<div class="mobile-filter-sheet">
|
||||||
|
<header class="mobile-filter-header">
|
||||||
|
{activeMobileCat ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="mobile-filter-back-btn"
|
||||||
|
onClick={() => {
|
||||||
|
setActiveMobileCatId(null);
|
||||||
|
setSearchTerm("");
|
||||||
|
}}
|
||||||
|
aria-label="Back to categories"
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||||
|
<path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<div class="mobile-filter-header-spacer" />
|
||||||
|
)}
|
||||||
|
<h3>{activeMobileCat ? activeMobileCat.label : "Filter & Sort"}</h3>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="mobile-filter-close-btn"
|
||||||
|
onClick={() => {
|
||||||
|
setMenuOpen(false);
|
||||||
|
setActiveMobileCatId(null);
|
||||||
|
}}
|
||||||
|
aria-label="Close filter"
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||||
|
<path fill="currentColor" d={ICONS.close} />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="mobile-filter-body">
|
||||||
|
{activeMobileCat ? (
|
||||||
|
/* Category Options View */
|
||||||
|
<div class="mobile-filter-category-view">
|
||||||
|
{activeMobileCat.options.length > 0 ? (
|
||||||
|
<>
|
||||||
|
<div class="filter-chip-search">
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||||
|
<path fill="currentColor" d={ICONS.search} />
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
placeholder={`Search ${activeMobileCat.label.toLowerCase()}`}
|
||||||
|
value={searchTerm}
|
||||||
|
onInput={e => setSearchTerm((e.target as HTMLInputElement).value)}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ul class="filter-chip-options-list mobile-options-list">
|
||||||
|
{activeMobileFilteredOptions.length > 0 ? (
|
||||||
|
activeMobileFilteredOptions.map(opt => (
|
||||||
|
<li key={opt.id}>
|
||||||
|
<label class="filter-chip-option-label">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={opt.checked}
|
||||||
|
onChange={() => handleToggleOption(activeMobileCat.id, opt.id, opt.checked)}
|
||||||
|
/>
|
||||||
|
<span class="option-name">{opt.name}</span>
|
||||||
|
{typeof opt.count === "number" && (
|
||||||
|
<span class="option-count">({opt.count})</span>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<li class="filter-chip-empty">No matching options.</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div class="filter-chip-empty-message">
|
||||||
|
{activeMobileCat.emptyMessage || "No additional filter options available."}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
/* Main Filter & Sort Menu */
|
||||||
|
<div class="mobile-filter-main-view">
|
||||||
|
<div class="mobile-filter-section-heading">
|
||||||
|
<svg viewBox="0 0 18 21" aria-hidden="true" class="mobile-section-icon">
|
||||||
|
{ICONS.filter.map((d, i) => (
|
||||||
|
<path key={i} fill="currentColor" d={d} />
|
||||||
|
))}
|
||||||
|
</svg>
|
||||||
|
<span>Filter</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mobile-filter-card">
|
||||||
|
{categories.map(cat => {
|
||||||
|
const isActive = hasActiveOptions(cat);
|
||||||
|
const activeOptsCount = cat.options.filter(o => o.checked).length;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={cat.id}
|
||||||
|
type="button"
|
||||||
|
class={`mobile-filter-card-item ${isActive ? "active" : ""}`}
|
||||||
|
onClick={() => handleAddCategory(cat.id)}
|
||||||
|
>
|
||||||
|
<div class="filter-item-left">
|
||||||
|
<span class="filter-item-icon">
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||||
|
<path fill="currentColor" d={cat.icon} />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span class="filter-item-label">{cat.label}</span>
|
||||||
|
{activeOptsCount > 0 && (
|
||||||
|
<span class="mobile-active-badge">({activeOptsCount})</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span class="mobile-item-chevron" aria-hidden="true">
|
||||||
|
<svg viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M8.59 16.59 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mobile-clear-card">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="mobile-clear-btn"
|
||||||
|
disabled={totalActiveCount === 0}
|
||||||
|
onClick={handleClearAll}
|
||||||
|
>
|
||||||
|
Clear all
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 3. Secondary Active Filter Rule Chips Bar (Horizontal Scroll on Mobile) */}
|
||||||
{visibleCategoryIds.length > 0 && (
|
{visibleCategoryIds.length > 0 && (
|
||||||
<div class="filter-rules-row" role="region" aria-label="Active filters">
|
<div class="filter-rules-row" role="region" aria-label="Active filters">
|
||||||
{visibleCategoryIds.map((catId, index) => {
|
{visibleCategoryIds.map((catId, index) => {
|
||||||
@@ -340,14 +532,11 @@ export default function WorkspaceFilterBar({
|
|||||||
<div key={catId} class="filter-rule-item-group">
|
<div key={catId} class="filter-rule-item-group">
|
||||||
{index > 0 && <span class="filter-connector-and">And</span>}
|
{index > 0 && <span class="filter-connector-and">And</span>}
|
||||||
|
|
||||||
<div class={`filter-rule-chip ${isOpen ? "open" : ""}`}>
|
<div class={`filter-rule-chip ${isOpen ? "open" : ""}`} data-cat-id={catId}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="filter-rule-chip-main"
|
class="filter-rule-chip-main"
|
||||||
onClick={() => {
|
onClick={(e) => handleOpenChip(catId, e as any)}
|
||||||
setOpenChipId(isOpen ? null : catId);
|
|
||||||
setSearchTerm("");
|
|
||||||
}}
|
|
||||||
aria-expanded={isOpen}
|
aria-expanded={isOpen}
|
||||||
>
|
>
|
||||||
<span class="filter-chip-icon">
|
<span class="filter-chip-icon">
|
||||||
@@ -375,9 +564,9 @@ export default function WorkspaceFilterBar({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Dropdown Popover for Active Chip */}
|
{/* Dropdown Popover for Active Chip (Desktop) */}
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div class="filter-chip-popover" ref={popoverRef}>
|
<div class={`filter-chip-popover desktop-only-filter-popover ${chipAlignRight ? "align-right" : "align-left"}`} ref={popoverRef}>
|
||||||
{cat.options.length > 0 ? (
|
{cat.options.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<div class="filter-chip-search">
|
<div class="filter-chip-search">
|
||||||
@@ -398,7 +587,7 @@ export default function WorkspaceFilterBar({
|
|||||||
<li key={opt.id}>
|
<li key={opt.id}>
|
||||||
<label class="filter-chip-option-label">
|
<label class="filter-chip-option-label">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={opt.checked}
|
checked={opt.checked}
|
||||||
onChange={() => handleToggleOption(catId, opt.id, opt.checked)}
|
onChange={() => handleToggleOption(catId, opt.id, opt.checked)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useEffect, useRef } from "preact/hooks";
|
||||||
import type { RecipeStructure } from "../../lib/database";
|
import type { RecipeStructure } from "../../lib/database";
|
||||||
import type { DragState, Option, PercentMode } from "./types";
|
import type { DragState, Option, PercentMode } from "./types";
|
||||||
|
|
||||||
@@ -38,6 +39,12 @@ type Props = {
|
|||||||
const normal = (value: string) =>
|
const normal = (value: string) =>
|
||||||
value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
||||||
|
|
||||||
|
/** Commit a number input's value, returning the parsed number or undefined. */
|
||||||
|
const commitNumericInput = (input: HTMLInputElement): number | undefined => {
|
||||||
|
const num = Number(input.value);
|
||||||
|
return Number.isFinite(num) ? num : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
export default function RecipeItemRow({
|
export default function RecipeItemRow({
|
||||||
item,
|
item,
|
||||||
componentIndex,
|
componentIndex,
|
||||||
@@ -67,6 +74,31 @@ export default function RecipeItemRow({
|
|||||||
onCreatePendingIngredient,
|
onCreatePendingIngredient,
|
||||||
onSetPercent,
|
onSetPercent,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
// Uncontrolled refs for numeric inputs — avoids Preact fighting
|
||||||
|
// intermediate values like "2." during decimal entry.
|
||||||
|
const qtyInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const qtyTracker = useRef(item.quantity);
|
||||||
|
useEffect(() => {
|
||||||
|
// Sync DOM value when parent changes externally (e.g. percent-driven)
|
||||||
|
if (item.quantity !== qtyTracker.current && qtyInputRef.current) {
|
||||||
|
qtyTracker.current = item.quantity;
|
||||||
|
if (document.activeElement !== qtyInputRef.current) {
|
||||||
|
qtyInputRef.current.value = String(item.quantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [item.quantity]);
|
||||||
|
|
||||||
|
const pctInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const percentDisplay = percentValue != null ? Math.round(percentValue * 1000) / 1000 : undefined;
|
||||||
|
const pctTracker = useRef(percentDisplay);
|
||||||
|
useEffect(() => {
|
||||||
|
if (percentDisplay !== pctTracker.current && pctInputRef.current) {
|
||||||
|
pctTracker.current = percentDisplay;
|
||||||
|
if (document.activeElement !== pctInputRef.current) {
|
||||||
|
pctInputRef.current.value = percentDisplay != null ? String(percentDisplay) : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [percentDisplay]);
|
||||||
const isPending = Boolean(
|
const isPending = Boolean(
|
||||||
item.ingredient_id &&
|
item.ingredient_id &&
|
||||||
pendingIngredients.some((entry) => entry.id === item.ingredient_id)
|
pendingIngredients.some((entry) => entry.id === item.ingredient_id)
|
||||||
@@ -121,14 +153,21 @@ export default function RecipeItemRow({
|
|||||||
>
|
>
|
||||||
<td class="quantity-column">
|
<td class="quantity-column">
|
||||||
<input
|
<input
|
||||||
|
ref={qtyInputRef}
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
step="any"
|
step="any"
|
||||||
aria-label="Quantity"
|
aria-label="Quantity"
|
||||||
value={item.quantity}
|
defaultValue={item.quantity}
|
||||||
onInput={(event) =>
|
onBlur={(event) => {
|
||||||
onUpdateQuantity(Number(event.currentTarget.value))
|
const num = commitNumericInput(event.currentTarget);
|
||||||
}
|
if (num != null) {
|
||||||
|
qtyTracker.current = num;
|
||||||
|
onUpdateQuantity(num);
|
||||||
|
} else {
|
||||||
|
event.currentTarget.value = String(item.quantity);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td class="unit-column">
|
<td class="unit-column">
|
||||||
@@ -246,6 +285,7 @@ export default function RecipeItemRow({
|
|||||||
<span title="A weight equivalency is required">—</span>
|
<span title="A weight equivalency is required">—</span>
|
||||||
) : (
|
) : (
|
||||||
<input
|
<input
|
||||||
|
ref={pctInputRef}
|
||||||
aria-label={`${label} percentage`}
|
aria-label={`${label} percentage`}
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -255,8 +295,16 @@ export default function RecipeItemRow({
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
step="any"
|
step="any"
|
||||||
value={Math.round(percentValue * 1000) / 1000}
|
defaultValue={percentDisplay}
|
||||||
onInput={(event) => onSetPercent(Number(event.currentTarget.value))}
|
onBlur={(event) => {
|
||||||
|
const num = commitNumericInput(event.currentTarget);
|
||||||
|
if (num != null) {
|
||||||
|
pctTracker.current = num;
|
||||||
|
onSetPercent(num);
|
||||||
|
} else {
|
||||||
|
event.currentTarget.value = percentDisplay != null ? String(percentDisplay) : "";
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -219,15 +219,17 @@ input[type="search"]::-webkit-search-results-decoration,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.application-body .workspace-pills > a {
|
.application-body .workspace-pills > a {
|
||||||
min-height: 40px;
|
min-height: 38px;
|
||||||
padding: 8px 16px 8px 12px;
|
padding: 6px 14px 6px 10px;
|
||||||
color: rgba(0,0,0,0.87);
|
color: rgba(0,0,0,0.87);
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #ececec;
|
border: 1px solid #ececec;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
font-size: 16px;
|
font-size: 15px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex: 0 0 auto;
|
||||||
transition: background-color .15s cubic-bezier(0.4,0,0.2,1), border-color .15s cubic-bezier(0.4,0,0.2,1), color .15s ease, box-shadow .15s ease;
|
transition: background-color .15s cubic-bezier(0.4,0,0.2,1), border-color .15s cubic-bezier(0.4,0,0.2,1), color .15s ease, box-shadow .15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,7 +318,8 @@ input[type="search"]::-webkit-search-results-decoration,
|
|||||||
|
|
||||||
.application-body .workspace-filter-trigger-wrapper {
|
.application-body .workspace-filter-trigger-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-left: auto;
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
flex: none;
|
flex: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,7 +364,8 @@ input[type="search"]::-webkit-search-results-decoration,
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 60;
|
z-index: 60;
|
||||||
top: calc(100% + 6px);
|
top: calc(100% + 6px);
|
||||||
right: 0;
|
left: 0;
|
||||||
|
right: auto;
|
||||||
width: 280px;
|
width: 280px;
|
||||||
max-width: min(280px, calc(100vw - 32px));
|
max-width: min(280px, calc(100vw - 32px));
|
||||||
max-height: calc(100vh - 120px);
|
max-height: calc(100vh - 120px);
|
||||||
@@ -375,6 +379,16 @@ input[type="search"]::-webkit-search-results-decoration,
|
|||||||
animation: meez-menu-in 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
animation: meez-menu-in 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.application-body .workspace-filter-menu-popover.align-right {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.application-body .workspace-filter-menu-popover.align-left {
|
||||||
|
left: 0;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.application-body .filter-menu-header {
|
.application-body .filter-menu-header {
|
||||||
padding: 4px 18px 8px;
|
padding: 4px 18px 8px;
|
||||||
}
|
}
|
||||||
@@ -492,6 +506,7 @@ input[type="search"]::-webkit-search-results-decoration,
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
flex-basis: 100%;
|
||||||
padding: 4px 0 0;
|
padding: 4px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -620,6 +635,7 @@ input[type="search"]::-webkit-search-results-decoration,
|
|||||||
z-index: 70;
|
z-index: 70;
|
||||||
top: calc(100% + 6px);
|
top: calc(100% + 6px);
|
||||||
left: 0;
|
left: 0;
|
||||||
|
right: auto;
|
||||||
min-width: 260px;
|
min-width: 260px;
|
||||||
max-width: min(340px, calc(100vw - 32px));
|
max-width: min(340px, calc(100vw - 32px));
|
||||||
max-height: calc(100vh - 120px);
|
max-height: calc(100vh - 120px);
|
||||||
@@ -633,6 +649,16 @@ input[type="search"]::-webkit-search-results-decoration,
|
|||||||
animation: meez-menu-in 0.18s cubic-bezier(0.4, 0, 0.2, 1);
|
animation: meez-menu-in 0.18s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.application-body .filter-chip-popover.align-right {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.application-body .filter-chip-popover.align-left {
|
||||||
|
left: 0;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.application-body .filter-chip-search {
|
.application-body .filter-chip-search {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -710,10 +736,219 @@ input[type="search"]::-webkit-search-results-decoration,
|
|||||||
.application-body .filter-chip-empty-message {
|
.application-body .filter-chip-empty-message {
|
||||||
padding: 16px 12px;
|
padding: 16px 12px;
|
||||||
color: #050841;
|
color: #050841;
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
font-weight: 400;
|
font-style: italic;
|
||||||
text-align: left;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.application-body .filter-chip-empty {
|
||||||
|
padding: 12px 8px;
|
||||||
|
color: #8b93a7;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: center;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Filter & Sort Modal (Meez Parity) */
|
||||||
|
.mobile-filter-backdrop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 980px) {
|
||||||
|
.desktop-only-filter-popover {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-filter-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 999999;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
background: rgba(5, 8, 65, 0.45);
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
animation: meez-fade-in 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes meez-fade-in {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-filter-sheet {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 85vh;
|
||||||
|
background: #ffffff !important;
|
||||||
|
border-radius: 16px 16px 0 0;
|
||||||
|
box-shadow: 0 -8px 32px rgba(5, 8, 65, 0.18);
|
||||||
|
animation: meez-slide-up 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes meez-slide-up {
|
||||||
|
from { transform: translateY(100%); }
|
||||||
|
to { transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-filter-header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 44px 1fr 44px;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 56px;
|
||||||
|
padding: 0 8px;
|
||||||
|
background: #ffffff !important;
|
||||||
|
border-bottom: 1px solid #edf0f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-filter-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #050841;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-filter-back-btn,
|
||||||
|
.mobile-filter-close-btn {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: #050841;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-filter-back-btn svg,
|
||||||
|
.mobile-filter-close-btn svg {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-filter-body {
|
||||||
|
padding: 16px;
|
||||||
|
background: #ffffff !important;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: calc(85vh - 56px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-filter-main-view,
|
||||||
|
.mobile-filter-category-view {
|
||||||
|
background: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-filter-section-heading {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: #050841;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-section-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 18px;
|
||||||
|
color: #3d5df6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-filter-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #edf0f5;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-filter-card-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 52px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 1px solid #edf0f5;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #050841;
|
||||||
|
font-family: var(--meez-font-sans);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 400;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-filter-card-item:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-filter-card-item.active {
|
||||||
|
color: #3d5df6;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-active-badge {
|
||||||
|
margin-left: 6px;
|
||||||
|
color: #3d5df6;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-item-chevron {
|
||||||
|
color: #a5a9c1;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-item-chevron svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-clear-card {
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #edf0f5;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-clear-btn {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: #3d5df6;
|
||||||
|
font-family: var(--meez-font-sans);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-clear-btn:disabled {
|
||||||
|
color: #a5a9c1;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-options-list {
|
||||||
|
max-height: calc(85vh - 160px);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.application-body .entity-directory-table { overflow:visible; border:0; background:#fff; }
|
.application-body .entity-directory-table { overflow:visible; border:0; background:#fff; }
|
||||||
.application-body .entity-directory-toolbar { grid-template-columns:36px 64px minmax(0,1fr) auto; gap:8px; min-height:57px; padding:0; background:#ffffff; border-bottom:1px solid #edf0f5; }
|
.application-body .entity-directory-toolbar { grid-template-columns:36px 64px minmax(0,1fr) auto; gap:8px; min-height:57px; padding:0; background:#ffffff; border-bottom:1px solid #edf0f5; }
|
||||||
.application-body .entity-directory-toolbar.has-selection { grid-template-columns:36px minmax(0,1fr); }
|
.application-body .entity-directory-toolbar.has-selection { grid-template-columns:36px minmax(0,1fr); }
|
||||||
|
|||||||
+37
-12
@@ -519,7 +519,7 @@
|
|||||||
border-radius: 6px !important;
|
border-radius: 6px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Workspace Pills on Mobile */
|
/* Workspace Pills on Mobile: Smooth Horizontal Scrolling */
|
||||||
.application-body .directory-workspace {
|
.application-body .directory-workspace {
|
||||||
width: calc(100% - 32px) !important;
|
width: calc(100% - 32px) !important;
|
||||||
margin-inline: auto !important;
|
margin-inline: auto !important;
|
||||||
@@ -528,11 +528,17 @@
|
|||||||
.application-body .workspace-pills {
|
.application-body .workspace-pills {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
align-items: center !important;
|
align-items: center !important;
|
||||||
flex-wrap: wrap !important;
|
flex-wrap: nowrap !important;
|
||||||
overflow: visible !important;
|
overflow-x: auto !important;
|
||||||
|
-webkit-overflow-scrolling: touch !important;
|
||||||
|
scrollbar-width: none !important;
|
||||||
gap: 8px !important;
|
gap: 8px !important;
|
||||||
padding: 0 0 16px !important;
|
padding: 0 0 4px !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
.application-body .workspace-pills::-webkit-scrollbar {
|
||||||
|
display: none !important;
|
||||||
}
|
}
|
||||||
.application-body .workspace-pills > a {
|
.application-body .workspace-pills > a {
|
||||||
min-height: 36px !important;
|
min-height: 36px !important;
|
||||||
@@ -541,20 +547,39 @@
|
|||||||
flex: 0 0 auto !important;
|
flex: 0 0 auto !important;
|
||||||
white-space: nowrap !important;
|
white-space: nowrap !important;
|
||||||
}
|
}
|
||||||
|
.application-body .workspace-filter-system {
|
||||||
|
display: contents !important;
|
||||||
|
}
|
||||||
|
.application-body .workspace-filter-trigger-wrapper {
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
flex: 0 0 auto !important;
|
||||||
|
margin-left: 0 !important;
|
||||||
|
}
|
||||||
.application-body .workspace-filter-btn {
|
.application-body .workspace-filter-btn {
|
||||||
min-height: 36px !important;
|
min-height: 36px !important;
|
||||||
padding: 6px 12px !important;
|
padding: 6px 12px !important;
|
||||||
font-size: 14px !important;
|
font-size: 14px !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
flex: 0 0 auto !important;
|
||||||
}
|
}
|
||||||
.application-body .workspace-filter-menu-popover {
|
.application-body .filter-rules-row {
|
||||||
max-width: calc(100vw - 32px) !important;
|
display: flex !important;
|
||||||
max-height: calc(100vh - 120px) !important;
|
align-items: center !important;
|
||||||
overflow-y: auto !important;
|
flex-wrap: nowrap !important;
|
||||||
|
overflow-x: auto !important;
|
||||||
|
-webkit-overflow-scrolling: touch !important;
|
||||||
|
scrollbar-width: none !important;
|
||||||
|
gap: 8px !important;
|
||||||
|
padding: 2px 0 8px !important;
|
||||||
|
width: 100% !important;
|
||||||
|
flex-basis: 100% !important;
|
||||||
}
|
}
|
||||||
.application-body .filter-chip-popover {
|
.application-body .filter-rules-row::-webkit-scrollbar {
|
||||||
max-width: calc(100vw - 32px) !important;
|
display: none !important;
|
||||||
max-height: calc(100vh - 120px) !important;
|
}
|
||||||
overflow-y: auto !important;
|
.application-body .filter-rule-item-group {
|
||||||
|
flex: 0 0 auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Directory Table Mobile Enhancements */
|
/* Directory Table Mobile Enhancements */
|
||||||
|
|||||||
Reference in New Issue
Block a user