diff --git a/libs/remix-ui/grid-view/src/index.ts b/libs/remix-ui/grid-view/src/index.ts
new file mode 100644
index 0000000000..db94458115
--- /dev/null
+++ b/libs/remix-ui/grid-view/src/index.ts
@@ -0,0 +1,4 @@
+export * from './lib/remix-ui-grid-view'
+export * from './lib/remix-ui-grid-section'
+export * from './lib/remix-ui-grid-cell'
+
diff --git a/libs/remix-ui/grid-view/src/lib/components/customCheckbox.tsx b/libs/remix-ui/grid-view/src/lib/components/customCheckbox.tsx
new file mode 100644
index 0000000000..6993e153e0
--- /dev/null
+++ b/libs/remix-ui/grid-view/src/lib/components/customCheckbox.tsx
@@ -0,0 +1,42 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+import React from 'react'
+import { useContext } from 'react'
+import FiltersContext from ".././filtersContext"
+
+interface CustomCheckboxProps {
+ label: string
+ color?: string
+}
+
+export const CustomCheckbox = (props: CustomCheckboxProps) => {
+ const filterCon = useContext(FiltersContext)
+ let textColor = props.color
+ let defChecked = true
+ if (filterCon.keyValueMap[props.label]) defChecked = filterCon.keyValueMap[props.label].enabled
+ if (!textColor || textColor == '') textColor = filterCon.keyValueMap[props.label].color
+
+ return (
+
+ {
+ if (props.label == 'no tag')
+ filterCon.showUntagged = ! filterCon.showUntagged
+ else filterCon.updateValue(props.label, e.target.checked, textColor)}}
+ type="checkbox"
+ />
+
+
+ )
+}
+
+export default CustomCheckbox
diff --git a/libs/remix-ui/grid-view/src/lib/filtersContext.tsx b/libs/remix-ui/grid-view/src/lib/filtersContext.tsx
new file mode 100644
index 0000000000..0513247b11
--- /dev/null
+++ b/libs/remix-ui/grid-view/src/lib/filtersContext.tsx
@@ -0,0 +1,16 @@
+import React, { createContext, useState, useContext } from 'react';
+
+interface FilterContextType {
+ showUntagged: boolean
+ keyValueMap: Record;
+ updateValue: (key: string, enabled: boolean, color: string) => void
+ addValue: (key: string, enabled: boolean, color: string) => void
+}
+const FiltersContext = createContext({
+ showUntagged: false,
+ keyValueMap: {},
+ updateValue: () => {},
+ addValue: () => {},
+});
+
+export default FiltersContext
diff --git a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.css b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.css
new file mode 100644
index 0000000000..19d630ddce
--- /dev/null
+++ b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.css
@@ -0,0 +1,21 @@
+.remixui_grid_cell {
+ font-weight: normal;
+}
+
+.remixui_grid_cell_container {
+ width: fit-content;
+}
+
+.remixui_grid_cell_title{
+ font-size: 0.8rem;
+ font-style: italic;
+}
+
+.remixui_grid_cell_btn {
+ width: 32px;
+}
+
+.remixui_grid_cell_logo {
+ width: 3rem;
+ height: 3rem;
+}
diff --git a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.tsx b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.tsx
new file mode 100644
index 0000000000..0642eb074c
--- /dev/null
+++ b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.tsx
@@ -0,0 +1,71 @@
+import React, {useState, useEffect, useContext, useRef, ReactNode} from 'react' // eslint-disable-line
+
+import './remix-ui-grid-cell.css'
+import FiltersContext from "./filtersContext"
+import { CustomTooltip } from '@remix-ui/helper'
+
+
+declare global {
+ interface Window {
+ _paq: any
+ }
+}
+const _paq = window._paq = window._paq || []
+
+interface RemixUIGridCellProps {
+ plugin: any
+ logo?: string
+ title?: string
+ tagList?: string[] // max 8, others will be ignored
+ classList?: string
+ styleList?: any
+ children?: ReactNode
+}
+
+export const RemixUIGridCell = (props: RemixUIGridCellProps) => {
+ const filterCon = useContext(FiltersContext)
+ const [anyEnabled, setAnyEnabled] = useState(false)
+
+ useEffect(() => {
+ if (props.tagList) setAnyEnabled(props.tagList.some((key) => filterCon.keyValueMap[key]?.enabled))
+ else setAnyEnabled(filterCon.showUntagged)
+ }, [filterCon, props.tagList])
+
+ return (
+
+ { anyEnabled &&
+
+
+
+ { props.logo &&
}
+ { props.title &&
}
+
+ { props.children }
+
+
+ { props.tagList &&
+ { Object.keys(props.tagList).map((key) => (
+ filterCon.keyValueMap[props.tagList[key]].enabled && (
+
+
+
+
+ )
+ )) }
+
}
+ { !props.tagList &&
+ }
+
}
+
+ )
+}
+
+export default RemixUIGridCell
diff --git a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-section.css b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-section.css
new file mode 100644
index 0000000000..5c79533a71
--- /dev/null
+++ b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-section.css
@@ -0,0 +1,26 @@
+.remixui_grid_section {
+ font-weight: normal;
+}
+
+.remixui_grid_section_container {
+ width: fit-content;
+}
+
+.remixui_grid_section_title{
+ font-size: 0.8rem;
+ font-style: italic;
+}
+
+.remixui_grid_section_btn {
+ width: 32px;
+}
+
+.remixui_grid_section_logo {
+ width: 3rem;
+ height: 3rem;
+}
+
+* {
+ scrollbar-width: thin;
+ scrollbar-color: var(--bg-dark) var(--bg-light);
+}
\ No newline at end of file
diff --git a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-section.tsx b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-section.tsx
new file mode 100644
index 0000000000..4dd5409678
--- /dev/null
+++ b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-section.tsx
@@ -0,0 +1,70 @@
+import React, {useState, useEffect, useContext, useRef, ReactNode} from 'react' // eslint-disable-line
+
+import './remix-ui-grid-section.css'
+import {ThemeContext, themes} from './themeContext'
+
+declare global {
+ interface Window {
+ _paq: any
+ }
+}
+const _paq = window._paq = window._paq || []
+
+interface RemixUIGridSectionProps {
+ plugin: any
+ title?: string
+ hScrollable: boolean
+ classList?: string
+ styleList?: any
+ children?: ReactNode
+}
+
+export const RemixUIGridSection = (props: RemixUIGridSectionProps) => {
+ const {plugin} = props.plugin
+ const searchInputRef = useRef(null)
+
+ console.log('props.hScrollable ', props.hScrollable)
+ const [state, setState] = useState<{
+ themeQuality: {filter: string; name: string}
+ }>({
+ themeQuality: themes.light
+ })
+
+ useEffect(() => {
+ plugin?.call('theme', 'currentTheme').then((theme) => {
+ // update theme quality. To be used for for images
+ setState((prevState) => {
+ return {
+ ...prevState,
+ themeQuality: theme.quality === 'dark' ? themes.dark : themes.light
+ }
+ })
+ })
+ plugin?.on('theme', 'themeChanged', (theme) => {
+ // update theme quality. To be used for for images
+ setState((prevState) => {
+ return {
+ ...prevState,
+ themeQuality: theme.quality === 'dark' ? themes.dark : themes.light
+ }
+ })
+ })
+ }, [plugin])
+
+ return (
+
+
+ { props.title &&
{ props.title }
}
+
+ { props.children }
+
+
+
+ )
+}
+
+export default RemixUIGridSection
diff --git a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.css b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.css
new file mode 100644
index 0000000000..89e41d0cdf
--- /dev/null
+++ b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.css
@@ -0,0 +1,25 @@
+.remixui_grid_view {
+ font-weight: normal;
+}
+
+.remixui_grid_view_container {
+ overflow: auto;
+}
+
+.remixui_grid_view_title{
+ font-size: 0.8rem;
+ font-style: italic;
+}
+
+.remixui_grid_view_btn {
+ width: 32px;
+}
+
+.remixui_grid_view_logo {
+ width: 3rem;
+ height: 3rem;
+}
+
+.remixui_grid_view_titlebar {
+ min-width: max-content;
+}
\ No newline at end of file
diff --git a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.tsx b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.tsx
new file mode 100644
index 0000000000..8f260487bd
--- /dev/null
+++ b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.tsx
@@ -0,0 +1,145 @@
+import React, {useState, useEffect, useContext, useRef, ReactNode} from 'react' // eslint-disable-line
+
+import './remix-ui-grid-view.css'
+import {ThemeContext, themes} from './themeContext'
+import CustomCheckbox from './components/customCheckbox'
+import FiltersContext from "./filtersContext"
+
+declare global {
+ interface Window {
+ _paq: any
+ }
+}
+const _paq = window._paq = window._paq || []
+
+interface RemixUIGridViewProps {
+ plugin: any
+ logo?: string
+ title?: string
+ enableFilter?: boolean
+ tagList?: [string, string][] // max 8, others will be ignored
+ showUntagged?: boolean
+ classList?: string
+ styleList?: any
+ description?: string
+ children?: ReactNode
+}
+
+export const RemixUIGridView = (props: RemixUIGridViewProps) => {
+ const [keyValueMap, setKeyValueMap] = useState>({});
+
+ const showUntagged = props.showUntagged || false
+ const updateValue = (key: string, enabled: boolean, color?: string) => {
+ if (!color || color === '') color = setKeyValueMap[key].color
+ setKeyValueMap((prevMap) => ({
+ ...prevMap,
+ [key]: {color, enabled},
+ }))
+ }
+
+ const addValue = (key: string, enabled: boolean, color: string) => {
+ // Check if the key already exists, if so, do not add
+ if (key in keyValueMap) {
+ return
+ }
+
+ // Add the new key-value pair
+ setKeyValueMap((prevMap) => ({
+ ...prevMap,
+ [key]: { enabled, color },
+ }))
+ }
+
+ const {plugin} = props.plugin
+ const searchInputRef = useRef(null)
+
+ const [state, setState] = useState<{
+ themeQuality: {filter: string; name: string}
+ }>({
+ themeQuality: themes.light
+ })
+
+ // Initialize filters context with data from props
+ useEffect(() => {
+ if (props.tagList && Array.isArray(props.tagList)) {
+ const initialKeyValueMap: Record = {};
+
+ // Limit to first 8 elements, ignoring the rest
+ for (let i = 0; i < props.tagList.length; i++) {
+ const [key, color] = props.tagList[i]
+ initialKeyValueMap[key] = { enabled: true, color }
+ }
+ if (showUntagged) initialKeyValueMap['no tag'] = { enabled: true, color: 'primary' }
+ setKeyValueMap(initialKeyValueMap)
+ }
+ }, [])
+
+ useEffect(() => {
+ plugin?.call('theme', 'currentTheme').then((theme) => {
+ // update theme quality. To be used for for images
+ setState((prevState) => {
+ return {
+ ...prevState,
+ themeQuality: theme.quality === 'dark' ? themes.dark : themes.light
+ }
+ })
+ })
+ plugin?.on('theme', 'themeChanged', (theme) => {
+ // update theme quality. To be used for for images
+ setState((prevState) => {
+ return {
+ ...prevState,
+ themeQuality: theme.quality === 'dark' ? themes.dark : themes.light
+ }
+ })
+ })
+ }, [plugin])
+
+ return (
+
+
+
+
+
+
+ { props.logo &&
}
+ { props.title &&
{ props.title }
}
+
+ { props.description &&
{ props.description }
}
+ { props.enableFilter &&
+
+
+
+
+
+
+ { Object.keys(keyValueMap).map((key) => (
+
+ )) }
+
+
+
}
+
+ { props.children }
+
+
+
+
+ )
+}
+
+export default RemixUIGridView
diff --git a/libs/remix-ui/grid-view/src/lib/themeContext.tsx b/libs/remix-ui/grid-view/src/lib/themeContext.tsx
new file mode 100644
index 0000000000..fc67007dac
--- /dev/null
+++ b/libs/remix-ui/grid-view/src/lib/themeContext.tsx
@@ -0,0 +1,16 @@
+import React from 'react' // eslint-disable-line
+
+export const themes = {
+ light: {
+ filter: 'invert(0)',
+ name: 'light'
+ },
+ dark: {
+ filter: 'invert(1)',
+ name: 'dark'
+ }
+}
+
+export const ThemeContext = React.createContext(
+ themes.dark // default value
+)