Add legend to help users discover options

Signed-off-by: Egor Savkin <es@m-labs.hk>
This commit is contained in:
Egor Savkin 2023-12-15 17:26:52 +08:00
parent a03a151c42
commit 14c365b20f
10 changed files with 102 additions and 19 deletions

View File

@ -212,10 +212,22 @@ button {
display: flex; display: flex;
font-size: .8rem; font-size: .8rem;
> p { > .description {
width: 50%; width: 50%;
padding-right: 30px; padding-right: 30px;
} }
> .legend {
//d-flex justify-content-end align-self-start
display: flex;
justify-content: end;
align-self: start;
width: 50%;
table {
width: 75%;
max-width: 300px;
}
}
} }
.summary { .summary {
@ -560,6 +572,7 @@ button {
.crate-bar { .crate-bar {
width: 100%; width: 100%;
font-size: 0.9rem;
.crate-mode { .crate-mode {
text-align: left; text-align: left;
@ -572,6 +585,7 @@ button {
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
padding-bottom: 5px; padding-bottom: 5px;
display: inline-block;
} }
a.active { a.active {
font-weight: 700; font-weight: 700;
@ -663,17 +677,13 @@ button {
color: white; color: white;
font-weight: 700; font-weight: 700;
font-size: .6rem; font-size: .6rem;
padding: .8rem 1rem; padding: .5rem .8rem;
box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.15); box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.15);
text-align: left; text-align: left;
p { p {
margin-bottom: 0; margin-bottom: 0;
} }
p + p {
padding-bottom: 8px;
}
} }
.k-popup-connectors { .k-popup-connectors {

View File

@ -108,9 +108,17 @@
} }
#root-shop .panel .control > .description, #root-shop .panel .control > .description,
#root-shop .panel .control > .legend,
#root-shop .crate-mode { #root-shop .crate-mode {
width: 100%; width: 100%;
} }
#root-shop .panel .control > .legend {
justify-content: center;
align-self: center;
}
#root-shop .panel .control > .legend tr {
padding: 0;
}
#root-shop .crate-mode { #root-shop .crate-mode {
text-align: left; text-align: left;
@ -308,12 +316,23 @@
} }
#root-shop .panel .control > .description, #root-shop .panel .control > .description,
#root-shop .crate-mode { #root-shop .panel .control > .legend {
width: 100%; width: 100%;
} }
#root-shop .panel .control > .legend {
justify-content: center;
align-self: center;
}
#root-shop .panel .control > .legend tr {
padding: 0;
}
#root-shop .crate-mode { #root-shop .panel .crate .crate-bar .crate-mode {
text-align: left; text-align: left;
width: 50%;
}
#root-shop .panel .crate .crate-bar .crate-mode a {
display: block;
} }
#root-shop .panel .summary { #root-shop .panel .summary {
@ -571,12 +590,23 @@
} }
#root-shop .panel .control > .description, #root-shop .panel .control > .description,
#root-shop .panel .control > .crate-mode { #root-shop .panel .control > .legend {
width: 100%; width: 100%;
} }
#root-shop .panel .control > .legend {
justify-content: center;
align-self: center;
}
#root-shop .panel .control > .legend tr {
padding: 0;
}
#root-shop .panel .control > .crate-mode { #root-shop .panel .crate .crate-bar .crate-mode {
text-align: left; text-align: left;
width: 50%;
}
#root-shop .panel .crate .crate-bar .crate-mode a {
display: block;
} }
#root-shop .panel .summary { #root-shop .panel .summary {

File diff suppressed because one or more lines are too long

View File

