AppsGrid.tsx 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. import { FormattedMessage, useIntl } from "react-intl"
  2. import { AppCard } from "../components/AppCard"
  3. import classNames from "classnames"
  4. import { useState } from "react"
  5. import SelectMenu from "../components/SelectMenu"
  6. import { sortBy as _sortBy } from "lodash"
  7. import type { appsList } from "../data/apps"
  8. import Category from "../components/Category"
  9. export type AppsGridProps = {
  10. apps: appsList
  11. }
  12. /** Renders AppCards as a grid, with sorting and filtering options */
  13. export const AppsGrid = ({ apps }: AppsGridProps) => {
  14. const intl = useIntl()
  15. const [activeCategory, setActiveCategory] = useState("all")
  16. /** normalizing the apps dictionary as an array */
  17. const allApps = Object.entries(apps)
  18. .map(([category, apps]) =>
  19. apps.map(({ name, icon, url, paid, released_on, hidden_from_all }) => ({
  20. name,
  21. icon,
  22. url,
  23. category,
  24. paid: paid ?? false,
  25. hidden_from_all: hidden_from_all ?? false,
  26. released_on: new Date(released_on) ?? null,
  27. }))
  28. )
  29. .flat()
  30. //prettier-ignore
  31. const sortOptions = [
  32. { value: "date_added", label: intl.formatMessage({ id: "sorting.recently_added", defaultMessage: "Recently Added" }) },
  33. { value: "paid", label: intl.formatMessage({ id: "sorting.free", defaultMessage: "Free" }) },
  34. { value: "name", label: intl.formatMessage({ id: "sorting.name", defaultMessage: "Alphabetical" }) },
  35. ]
  36. const [sortOption, setSortOption] = useState(sortOptions[0].value)
  37. const filteredApps = allApps.filter(
  38. ({ category, hidden_from_all }) => category === activeCategory || (activeCategory === "all" && !hidden_from_all)
  39. )
  40. const sortedAndFilteredApps = _sortBy(filteredApps, sortOption)
  41. //prettier-ignore
  42. const categories = [
  43. { key: "all", label: intl.formatMessage({ id: "browse_apps.all", defaultMessage: "All" }) },
  44. { key: "android", label: intl.formatMessage({ id: "browse_apps.android", defaultMessage: "Android" }) },
  45. { key: "ios", label: intl.formatMessage({ id: "browse_apps.ios", defaultMessage: "iOS" }) },
  46. { key: "web", label: intl.formatMessage({ id: "browse_apps.web", defaultMessage: "Web" }) },
  47. { key: "sailfish", label: intl.formatMessage({ id: "browse_apps.sailfish", defaultMessage: "SailfishOS" }) },
  48. { key: "desktop", label: intl.formatMessage({ id: "browse_apps.desktop", defaultMessage: "Desktop" }) },
  49. ]
  50. return (
  51. <div>
  52. <div>
  53. <h2 className="h4 mb-8">
  54. <FormattedMessage
  55. id="browse_apps.title2"
  56. defaultMessage="Browse third-party apps"
  57. />
  58. </h2>
  59. <div className="-mx-gutter pis-gutter mb-6 overflow-x-auto">
  60. <div className="flex flex-wrap gap-gutter md:flex-nowrap">
  61. {categories.map((category) => (
  62. <Category
  63. key={category.key}
  64. value={category.key}
  65. currentValue={activeCategory}
  66. label={category.label}
  67. onChange={(e) => setActiveCategory(e.target.value)}
  68. />
  69. ))}
  70. </div>
  71. </div>
  72. </div>
  73. <div className="my-8">
  74. <SelectMenu
  75. label={
  76. <FormattedMessage id="sorting.sort_by" defaultMessage="Sort" />
  77. }
  78. value={sortOption}
  79. onChange={(v) => {
  80. setSortOption(v)
  81. }}
  82. options={sortOptions}
  83. />
  84. </div>
  85. <div className="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-4">
  86. {sortedAndFilteredApps.map(AppCard)}
  87. </div>
  88. </div>
  89. )
  90. }
  91. export default AppsGrid