-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5d0234f
commit 0e58e00
Showing
40 changed files
with
2,540 additions
and
288 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,288 +1,18 @@ | ||
const limitPerPage = 21; | ||
let components = []; | ||
let totalApps = 0; | ||
let totalComponents = 0; | ||
const componentsUrl = "#/near/widget/ComponentsPage"; | ||
const searchRequiredTag = state.selectedTab === "apps" ? "app" : null; | ||
const searchPlaceholder = state.selectedTab === "apps" ? "Search Apps" : "Search Components"; | ||
|
||
State.init({ | ||
currentPage: 0, | ||
selectedTab: props.tab || "all", | ||
}); | ||
|
||
if (props.tab && props.tab !== state.selectedTab) { | ||
State.update({ | ||
selectedTab: props.tab, | ||
}); | ||
} | ||
|
||
const tagsData = Social.get("*/widget/*/metadata/tags/*", "final"); | ||
|
||
const data = Social.keys("*/widget/*", "final", { | ||
return_type: "BlockHeight", | ||
}); | ||
|
||
if (data) { | ||
const result = []; | ||
|
||
Object.keys(data).forEach((accountId) => { | ||
return Object.keys(data[accountId].widget).forEach((widgetName) => { | ||
totalComponents++; | ||
|
||
if (state.selectedTab === "apps") { | ||
const hasAppTag = tagsData[accountId].widget[widgetName]?.metadata?.tags["app"] === ""; | ||
if (!hasAppTag) return; | ||
totalApps++; | ||
} | ||
|
||
result.push({ | ||
accountId, | ||
widgetName, | ||
blockHeight: data[accountId].widget[widgetName], | ||
}); | ||
}); | ||
}); | ||
|
||
result.sort((a, b) => b.blockHeight - a.blockHeight); | ||
components = result.slice(0, state.currentPage * limitPerPage + limitPerPage); | ||
} | ||
|
||
function onSearchChange({ result, term }) { | ||
if (term.trim()) { | ||
State.update({ searchResults: result || [] }); | ||
} else { | ||
State.update({ searchResults: null }); | ||
} | ||
} | ||
|
||
const items = state.searchResults || components; | ||
|
||
const Wrapper = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
gap: 48px; | ||
padding-bottom: 48px; | ||
padding-top: 48px; | ||
`; | ||
|
||
const Header = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
gap: 12px; | ||
`; | ||
|
||
const Search = styled.div` | ||
width: 246px; | ||
@media (max-width: 500px) { | ||
width: 100%; | ||
} | ||
`; | ||
|
||
const H1 = styled.h1` | ||
font-weight: 600; | ||
font-size: 32px; | ||
line-height: 39px; | ||
color: #11181c; | ||
margin: 0; | ||
`; | ||
|
||
const H2 = styled.h2` | ||
font-weight: 400; | ||
font-size: 20px; | ||
line-height: 24px; | ||
color: #687076; | ||
margin: 0; | ||
`; | ||
|
||
const Text = styled.p` | ||
margin: 0; | ||
line-height: 1.5rem; | ||
color: ${(p) => (p.bold ? "#11181C" : "#687076")} !important; | ||
font-weight: ${(p) => (p.bold ? "600" : "400")}; | ||
font-size: ${(p) => (p.small ? "12px" : "14px")}; | ||
overflow: ${(p) => (p.ellipsis ? "hidden" : "")}; | ||
text-overflow: ${(p) => (p.ellipsis ? "ellipsis" : "")}; | ||
white-space: ${(p) => (p.ellipsis ? "nowrap" : "")}; | ||
overflow-wrap: anywhere; | ||
b { | ||
font-weight: 600; | ||
color: #11181c; | ||
} | ||
&[href] { | ||
display: inline-flex; | ||
gap: 0.25rem; | ||
&:hover, | ||
&:focus { | ||
text-decoration: underline; | ||
} | ||
} | ||
`; | ||
|
||
const Items = styled.div` | ||
display: grid; | ||
grid-template-columns: repeat(3, minmax(0, 1fr)); | ||
gap: 24px; | ||
@media (max-width: 1200px) { | ||
grid-template-columns: repeat(2, minmax(0, 1fr)); | ||
} | ||
@media (max-width: 800px) { | ||
grid-template-columns: minmax(0, 1fr); | ||
} | ||
`; | ||
|
||
const Item = styled.div``; | ||
|
||
const Button = styled.button` | ||
display: block; | ||
width: 100%; | ||
padding: 8px; | ||
height: 32px; | ||
background: #fbfcfd; | ||
border: 1px solid #d7dbdf; | ||
border-radius: 50px; | ||
font-weight: 600; | ||
font-size: 12px; | ||
line-height: 15px; | ||
text-align: center; | ||
cursor: pointer; | ||
color: #11181c !important; | ||
margin: 0; | ||
&:hover, | ||
&:focus { | ||
background: #ecedee; | ||
text-decoration: none; | ||
outline: none; | ||
} | ||
span { | ||
color: #687076 !important; | ||
} | ||
`; | ||
|
||
const Tabs = styled.div` | ||
display: flex; | ||
height: 48px; | ||
border-bottom: 1px solid #eceef0; | ||
margin-bottom: -24px; | ||
overflow: auto; | ||
scroll-behavior: smooth; | ||
@media (max-width: 1200px) { | ||
background: #f8f9fa; | ||
border-top: 1px solid #eceef0; | ||
margin-left: -12px; | ||
margin-right: -12px; | ||
> * { | ||
flex: 1; | ||
} | ||
} | ||
`; | ||
|
||
const TabsButton = styled.a` | ||
display: inline-flex; | ||
align-items: center; | ||
justify-content: center; | ||
height: 100%; | ||
font-weight: 600; | ||
font-size: 12px; | ||
padding: 0 12px; | ||
position: relative; | ||
color: ${(p) => (p.selected ? "#11181C" : "#687076")}; | ||
background: none; | ||
border: none; | ||
outline: none; | ||
text-align: center; | ||
text-decoration: none !important; | ||
&:hover { | ||
color: #11181c; | ||
} | ||
&::after { | ||
content: ""; | ||
display: ${(p) => (p.selected ? "block" : "none")}; | ||
position: absolute; | ||
bottom: 0; | ||
left: 0; | ||
right: 0; | ||
height: 3px; | ||
background: #59e692; | ||
} | ||
`; | ||
|
||
return ( | ||
<Wrapper className="container-xl"> | ||
<Header> | ||
{state.selectedTab === "apps" && ( | ||
<> | ||
<H1>{totalApps} Apps</H1> | ||
<H2>Discover the latest apps from the NEAR community.</H2> | ||
</> | ||
)} | ||
|
||
{state.selectedTab !== "apps" && ( | ||
<> | ||
<H1>{totalComponents} Components</H1> | ||
<H2>Discover the latest components from the NEAR community.</H2> | ||
</> | ||
)} | ||
</Header> | ||
|
||
<Search> | ||
<Widget | ||
src="${alias_near}/widget/ComponentSearch" | ||
props={{ | ||
limit: 21, | ||
onChange: onSearchChange, | ||
placeholder: searchPlaceholder, | ||
filterTag: searchRequiredTag, | ||
}} | ||
/> | ||
</Search> | ||
|
||
{!state.searchResults && ( | ||
<Tabs> | ||
<TabsButton href={`${componentsUrl}?tab=all`} selected={state.selectedTab === "all"}> | ||
All | ||
</TabsButton> | ||
|
||
<TabsButton href={`${componentsUrl}?tab=apps`} selected={state.selectedTab === "apps"}> | ||
Apps | ||
</TabsButton> | ||
</Tabs> | ||
)} | ||
|
||
{state.searchResults?.length === 0 && <Text>No components matched your search.</Text>} | ||
|
||
{items.length > 0 && ( | ||
<Items> | ||
{items.map((component, i) => ( | ||
<Item key={component.accountId + component.widgetName}> | ||
<Widget | ||
src="${config_account}/widget/component.card" | ||
props={{ | ||
src: `${component.accountId}/widget/${component.widgetName}`, | ||
blockHeight: component.blockHeight, | ||
}} | ||
/> | ||
</Item> | ||
))} | ||
</Items> | ||
)} | ||
|
||
{!state.searchResults && ( | ||
<Button type="button" onClick={() => State.update({ currentPage: state.currentPage + 1 })}> | ||
Load More | ||
</Button> | ||
)} | ||
</Wrapper> | ||
); | ||
const { Avatar } = VM.require("${config_account}/widget/components.ui.avatar"); | ||
const { AvatarGroup } = VM.require("${config_account}/widget/components.ui.avatar-group"); | ||
const { Badge } = VM.require("${config_account}/widget/components.ui.badge"); | ||
const { Button } = VM.require("${config_account}/widget/components.ui.button"); | ||
const { Chip } = VM.require("${config_account}/widget/components.ui.chip"); | ||
const { Container } = VM.require("${config_account}/widget/components.ui.container"); | ||
const { Logo } = VM.require("${config_account}/widget/components.ui.logo"); | ||
const { Tag } = VM.require("${config_account}/widget/components.ui.tag"); | ||
return { | ||
Avatar, | ||
AvatarGroup, | ||
Badge, | ||
Button, | ||
Chip, | ||
Container, | ||
Logo, | ||
Tag, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
const { Chip } = VM.require("${config_account}/widget/components") || { | ||
Chip: () => <></>, | ||
}; | ||
|
||
const items = props.items ?? []; | ||
const onSelect = props.onSelect ?? (() => {}); | ||
|
||
const [selectedItems, setSelectedItems] = useState([]); | ||
|
||
const handleClick = (item) => { | ||
if (props.multiple) { | ||
if (!selectedItems.includes(item)) { | ||
setSelectedItems((prev) => [...prev, item]); | ||
} else if (selectedItems.includes(item)) { | ||
setSelectedItems((prev) => prev.filter((i) => i !== item)); | ||
} | ||
} else { | ||
if (selectedItems !== item) { | ||
setSelectedItems(item); | ||
} else if (selectedItems === item) { | ||
setSelectedItems(null); | ||
} | ||
} | ||
}; | ||
|
||
useEffect(() => { | ||
onSelect(selectedItems); | ||
}, [selectedItems, onSelect]); | ||
|
||
if (items.length === 0) { | ||
return <div>No items passed</div>; | ||
} | ||
|
||
const duplicates = items.filter((item, index) => items.indexOf(item) !== index); | ||
if (duplicates.length > 0) { | ||
return <div>Duplicate Items Found</div>; | ||
} | ||
|
||
return ( | ||
<div className="d-flex align-items-center gap-2 flex-wrap"> | ||
{items.map((item) => ( | ||
<Chip | ||
key={item} | ||
selected={props.multiple ? selectedItems.includes(item) : selectedItems === item} | ||
onClick={() => handleClick(item)} | ||
size={props.size} | ||
multiple={props.multiple} | ||
> | ||
{item} | ||
</Chip> | ||
))} | ||
</div> | ||
); |
Oops, something went wrong.