@ -26,10 +26,10 @@ export function Crate({crate_index}) {
return ( return (
<div className="crate"> <div className="crate">
<div className="crate-bar d-inline-flex"> <div className="crate-bar d-inline-flex justify-content-between">
<CrateMode crate_index={crate_index}/> <CrateMode crate_index={crate_index}/>
<div className="delete-crate align-self-end align-content-end justify-content-end" onClick={() => onDeleteCrate(crate.id)}> <div className="delete-crate align-self-start align-content-start justify-content-end" onClick={() => onDeleteCrate(crate.id)}>
Delete crate <img src="/images/shop/icon-remove.svg" alt="remove"/> Delete crate <img src="/images/shop/icon-remove.svg" alt="remove"/>
</div> </div>
</div> </div>

View File

@ -24,7 +24,7 @@ export function CrateMode({crate_index}) {
{modes_order.map((mode_name, _) => ( {modes_order.map((mode_name, _) => (
<a <a
key={mode_name} key={mode_name}
className={crate.crate_mode === mode_name ? 'active' : ''} className={(crate.crate_mode === mode_name ? 'active' : '') }
onClick={() => setMode(crate.id, mode_name)} onClick={() => setMode(crate.id, mode_name)}
href="#" href="#"
role="button">{crate_modes[mode_name].name}</a> role="button">{crate_modes[mode_name].name}</a>

21
static/js/shop/Legend.jsx Normal file
View File

@ -0,0 +1,21 @@
import React from "react";
import {useShopStore} from "./shop_store";
export function LegendItem({icon, description}) {
return (
<tr>
<td className="p-1"><img className="" width="20px" src={icon} alt={description}/></td>
<td className="p-1"><span> {description} </span></td>
</tr>
)
}
export function Legend() {
const legend = useShopStore(state => state.legend);
return <table>
<tbody>
{legend.map((item, i) => <LegendItem key={"legend_item"+i} icon={item.icon} description={item.description}/>)}
</tbody>
</table>
}

View File

@ -13,7 +13,7 @@ import {useRenderCount} from "@uidotdev/usehooks";
* Component that renders all things for order. * Component that renders all things for order.
* It acts like-a layout, this component do nothing more. * It acts like-a layout, this component do nothing more.
*/ */
export function OrderPanel({title, description}) { export function OrderPanel({title, description, legend}) {
// #!render_count // #!render_count
const renderCount = useRenderCount(); const renderCount = useRenderCount();
const isMobile = useShopStore((state) => state.isMobile); const isMobile = useShopStore((state) => state.isMobile);
@ -26,8 +26,13 @@ export function OrderPanel({title, description}) {
<h2>{title}</h2> <h2>{title}</h2>
<div className="control"> <div className="control justify-content-between">
{description} {description}
<div className="legend">
{legend}
</div>
</div> </div>
<div> <div>

View File

@ -9,6 +9,7 @@ import {Layout} from "./Layout";
import {Backlog} from "./Backlog"; import {Backlog} from "./Backlog";
import {OrderPanel} from "./OrderPanel"; import {OrderPanel} from "./OrderPanel";
import {useShopStore} from "./shop_store"; import {useShopStore} from "./shop_store";
import {Legend} from "./Legend";
/** /**
* Component that renders the entire shop * Component that renders the entire shop
@ -59,6 +60,7 @@ export function Shop() {
this ordering system, or if you need other configurations, email us directly anytime this ordering system, or if you need other configurations, email us directly anytime
at <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a>. The price is estimated and must at <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a>. The price is estimated and must
be confirmed by a quote.</p>)} be confirmed by a quote.</p>)}
legend={(<Legend/>)}
/> />
)}> )}>
</Layout> </Layout>

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
import {create} from "zustand"; import {createWithEqualityFn} from "zustand/traditional";
import {data as shared_data, itemsUnfoldedList} from "./utils"; import {data as shared_data, itemsUnfoldedList} from "./utils";
import {true_type_of} from "./options/utils"; import {true_type_of} from "./options/utils";
import {v4 as uuidv4} from "uuid"; import {v4 as uuidv4} from "uuid";
@ -28,6 +28,10 @@ const useBacklog = ((set, get) => ({
cardIndexById: card_id => get().cards_list.findIndex((element) => (card_id === element)) cardIndexById: card_id => get().cards_list.findIndex((element) => (card_id === element))
})); }));
const useLegend = ((set, get) => ({
legend: shared_data.legend
}))
const useCrateModes = ((set, get) => ({ const useCrateModes = ((set, get) => ({
crate_modes: shared_data.crateModes, crate_modes: shared_data.crateModes,
modes_order: shared_data.crateModeOrder, modes_order: shared_data.crateModeOrder,
@ -389,7 +393,7 @@ const useCart = ((set, get) => ({
setCrateMode: (id, mode) => { setCrateMode: (id, mode) => {
get()._setCrateMode(id, mode) get()._setCrateMode(id, mode)
get().fillExtData(crate_id); get().fillExtData(id);
get().fillWarnings(id); get().fillWarnings(id);
get().setActiveCrate(id); get().setActiveCrate(id);
}, },
@ -435,7 +439,7 @@ const useCart = ((set, get) => ({
})) }))
export const useShopStore = create((...params) => ({ export const useShopStore = createWithEqualityFn((...params) => ({
...useBacklog(...params), ...useBacklog(...params),
...useCrateModes(...params), ...useCrateModes(...params),
...useCart(...params), ...useCart(...params),
@ -443,4 +447,5 @@ export const useShopStore = create((...params) => ({
...useLayout(...params), ...useLayout(...params),
...useHighlighted(...params), ...useHighlighted(...params),
...useImportJSON(...params), ...useImportJSON(...params),
...useLegend(...params),
})) }))

View File

@ -3,6 +3,16 @@ const shop_data = {
API_RFQ: 'https://hooks.m-labs.hk/rfq', API_RFQ: 'https://hooks.m-labs.hk/rfq',
currency: 'USD', currency: 'USD',
legend: [
{icon: "/images/shop/icon-customize.svg", description: "cards configuration available"},
{icon: "/images/shop/icon-add.svg", description: "add a card or crate to the order"},
{icon: "/images/shop/icon-remove.svg", description: "remove a card or crate from the order"},
{icon: "/images/shop/icon-clear.svg", description: "remove all the cards from the crate"},
{icon: "/images/shop/icon-warning.svg", description: "the card or crate contains errors"},
{icon: "/images/shop/icon-reminder.svg", description: "suggestions or hints available"},
],
crateModes: { crateModes: {
rack: { rack: {
id: 'rack', id: 'rack',