diff --git a/packages/system/package.json b/packages/system/package.json index 987b6f2..fd672bb 100644 --- a/packages/system/package.json +++ b/packages/system/package.json @@ -1,6 +1,6 @@ { "name": "@gotpop/system", - "version": "0.1.284", + "version": "0.1.287", "description": "React design system components for gotpop", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -68,6 +68,7 @@ "publish:github": "npm version patch && yarn build && npm publish && cd ../.. && rm -f package-lock.json" }, "dependencies": { + "@icons-pack/react-simple-icons": "^13.8.0", "@next/font": "^14.2.15", "lucide-react": "^0.469.0", "motion": "^12.23.26", diff --git a/packages/system/src/components/storyblok/CardsClientFilter/CardsClientFilter.tsx b/packages/system/src/components/storyblok/CardsClientFilter/CardsClientFilter.tsx index 5035164..7b7e24b 100644 --- a/packages/system/src/components/storyblok/CardsClientFilter/CardsClientFilter.tsx +++ b/packages/system/src/components/storyblok/CardsClientFilter/CardsClientFilter.tsx @@ -9,11 +9,38 @@ import type { import { getMeta } from "../../../utils/card-utils" import { CardsControl } from "../../ui/CardsControl/CardsControl" import { CustomElement } from "../../ui/CustomElement" +import type { IconName } from "../../ui/Icon/Icon" import { Card, type CardBlokProps } from "../Card/Card" import { CardImage } from "../CardImage" import { useCardsFilter } from "./use-cards-filter" import "./CardsClientFilter.css" +const SORT_ICON_MAP: Record = { + published_desc: "calendar-arrow-down", + published_asc: "calendar-arrow-up", + name_asc: "arrow-down-az", + name_desc: "arrow-up-za", +} + +const TAG_ICON_MAP: Record = { + all: "tag", + css: "css", + javascript: "javascript", + html: "html5", + react: "react", + angular: "angular", + vuejs: "vuedotjs", + next: "nextdotjs", + nextjs: "nextdotjs", + node: "nodedotjs", + nodejs: "nodedotjs", + typescript: "typescript", + graphql: "graphql", + cloudflare: "cloudflare", + accessibility: "accessibility", + aws: "cloud", +} + const SORT_OPTIONS = [ { value: "published_desc", label: "Newest First" }, { value: "published_asc", label: "Oldest First" }, @@ -76,13 +103,19 @@ export function CardsClientFilter({ .map((tag) => ({ value: tag.value, label: tag.name, + icon: TAG_ICON_MAP[tag.value.toLowerCase()], })) const tagOptions = [ - { value: "all", label: "All Posts" }, + { value: "all", label: "All Posts", icon: TAG_ICON_MAP.all }, ...availableTagOptions, ] + const sortOptions = SORT_OPTIONS.map((option) => ({ + ...option, + icon: SORT_ICON_MAP[option.value], + })) + const output = filteredAndSortedPosts.length > 0 && filteredAndSortedPosts.map((blok) => @@ -107,7 +140,7 @@ export function CardsClientFilter({ label="Sort" value={currentSort} onChange={handleSortChange} - options={SORT_OPTIONS} + options={sortOptions} /> diff --git a/packages/system/src/components/ui/CardsControl/CardsControl.css b/packages/system/src/components/ui/CardsControl/CardsControl.css index 86b11ee..9430e96 100644 --- a/packages/system/src/components/ui/CardsControl/CardsControl.css +++ b/packages/system/src/components/ui/CardsControl/CardsControl.css @@ -1,42 +1,123 @@ @scope (select-option) { & { align-items: center; - background: var(--dark-500); + background: var(--light-100); + border: 1px solid var(--light-300); display: grid; grid-column: var(--grid-column, auto); grid-template-columns: auto 1fr; + height: var(--spacing-lg); min-width: var(--round-up); } + .select { + align-items: center; + appearance: none; + background-image: none; + border-radius: 0px; + border: none; + box-sizing: border-box; + color: var(--dark-500); + cursor: pointer; + display: flex; + font-size: var(--text-xs); + height: 46px; + outline: none; + padding: 0.5rem 1.5rem; + + &::picker-icon { + color: var(--dark-500); + transition: 0.4s rotate; + transition-delay: 0.25s; + } + + &:open::picker-icon { + rotate: -180deg; + } + + @supports (appearance: base-select) { + appearance: base-select; + } + } + .select-label { align-items: center; - color: white; + border-right: 1px solid var(--light-300); + color: var(--dark-800); + cursor: pointer; display: flex; + font-size: var(--text-xs); height: max-content; justify-content: center; - min-height: 2rem; - min-width: 6rem; + min-height: var(--spacing-md); + min-width: 5rem; width: max-content; - padding-inline: 1rem; } - .select { - appearance: none; - background-image: none; - background: var(--light-100); - border-radius: 0px; + .selected-content { + align-items: end; + display: flex; + gap: var(--spacing-base); + min-width: 8rem; + + span { + color: var(--secondary-700); + } + } + + ::picker(select) { border: 1px solid var(--light-300); - box-sizing: border-box; + opacity: 0; + scale: 0.5; + transition: all 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) allow-discrete; + transform-origin: top center; + + &:popover-open { + opacity: 1; + scale: 1; + + @starting-style { + opacity: 0; + scale: 0.5; + } + } + } + + option { + align-items: center; + background-color: var(--light-100); + border-bottom: 1px solid var(--light-200); color: var(--dark-500); + cursor: pointer; display: flex; font-size: var(--text-sm); - height: 2rem; - line-height: 0.8; - outline: none; - padding: 0.5rem 0.75rem; + font-size: var(--text-xs); + gap: var(--spacing-base); + height: var(--spacing-lg); + justify-content: start; + padding: 0 1.5rem; + transition: color 0.3s ease-in-out; - @supports (appearance: base-select) { - appearance: base-select; + &::checkmark { + display: none; + } + + &:last-child { + border-bottom: none; + } + + &:checked { + font-weight: bold; + } + + span { + display: flex; + align-items: center; + } + + &:hover, + &:focus-visible { + color: var(--primary-400); } } } diff --git a/packages/system/src/components/ui/CardsControl/CardsControl.tsx b/packages/system/src/components/ui/CardsControl/CardsControl.tsx index 2a1e1e2..e2f63e0 100644 --- a/packages/system/src/components/ui/CardsControl/CardsControl.tsx +++ b/packages/system/src/components/ui/CardsControl/CardsControl.tsx @@ -2,13 +2,20 @@ import { useId } from "react" import { CustomElement } from "../../ui/CustomElement" +import { Icon, type IconName } from "../Icon/Icon" import "./CardsControl.css" +export interface CardsControlOption { + value: string + label: string + icon?: IconName +} + interface CardsControlProps { label: string value: string onChange: (value: string) => void - options: { value: string; label: string }[] + options: CardsControlOption[] className?: string style?: React.CSSProperties } @@ -34,10 +41,14 @@ export function CardsControl({ onChange={(e) => onChange(e.target.value)} className="select" > + {options .filter((option) => option.value && option.label) .map((option) => ( ))} diff --git a/packages/system/src/components/ui/Icon/Icon.tsx b/packages/system/src/components/ui/Icon/Icon.tsx index d952955..2aaa8df 100644 --- a/packages/system/src/components/ui/Icon/Icon.tsx +++ b/packages/system/src/components/ui/Icon/Icon.tsx @@ -1,7 +1,28 @@ import { + SiAngular, + SiCloudflare, + SiCss, + SiGraphql, + SiHtml5, + SiJavascript, + SiNextdotjs, + SiNodedotjs, + SiReact, + SiTypescript, + SiVuedotjs, +} from "@icons-pack/react-simple-icons" +import { + Accessibility, + ArrowDownAZ, + ArrowDownZA, + ArrowUpAZ, + ArrowUpZA, BriefcaseBusiness, + CalendarArrowDown, + CalendarArrowUp, ChevronLeft, ChevronRight, + Cloud, ExternalLink, Github, HelpCircle, @@ -13,17 +34,11 @@ import { Phone, Search, Star, + Tag, User, X, } from "lucide-react" -/** - * Icon registry - single source of truth for available icons. - * Only icons listed here will be bundled (tree-shaking optimization). - * Add new icons here and run `yarn sync-icons` to update Storyblok datasource. - * - * Keys match the CMS datasource values (kebab-case) - */ const ICON_REGISTRY = { mail: Mail, newspaper: Newspaper, @@ -41,14 +56,32 @@ const ICON_REGISTRY = { star: Star, x: X, user: User, + tag: Tag, + "arrow-down-az": ArrowDownAZ, + "arrow-up-az": ArrowUpAZ, + "arrow-down-za": ArrowDownZA, + "arrow-up-za": ArrowUpZA, + "calendar-arrow-down": CalendarArrowDown, + "calendar-arrow-up": CalendarArrowUp, + css: SiCss, + javascript: SiJavascript, + html5: SiHtml5, + react: SiReact, + angular: SiAngular, + vuedotjs: SiVuedotjs, + nextdotjs: SiNextdotjs, + nodedotjs: SiNodedotjs, + typescript: SiTypescript, + graphql: SiGraphql, + cloudflare: SiCloudflare, + accessibility: Accessibility, + cloud: Cloud, } as const -// Export for sync scripts and type generation export const AVAILABLE_ICONS = Object.keys(ICON_REGISTRY) as Array< keyof typeof ICON_REGISTRY > -// Type-safe icon names export type IconName = keyof typeof ICON_REGISTRY interface IconProps { @@ -76,7 +109,7 @@ export function Icon({ "Icon not found:", JSON.stringify({ name, availableIcons: AVAILABLE_ICONS }, null, 2) ) - // render a safe fallback so consumers still get an icon + return ( diff --git a/packages/system/src/types/custom-elements.d.ts b/packages/system/src/types/custom-elements.d.ts index 3719d24..8acfd94 100644 --- a/packages/system/src/types/custom-elements.d.ts +++ b/packages/system/src/types/custom-elements.d.ts @@ -48,6 +48,14 @@ declare module "react" { React.HTMLAttributes, HTMLElement > + "select-option": React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + > + selectedcontent: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + > } } } diff --git a/yarn.lock b/yarn.lock index 81d46a5..3f48bf9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -938,9 +938,10 @@ "@storyblok/react" "5.4.20" "@gotpop/system@*", "@gotpop/system@file:/Users/minivan/web/sites/platform/system/packages/system": - version "0.1.284" + version "0.1.287" resolved "file:packages/system" dependencies: + "@icons-pack/react-simple-icons" "^13.8.0" "@next/font" "^14.2.15" lucide-react "^0.469.0" motion "^12.23.26" @@ -950,6 +951,11 @@ simple-icons "^16.3.0" storyblok-rich-text-react-renderer "^3.0.1" +"@icons-pack/react-simple-icons@^13.8.0": + version "13.8.0" + resolved "https://registry.npmjs.org/@icons-pack/react-simple-icons/-/react-simple-icons-13.8.0.tgz" + integrity sha512-iZrhL1fSklfCCVn68IYHaAoKfcby3RakUTn2tRPyHBkhr2tkYqeQbjJWf+NizIYBzKBn2IarDJXmTdXd6CuEfw== + "@img/colour@^1.0.0": version "1.0.0" resolved "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz" @@ -3971,7 +3977,7 @@ react-refresh@^0.14.0, "react-refresh@>=0.10.0 <1.0.0": resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz" integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== -"react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react@^17 || ^18 || ^19", "react@^18.0.0 || ^19.0.0", "react@^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", react@^19.2.0, react@^19.2.3, "react@>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0", react@>=16.0.0, react@>=19.2.3, react@19.2.3: +"react@^16.13 || ^17 || ^18 || ^19", "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react@^17 || ^18 || ^19", "react@^18.0.0 || ^19.0.0", "react@^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", react@^19.2.0, react@^19.2.3, "react@>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0", react@>=16.0.0, react@>=19.2.3, react@19.2.3: version "19.2.3" resolved "https://registry.npmjs.org/react/-/react-19.2.3.tgz" integrity sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==