diff --git a/website/src/components/showcase/ShowcaseCard/index.js b/website/src/components/showcase/ShowcaseCard/index.js new file mode 100644 index 0000000000..36fb3bd750 --- /dev/null +++ b/website/src/components/showcase/ShowcaseCard/index.js @@ -0,0 +1,94 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {memo} from 'react'; + +import styles from './styles.module.css'; +import clsx from 'clsx'; +import Image from '@theme/IdealImage'; +import {Tags, TagList} from '../../../data/users'; +import {sortBy} from '../../../utils/jsUtils'; + +function TagIcon({label, description, icon}) { + return ( + + {icon} + + ); +} + +function ShowcaseCardTagIcons({tags}) { + const tagObjects = tags + .map((tag) => ({tag, ...Tags[tag]})) + .filter((tagObject) => !!tagObject.icon); + + // Keep same order of icons for all tags + const tagObjectsSorted = sortBy(tagObjects, (tagObject) => + TagList.indexOf(tagObject.tag), + ); + + return tagObjectsSorted.map((tagObject, index) => ( + + )); +} + +const ShowcaseCard = memo(function ({user}) { + return ( +
+
+
+ {user.title} +
+
+
+
+
+
+

{user.title}

+
+
+ +
+
+ {user.description} +
+
+
+ {(user.website || user.source) && ( +
+
+ {user.website && ( + + Website + + )} + {user.source && ( + + Source + + )} +
+
+ )} +
+
+ ); +}); + +export default ShowcaseCard; diff --git a/website/src/components/showcase/ShowcaseCard/styles.module.css b/website/src/components/showcase/ShowcaseCard/styles.module.css new file mode 100644 index 0000000000..2495278b25 --- /dev/null +++ b/website/src/components/showcase/ShowcaseCard/styles.module.css @@ -0,0 +1,35 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.showcaseCard { + height: 100%; +} + +.showcaseCardImage { + max-height: 175px; + overflow: hidden; +} + +.titleIconsRow { + display: flex; + flex-direction: row; + flex-wrap: nowrap; +} + +.titleIconsRowTitle { + flex: 1; +} + +.titleIconsRowIcons { + flex-grow: 0; + flex-shrink: 0; +} + +.tagIcon { + margin: 0.2rem; + user-select: none; +} diff --git a/website/src/components/showcase/ShowcaseCheckbox/index.js b/website/src/components/showcase/ShowcaseCheckbox/index.js new file mode 100644 index 0000000000..79df701209 --- /dev/null +++ b/website/src/components/showcase/ShowcaseCheckbox/index.js @@ -0,0 +1,36 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import clsx from 'clsx'; + +import styles from './styles.module.css'; + +function ShowcaseCheckbox({ + className, + name, + label, + onChange, + checked, + ...props +}) { + const id = `showcase_checkbox_id_${name};`; + return ( +
+ + +
+ ); +} + +export default ShowcaseCheckbox; diff --git a/website/src/components/showcase/ShowcaseCheckbox/styles.module.css b/website/src/components/showcase/ShowcaseCheckbox/styles.module.css new file mode 100644 index 0000000000..98d742ff2b --- /dev/null +++ b/website/src/components/showcase/ShowcaseCheckbox/styles.module.css @@ -0,0 +1,21 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.checkboxContainer { + display: inline; + padding: 5px; + user-select: none; +} + +.checkboxContainer, .checkboxContainer > * { + cursor: pointer; +} + +.checkboxContainer label { + margin-left: 0.5rem; + text-overflow: ellipsis; +} diff --git a/website/src/components/showcase/ShowcaseSelect/index.js b/website/src/components/showcase/ShowcaseSelect/index.js new file mode 100644 index 0000000000..bb678d8e57 --- /dev/null +++ b/website/src/components/showcase/ShowcaseSelect/index.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; + +import styles from './styles.module.css'; + +function ShowcaseSelect({tag, label, onChange, value, children}) { + const id = `showcase_select_id_${tag};`; + return ( +
+ + +
+ ); +} + +export default ShowcaseSelect; diff --git a/website/src/components/showcase/ShowcaseSelect/styles.module.css b/website/src/components/showcase/ShowcaseSelect/styles.module.css new file mode 100644 index 0000000000..0257a0ef91 --- /dev/null +++ b/website/src/components/showcase/ShowcaseSelect/styles.module.css @@ -0,0 +1,12 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.selectContainer { + display: inline; + padding: 5px; + user-select: none; +} diff --git a/website/src/data/users.js b/website/src/data/users.js index 055719399e..41b7e6869f 100644 --- a/website/src/data/users.js +++ b/website/src/data/users.js @@ -5,20 +5,23 @@ * LICENSE file in the root directory of this source tree. */ +import React from 'react'; +import {sortBy, difference} from '../utils/jsUtils'; + /* - * ADD YOUR SITE TO DOCUSAURUS SHOWCASE: + * ADD YOUR SITE TO THE DOCUSAURUS SHOWCASE: * * Requirements for adding your site to our showcase: - * - It is a real site with real content and customizations (different enough from init templates) - * - It has a stable domain name (a random Netlify/Vercel domain is not allowed) - * - The code is publicly available + * - It is a production-ready site with real content and decent customizations (different from the init templates) + * - It is NOT a work-in-progress with empty pages + * - It has a stable domain name (a Netlify/Vercel deploy preview is not allowed) * * Instructions: - * - Add your site in the json array below, in alphabetical order of title + * - Add your site in the json array below * - Add a local image preview (decent screenshot of your Docusaurus site) - * - Use `tags: []`: it is our responsibility to assign site tags - * - * The image must be added to the GitHub repository, and use `require("image")` + * - Use relevant tags to qualify your site (read the tag descriptions bellow) + * - The image MUST be added to the GitHub repository, and use `require("image")` + * - Open a PR and check for reported CI errors * * Example PR: https://github.com/facebook/docusaurus/pull/3976 * @@ -28,17 +31,106 @@ * - Go to https://github.com//docusaurus/tree//website/src/data/showcase * - Drag-and-drop an image here to add it to your existing PR * + * Please help us maintain this showcase page data: + * - Update sites with wrong data + * - Ensure site tags remains correct over time + * - Remove sites not using Docusaurus anymore + * - Add missing Docusaurus sites (if the site owner agreed) + * */ +// LIST OF AVAILABLE TAGS +// Available tags to assign to your site +// Please choose widely, we'll remove unappropriate tags +export const Tags = { + // DO NOT USE THIS TAG: we choose sites to add to favorites + favorite: { + label: 'Favorite', + description: + 'Our favorite Docusaurus sites that you must absolutely check-out!', + icon: <>โค๏ธ, + }, + + // For open-source sites, a link to the source code is required + opensource: { + label: 'Open-Source', + description: 'Open-Source Docusaurus sites can be useful for inspiration!', + icon: <>๐Ÿ‘จโ€๐Ÿ’ป, + }, + + product: { + label: 'Product', + description: 'Docusaurus sites associated to a commercial product!', + icon: <>๐Ÿ’ต, + }, + + design: { + label: 'Design', + description: + 'Beautiful Docusaurus sites, polished and standing out from the initial template!', + icon: <>๐Ÿ’…, + }, + + i18n: { + label: 'I18n', + description: + 'Translated Docusaurus sites using the internationalization support with more than 1 locale.', + icon: <>๐Ÿณ๏ธ, + }, + + versioning: { + label: 'Versioning', + description: + 'Docusaurus sites using the versioning feature of the docs plugin to manage multiple versions.', + icon: <>๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ, + }, + // Sites using multi-instance plugins + multiInstance: { + label: 'Multi-Instance', + description: + 'Docusaurus sites using multiple instances of the same plugin on the same site.', + icon: <>๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ, + }, + + // Large Docusaurus sites, with a lot of content (> 200 pages, excluding versions) + large: { + label: 'Large site', + description: + 'Very large Docusaurus sites, including much more pages than the average!', + icon: <>๐Ÿ’ช, + }, + + facebook: { + label: 'Facebook sites', + description: 'Docusaurus sites of Facebook projects', + icon: <>๐Ÿ‘ฅ, + }, + + personal: { + label: 'Personal sites', + description: + 'Personal websites, blogs and digital gardens built with Docusaurus', + icon: <>๐Ÿ™‹, + }, + + rtl: { + label: 'RTL Direction', + description: + 'Docusaurus sites using the right-to-left reading direction support.', + icon: <>โ†ช๏ธ, + }, +}; + +// Add your site to this list // prettier-ignore -const users = [ +const Users = [ { title: 'AgileTs', description: 'Global State and Logic Framework for reactive Applications', preview: require('./showcase/agilets.png'), website: 'https://agile-ts.org/', source: 'https://github.com/agile-ts/documentation', - tags: ['design'], + tags: ['opensource', 'design'], }, { title: 'AI-Speaker', @@ -46,7 +138,7 @@ const users = [ preview: require('./showcase/aispeaker.png'), website: 'https://ai-speaker.com/', source: 'https://github.com/sviete/AIS-WWW', - tags: [], + tags: ['opensource'], }, { title: 'Algolia Docsearch', @@ -55,7 +147,7 @@ const users = [ preview: require('./showcase/algolia.png'), website: 'https://docsearch.algolia.com/', source: 'https://github.com/algolia/docsearch-website', - tags: ['highlight', 'design'], + tags: ['opensource', 'product'], }, { title: 'Amphora Data', @@ -63,31 +155,34 @@ const users = [ preview: require('./showcase/amphora.png'), website: 'https://www.amphoradata.com/', source: 'https://github.com/amphoradata/amphoradata.github.io', - tags: ['large'], + tags: ['opensource', 'product'], }, { title: 'Apache APISIX', - description: 'A Dynamic, Real-Time, High-Performance Cloud-Native API Gateway', + description: + 'A Dynamic, Real-Time, High-Performance Cloud-Native API Gateway', preview: require('./showcase/apache-apisix.png'), website: 'https://apisix.apache.org/', source: 'https://github.com/apache/apisix-website', - tags: [], + tags: ['opensource','i18n','large'], }, { title: 'AttoBot', - description: 'A multi-purpose Discord bot with many features and API integrations that will enhance your Discord experience.', + description: + 'A multi-purpose Discord bot with many features and API integrations that will enhance your Discord experience.', preview: require('./showcase/attobot.png'), website: 'https://attobot.xyz', source: 'https://github.com/attobot-discord/website', - tags: [], + tags: ['opensource'], }, { title: 'Awe framework', - description: 'Awe framework, Build light-weight and functional websites quickly', + description: + 'Awe framework, Build light-weight and functional websites quickly', preview: require('./showcase/awe-framework.png'), website: 'https://docs.aweframework.com/', source: 'https://gitlab.com/aweframework/awe', - tags: [], + tags: ['opensource','versioning'], }, { title: 'Axioms', @@ -95,7 +190,7 @@ const users = [ preview: require('./showcase/axioms.png'), website: 'https://developer.axioms.io/', source: 'https://github.com/axioms-io/developer', - tags: ['large'], + tags: ['opensource', 'product'], }, { title: 'Benthos', @@ -103,7 +198,7 @@ const users = [ preview: require('./showcase/benthos.png'), website: 'https://benthos.dev/', source: 'https://github.com/Jeffail/benthos', - tags: ['design', 'large'], + tags: ['opensource', 'large'], }, { title: 'blog.johnnyreilly.com', @@ -111,7 +206,7 @@ const users = [ preview: require('./showcase/johnnyreilly.png'), website: 'https://blog.johnnyreilly.com/', source: 'https://github.com/johnnyreilly/blog.johnnyreilly.com', - tags: [], + tags: ['opensource','personal','large'], }, { title: 'Botonic', @@ -119,7 +214,7 @@ const users = [ preview: require('./showcase/botonic.png'), website: 'https://botonic.io/', source: 'https://github.com/hubtype/botonic', - tags: ['large'], + tags: ['opensource'], }, { title: 'Build Tracker', @@ -128,7 +223,7 @@ const users = [ preview: require('./showcase/build-tracker.png'), website: 'https://buildtracker.dev', source: 'https://github.com/paularmstrong/build-tracker', - tags: [], + tags: ['opensource'], }, { title: 'Clutch', @@ -136,7 +231,7 @@ const users = [ preview: require('./showcase/clutch.png'), website: 'https://clutch.sh/', source: 'https://github.com/lyft/clutch', - tags: ['highlight', 'design', 'large'], + tags: ['opensource'], }, { title: 'Component Kit', @@ -144,7 +239,7 @@ const users = [ preview: require('./showcase/componentkit.png'), website: 'https://componentkit.org', source: 'https://github.com/facebook/componentkit', - tags: ['highlight', 'facebook'], + tags: ['opensource', 'favorite', 'facebook'], }, { title: 'ConfigCat Feature Flags', @@ -152,7 +247,7 @@ const users = [ preview: require('./showcase/configcat.png'), website: 'https://configcat.com/docs/', source: 'https://github.com/configcat/docs', - tags: ['large'], + tags: ['opensource', 'product'], }, { title: 'Console Table', @@ -160,7 +255,7 @@ const users = [ preview: require('./showcase/console-table.png'), website: 'https://console-table.netlify.app/', source: 'https://github.com/ayonious/console-table-docu', - tags: [], + tags: ['opensource'], }, { title: 'Create React App', @@ -168,7 +263,7 @@ const users = [ preview: require('./showcase/create-react-app.png'), website: 'https://facebook.github.io/create-react-app/', source: 'https://github.com/facebook/create-react-app', - tags: ['highlight', 'design', 'large', 'facebook'], + tags: ['opensource', 'facebook'], }, { title: 'Datagit', @@ -177,7 +272,7 @@ const users = [ preview: require('./showcase/datagit.png'), website: 'https://datagit.ir/', source: 'https://github.com/massoudmaboudi/datagit_v2.docusaurus', - tags: ['rtl'], + tags: ['opensource', 'favorite', 'rtl'], }, { title: 'DevSpace', @@ -185,15 +280,16 @@ const users = [ preview: require('./showcase/devspace.png'), website: 'https://devspace.sh/cli/docs/', source: 'https://github.com/loft-sh/devspace', - tags: [], + tags: ['opensource'], }, { title: 'Diem', - description: 'A decentralized, programmable database which provides a financial infrastructure that can empower billions of people.', + description: + 'A decentralized, programmable database which provides a financial infrastructure that can empower billions of people.', preview: require('./showcase/diem.png'), - website: 'https://developers.libra.org', + website: 'https://dip.diem.com/', source: 'https://github.com/diem/diem', - tags: ['highlight', 'design', 'large'], + tags: ['opensource', 'favorite', 'design', 'large', 'facebook'], }, { title: 'Draft.js', @@ -201,23 +297,25 @@ const users = [ preview: require('./showcase/draftjs.png'), website: 'https://draftjs.org/', source: 'https://github.com/facebook/draft-js', - tags: [], + tags: ['opensource','facebook'], }, { title: 'Eightshift Docs', - description: 'All the tools you need to start building a modern WordPress project, using all the latest development tools.', + description: + 'All the tools you need to start building a modern WordPress project, using all the latest development tools.', preview: require('./showcase/eightshift-docs.png'), website: 'https://infinum.github.io/eightshift-docs/', source: 'https://github.com/infinum/eightshift-docs', - tags: ['highlight', 'design', 'large'], + tags: ['opensource', 'favorite', 'design'], }, { title: 'Erxes', - description: 'Combine all your business tools into one streamlined and integrated open-source framework', + description: + 'Combine all your business tools into one streamlined and integrated open-source framework', preview: require('./showcase/erxes.png'), website: 'https://docs.erxes.io/', source: 'https://github.com/erxes/erxes', - tags: ['design'], + tags: ['opensource'], }, { title: 'Eta', @@ -225,7 +323,7 @@ const users = [ preview: require('./showcase/eta.png'), website: 'https://eta.js.org/', source: 'https://github.com/eta-dev/eta', - tags: ['design'], + tags: ['opensource'], }, { title: 'FBT', @@ -233,7 +331,7 @@ const users = [ preview: require('./showcase/fbt.png'), website: 'https://facebookincubator.github.io/fbt/', source: 'https://github.com/facebook/fbt', - tags: ['facebook'], + tags: ['opensource', 'facebook'], }, { title: 'Flipper', @@ -241,7 +339,7 @@ const users = [ preview: require('./showcase/flipper.png'), website: 'https://fbflipper.com', source: 'https://github.com/facebook/flipper', - tags: ['design', 'facebook'], + tags: ['opensource', 'design', 'facebook'], }, { title: 'FlexIt Analytics', @@ -249,7 +347,7 @@ const users = [ preview: require('./showcase/flexit.png'), website: 'https://learn.flexitanalytics.com/', source: 'https://github.com/ataft/flexit-docs', - tags: ['design', 'large'], + tags: ['opensource','product'], }, { title: 'Flux', @@ -257,7 +355,7 @@ const users = [ preview: require('./showcase/flux.png'), website: 'https://facebook.github.io/flux/', source: 'https://github.com/facebook/flux', - tags: ['facebook'], + tags: ['opensource', 'facebook'], }, { title: 'FoalTS', @@ -265,7 +363,7 @@ const users = [ preview: require('./showcase/foal.png'), website: 'https://foalts.org/', source: 'https://github.com/FoalTS/foal/tree/master/docs', - tags: ['highlight', 'design', 'large', 'versioning', 'i18n'], + tags: ['opensource', 'design', 'versioning', 'i18n'], }, { title: 'Gladys Assistant', @@ -273,7 +371,7 @@ const users = [ preview: require('./showcase/gladys-assistant.png'), website: 'https://gladysassistant.com/', source: 'https://github.com/GladysAssistant/v4-website', - tags: ['i18n'], + tags: ['opensource', 'i18n'], }, { title: 'GraphQL Code Generator', @@ -282,7 +380,7 @@ const users = [ preview: require('./showcase/graphql-codegen.png'), website: 'https://graphql-code-generator.com/', source: 'https://github.com/dotansimha/graphql-code-generator', - tags: ['design', 'large'], + tags: ['opensource','design'], }, { title: 'GraphQL Inspector', @@ -290,7 +388,7 @@ const users = [ preview: require('./showcase/graphql-inspector.png'), website: 'https://graphql-inspector.com', source: 'https://github.com/kamilkisiela/graphql-inspector', - tags: ['highlight', 'design'], + tags: ['opensource', 'design','product'], }, { title: 'GraphQL Mesh', @@ -298,7 +396,7 @@ const users = [ preview: require('./showcase/graphql-mesh.png'), website: 'https://graphql-mesh.com', source: 'https://github.com/urigo/graphql-mesh', - tags: ['large'], + tags: ['opensource'], }, { title: 'Gulp', @@ -306,7 +404,7 @@ const users = [ preview: require('./showcase/gulp.png'), website: 'https://gulpjs.com', source: 'https://github.com/gulpjs/gulp', - tags: ['highlight', 'design', 'large'], + tags: ['opensource', 'large'], }, { title: 'Hashnode Support', @@ -314,7 +412,7 @@ const users = [ preview: require('./showcase/hashnode.png'), website: 'https://support.hashnode.com/', source: 'https://github.com/Hashnode/support', - tags: ['highlight', 'large'], + tags: ['opensource'], }, { title: 'Hermes', @@ -322,7 +420,7 @@ const users = [ preview: require('./showcase/hermes.png'), website: 'https://hermesengine.dev', source: 'https://github.com/facebook/hermes', - tags: ['facebook'], + tags: ['opensource', 'facebook'], }, { title: 'Home Assistant', @@ -330,7 +428,7 @@ const users = [ preview: require('./showcase/home-assistant.png'), website: 'https://developers.home-assistant.io/', source: 'https://github.com/home-assistant/core', - tags: ['large'], + tags: ['opensource'], }, { title: 'Idb', @@ -338,7 +436,7 @@ const users = [ preview: require('./showcase/idb.png'), website: 'https://www.fbidb.io/', source: 'https://github.com/facebook/idb', - tags: ['facebook'], + tags: ['opensource', 'facebook'], }, { title: 'IntelAGENT Billing', @@ -346,23 +444,7 @@ const users = [ preview: require('./showcase/intelagent.png'), website: 'https://www.intelagent.ca/', source: 'https://github.com/intelagentbilling/docs', - tags: [''], - }, - { - title: 'mailgo', - description: 'A new concept of mailto and tel links', - preview: require('./showcase/mailgo.png'), - website: 'https://mailgo.js.org/', - source: 'https://github.com/manzinello/mailgo', - tags: ['design', 'large'], - }, - { - title: 'Metro', - description: 'The JavaScript bundler for React Native', - preview: require('./showcase/metro.png'), - website: 'https://facebook.github.io/metro/', - source: 'https://github.com/facebook/metro', - tags: ['facebook'], + tags: ['opensource','product'], }, { title: 'Kotest', @@ -370,7 +452,7 @@ const users = [ preview: require('./showcase/kotest.jpg'), website: 'https://kotest.io', source: 'https://github.com/kotest/kotest', - tags: ['large'], + tags: ['opensource'], }, { title: 'SICOPE Model', @@ -378,7 +460,7 @@ const users = [ preview: require('./showcase/sicope-model.png'), website: 'https://sicope-model.github.io/', source: 'https://github.com/sicope-model/sicope-model', - tags: [], + tags: ['opensource'], }, { title: 'Mailgo', @@ -386,15 +468,24 @@ const users = [ preview: require('./showcase/mailgo.png'), website: 'https://mailgo.dev/', source: 'https://github.com/manzinello/mailgo.dev', - tags: [], + tags: ['opensource'], + }, + { + title: 'Metro', + description: 'The JavaScript bundler for React Native', + preview: require('./showcase/metro.png'), + website: 'https://facebook.github.io/metro/', + source: 'https://github.com/facebook/metro', + tags: ['opensource', 'facebook'], }, { title: 'MikroORM', - description: 'TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns.', + description: + 'TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns.', preview: require('./showcase/mikro-orm.png'), website: 'https://mikro-orm.io', source: 'https://github.com/mikro-orm/mikro-orm', - tags: ['highlight', 'large', 'versioning'], + tags: ['opensource', 'large', 'versioning'], }, { title: 'Motion Layout', @@ -403,7 +494,7 @@ const users = [ preview: require('./showcase/motion-layout.png'), website: 'https://motion-layout.azurewebsites.net', source: 'https://github.com/jeffersonlicet/react-motion-layout', - tags: ['design'], + tags: ['opensource'], }, { title: 'Neutron JS', @@ -411,7 +502,7 @@ const users = [ preview: require('./showcase/neutronjs.png'), website: 'https://www.neutronjs.com/', source: 'https://github.com/neutronjs/neutron-cli', - tags: ['versioning'], + tags: ['opensource', 'versioning'], }, { title: 'NextAuth.js', @@ -419,7 +510,7 @@ const users = [ preview: require('./showcase/nextauthjs.png'), website: 'https://next-auth.js.org/', source: 'https://github.com/nextauthjs/next-auth', - tags: ['highlight', 'design', 'large'], + tags: ['opensource','design'], }, { title: 'Node SerialPort', @@ -427,23 +518,25 @@ const users = [ preview: require('./showcase/node-serialport.png'), website: 'https://serialport.io', source: 'https://github.com/serialport/node-serialport', - tags: ['large', 'versioning'], + tags: ['opensource','versioning'], }, { title: 'Nodify', - description: 'High-performance WPF node editor component designed for MVVM.', + description: + 'High-performance WPF node editor component designed for MVVM.', preview: require('./showcase/nodify.png'), website: 'https://miroiu.github.io/nodify/', source: 'https://github.com/miroiu/nodify', - tags: ['design'], + tags: ['opensource', 'design'], }, { title: 'OCPeasy', - description: 'Open-source software provisioning, configuration management, and application-deployment tool enabling infrastructure as code on OpenShift.', + description: + 'Open-source software provisioning, configuration management, and application-deployment tool enabling infrastructure as code on OpenShift.', preview: require('./showcase/ocpeasy.png'), website: 'https://www.ocpeasy.org', source: 'https://github.com/ocpeasy/website', - tags: [], + tags: ['opensource'], }, { title: 'Oxidizer', @@ -451,14 +544,15 @@ const users = [ preview: require('./showcase/oxidizer.png'), website: 'https://oxidizer.rs', source: 'https://github.com/oxidizer-rs/website', - tags: ['design'], + tags: ['opensource'], }, { title: 'Paubox', description: 'Paubox API Documentation', preview: require('./showcase/paubox.png'), website: 'https://docs.paubox.com/', - tags: [], + source: null, + tags: ["product"], }, { title: 'pnpm', @@ -466,23 +560,24 @@ const users = [ preview: require('./showcase/pnpm.png'), website: 'https://pnpm.js.org/', source: 'https://github.com/pnpm/pnpm.github.io/', - tags: [], + tags: ['opensource','i18n'], }, { title: 'Postgres.ai โ€“ Database Lab', - description: 'DEPLOY WITH CONFIDENCE. Been stung by a poorly tested database migration? We won\'t let it happen again.', + description: + "Deploy with confidence. Been stung by a poorly tested database migration? We won't let it happen again.", preview: require('./showcase/postgres-ai.png'), website: 'https://postgres.ai/', source: 'https://gitlab.com/postgres-ai/docs', - tags: ['large'], + tags: ['opensource', 'product'], }, { - title: 'Power\'s Wiki', + title: "Power's Wiki", description: 'An example of personal wiki ', preview: require('./showcase/power.png'), website: 'https://wiki-power.com/', source: 'https://github.com/linyuxuanlin/Wiki_Docusaurus', - tags: ['large', 'personal'], + tags: ['opensource', 'large', 'personal'], }, { title: 'PptxGenJS', @@ -490,7 +585,7 @@ const users = [ preview: require('./showcase/pptxgenjs.png'), website: 'https://gitbrent.github.io/PptxGenJS/', source: 'https://github.com/gitbrent/PptxGenJS', - tags: [], + tags: ['opensource','design'], }, { title: 'Profilo', @@ -498,7 +593,7 @@ const users = [ preview: require('./showcase/profolo.png'), website: 'https://facebookincubator.github.io/profilo/', source: 'https://github.com/facebookincubator/profilo', - tags: ['facebook'], + tags: ['opensource', 'facebook'], }, { title: 'Pyre', @@ -506,7 +601,7 @@ const users = [ preview: require('./showcase/pyre.png'), website: 'https://pyre-check.org', source: 'https://github.com/facebook/pyre-check', - tags: ['facebook', 'large'], + tags: ['opensource', 'facebook'], }, { title: 'QA-Board', @@ -515,7 +610,7 @@ const users = [ preview: require('./showcase/qa-board.png'), website: 'https://samsung.github.io/qaboard/', source: 'https://github.com/Samsung/qaboard', - tags: ['highlight', 'design', 'large'], + tags: ['opensource'], }, { title: 'QuantCDN', @@ -524,7 +619,7 @@ const users = [ preview: require('./showcase/quantcdn.png'), website: 'https://docs.quantcdn.io', source: 'https://github.com/quantcdn/docs', - tags: [], + tags: ['opensource','product'], }, { title: 'QuestDB', @@ -533,7 +628,7 @@ const users = [ preview: require('./showcase/questdb.png'), website: 'https://questdb.io', source: 'https://github.com/questdb/questdb.io', - tags: ['highlight', 'design', 'large'], + tags: ['opensource', 'favorite', 'design', 'large'], }, { title: 'RactivePlayer', @@ -541,7 +636,7 @@ const users = [ preview: require('./showcase/ractive-player.png'), website: 'https://ractive-player.org', source: 'https://github.com/ysulyma/ractive-player', - tags: [], + tags: ['opensource'], }, { title: 'React-Leaflet', @@ -549,7 +644,7 @@ const users = [ preview: require('./showcase/react-leaflet.png'), website: 'https://react-leaflet.js.org/', source: 'https://github.com/PaulLeCam/react-leaflet', - tags: [], + tags: ['opensource'], }, { title: 'React Native', @@ -557,15 +652,23 @@ const users = [ preview: require('./showcase/reactnative.png'), website: 'https://reactnative.dev', source: 'https://github.com/facebook/react-native-website', - tags: ['facebook', 'large', 'highlight', 'design', 'versioning'], + tags: [ + 'opensource', + 'facebook', + 'large', + 'favorite', + 'design', + 'versioning', + ], }, { title: 'React Native Boilerplate', - description: 'A React Native project template for building solid applications through separation of concerns between the UI, state management and business logic.', + description: + 'A React Native project template for building solid applications through separation of concerns between the UI, state management and business logic.', preview: require('./showcase/reactnativeboilerplate.png'), website: 'https://thecodingmachine.github.io/react-native-boilerplate/', source: 'https://github.com/thecodingmachine/react-native-boilerplate', - tags: [], + tags: ['opensource'], }, { title: 'React Native Elements', @@ -573,15 +676,15 @@ const users = [ preview: require('./showcase/react-native-elements.png'), website: 'https://react-native-training.github.io/react-native-elements/', source: 'https://github.com/react-native-elements/react-native-elements', - tags: ['large', 'highlight', 'design', 'versioning'], + tags: ['opensource'], }, { - title: 'react-native-ios-kit', + title: 'React Native iOS Kit', description: 'The missing React Native UI Kit for iOS.', preview: require('./showcase/react-native-ios-kit.png'), website: 'https://callstack.github.io/react-native-ios-kit', source: 'https://github.com/callstack/react-native-ios-kit', - tags: [], + tags: ['opensource'], }, { title: 'React Native Testing Library', @@ -589,7 +692,7 @@ const users = [ preview: require('./showcase/react-native-testing-library.png'), website: 'https://callstack.github.io/react-native-testing-library/', source: 'https://github.com/callstack/react-native-testing-library', - tags: [], + tags: ['opensource'], }, { title: 'React Redux', @@ -597,7 +700,7 @@ const users = [ preview: require('./showcase/react-redux.png'), website: 'https://react-redux.js.org', source: 'https://www.github.com/reduxjs/react-redux', - tags: ['highlight', 'large', 'versioning'], + tags: ['opensource'], }, { title: 'Redux', @@ -605,23 +708,25 @@ const users = [ preview: require('./showcase/redux.png'), website: 'https://redux.js.org/', source: 'https://www.github.com/reduxjs/redux', - tags: ['highlight', 'large'], + tags: ['opensource'], }, { title: 'React Native ARIA', - description: 'A library that provides accessible UI primitives for React Native apps.', + description: + 'A library that provides accessible UI primitives for React Native apps.', preview: require('./showcase/reactnative-aria.png'), website: 'https://react-native-aria.geekyants.com', source: 'https://github.com/GeekyAnts/react-native-aria-website', - tags: [], + tags: ['opensource'], }, { title: 'Realtime Web Applications Workshop', - description: 'A workshop about building realtime web applications with WebSockets and WebRTC.', + description: + 'A workshop about building realtime web applications with WebSockets and WebRTC.', preview: require('./showcase/realtime-apps-workshop.png'), website: 'https://realtime-apps-iap.github.io', source: 'https://github.com/realtime-apps-iap/realtime-apps-iap.github.io', - tags: [], + tags: ['opensource'], }, { title: 'Redis Labs Developer Site', @@ -629,15 +734,16 @@ const users = [ preview: require('./showcase/redis-developer.png'), website: 'https://developer.redislabs.com', source: 'https://github.com/redis-developer/redis-developer.github.io', - tags: [], + tags: ['opensource','product'], }, { title: 'Rematch', - description: 'Redux best practices without the boilerplate in less than 2kb', + description: + 'Redux best practices without the boilerplate in less than 2kb', preview: require('./showcase/rematch.png'), website: 'https://rematchjs.org', source: 'https://github.com/rematch/rematch', - tags: ['highlight', 'design', 'large'], + tags: ['opensource', 'design'], }, { title: 'Repeater.js', @@ -645,15 +751,16 @@ const users = [ preview: require('./showcase/repeaterjs.png'), website: 'https://repeater.js.org/', source: 'https://github.com/repeaterjs/repeater', - tags: [], + tags: ['opensource'], }, { title: 'Rooks', - description: 'Supercharge your components with this collection of React hooks.', + description: + 'Supercharge your components with this collection of React hooks.', preview: require('./showcase/rooks.png'), website: 'https://react-hooks.org/', source: 'https://github.com/imbhargav5/rooks', - tags: ['large', 'versioning'], + tags: ['opensource', 'versioning'], }, { title: 'RSocket', @@ -661,7 +768,7 @@ const users = [ preview: require('./showcase/rsocket.png'), website: 'https://rsocket.io/', source: 'https://github.com/rsocket/rsocket-website', - tags: [], + tags: ['opensource'], }, { title: 'Runlet', @@ -669,7 +776,7 @@ const users = [ preview: require('./showcase/runlet.png'), website: 'https://runlet.app', source: 'https://github.com/runletapp/website', - tags: ['design'], + tags: ['opensource'], }, { title: 'Saleor', @@ -677,7 +784,7 @@ const users = [ preview: require('./showcase/saleor.png'), website: 'https://docs.getsaleor.com/', source: 'https://github.com/mirumee/saleor-docs', - tags: ['design', 'large', 'versioning'], + tags: ['opensource', 'product', 'versioning'], }, { title: 'SCI WP Framework', @@ -685,38 +792,41 @@ const users = [ preview: require('./showcase/sciwp.png'), website: 'https://sciwp.com/', source: 'https://github.com/sciwp/sciwp-framework', - tags: ['highlight', 'design', 'large'], + tags: ['opensource'], }, { - title: 'single-spa', + title: 'Single SPA', description: 'A javascript router for front-end microservices', preview: require('./showcase/single-spa.png'), website: 'https://single-spa.js.org/', source: 'https://github.com/single-spa/single-spa', - tags: ['highlight', 'large', 'versioning', 'i18n'], + tags: ['opensource', 'large', 'versioning', 'i18n'], }, { title: 'smash.gg', description: 'Turning passions into careers', preview: require('./showcase/smashgg.png'), website: 'https://developer.smash.gg', - tags: ['large'], + source: 'https://github.com/smashgg/developer-portal', + tags: ['opensource','product'], }, { title: 'Shabad OS Docs', - description: 'Browse the latest docs, including tutorial guides, sample code, product articles, and API references', + description: + 'Browse the latest docs, including tutorial guides, sample code, product articles, and API references', preview: require('./showcase/shabados.png'), website: 'https://docs.shabados.com', source: 'https://github.com/shabados/docs', - tags: ['design'], + tags: ['opensource', 'design'], }, { title: 'social-embed', - description: 'Drop-in replacement for embed-friendly websites (and ร  la carte APIs for detecting and parsing them)', + description: + 'Drop-in replacement for embed-friendly websites (and ร  la carte APIs for detecting and parsing them)', preview: require('./showcase/social-embed.png'), website: 'https://social-embed.git-pull.com/', source: 'https://github.com/social-embed/social-embed', - tags: [], + tags: ['opensource'], }, { title: 'SpotifyAPI-NET', @@ -724,7 +834,7 @@ const users = [ preview: require('./showcase/spotifyapi-net.png'), website: 'https://johnnycrazy.github.io/SpotifyAPI-NET/', source: 'https://github.com/JohnnyCrazy/SpotifyAPI-NET', - tags: ['versioning'], + tags: ['opensource', 'versioning'], }, { title: 'Stryker Mutator', @@ -732,7 +842,7 @@ const users = [ preview: require('./showcase/stryker-mutator.png'), website: 'https://stryker-mutator.io', source: 'https://github.com/stryker-mutator/stryker-mutator.github.io', - tags: [], + tags: ['opensource'], }, { title: 'Stylable', @@ -740,7 +850,7 @@ const users = [ preview: require('./showcase/stylable.png'), website: 'https://stylable.io', source: 'https://github.com/wixplosives/stylable.io', - tags: [], + tags: ['opensource'], }, { title: 'Supabase', @@ -748,7 +858,7 @@ const users = [ preview: require('./showcase/supabase.png'), website: 'https://www.supabase.io/', source: 'https://github.com/supabase/monorepo', - tags: ['highlight', 'design', 'large'], + tags: ['opensource', 'favorite', 'design', 'large','product'], }, { title: 'T-Regx', @@ -756,7 +866,7 @@ const users = [ preview: require('./showcase/t-regx.png'), website: 'https://t-regx.com/', source: 'https://github.com/T-Regx/T-Regx', - tags: ['design', 'large'], + tags: ['opensource'], }, { title: 'Taro', @@ -764,15 +874,16 @@ const users = [ preview: require('./showcase/docs-taro-zone.png'), website: 'https://docs.taro.zone/', source: 'https://github.com/NervJS/taro', - tags: ['versioning', 'large'], + tags: ['opensource', 'versioning', 'large'], }, { title: 'Testing Library', - description: 'Simple and complete testing utilities that encourage good testing practices', + description: + 'Simple and complete testing utilities that encourage good testing practices', preview: require('./showcase/testing-library.png'), website: 'https://testing-library.com/', source: 'https://github.com/testing-library/testing-library-docs', - tags: ['design', 'large'], + tags: ['opensource'], }, { title: 'Tasit', @@ -781,41 +892,31 @@ const users = [ preview: require('./showcase/tasit.png'), website: 'https://docs.tasit.io/', source: 'https://github.com/tasitlabs/tasit-sdk', - tags: [], + tags: ['opensource'], }, { title: 'The Diff Podcast', description: 'A Podcast from Facebook Open Source', preview: require('./showcase/the-diff.png'), website: 'https://thediffpodcast.com', - tags: ['facebook'], - }, - { - title: 'Tourmaline', - description: - 'Fast and performant Telegram bot framework for the Crystal programming language', - preview: require('./showcase/tourmaline.png'), - website: 'https://tourmaline.dev', - source: 'https://github.com/protoncr/tourmaline', - tags: [], + source: null, + tags: [ 'facebook'], }, { title: 'TRPG Engine', - description: - 'IM Application which build for TRPG, like slack and discord', + description: 'IM Application which build for TRPG, like slack and discord', preview: require('./showcase/trpgengine.png'), website: 'https://trpgdoc.moonrailgun.com/', source: 'https://github.com/TRPGEngine/Client', - tags: [], + tags: ['opensource'], }, { title: 'Tuist', - description: - 'A tool to maintain and interact with Xcode projects at scale', + description: 'A tool to maintain and interact with Xcode projects at scale', preview: require('./showcase/tuist.png'), website: 'https://docs.tuist.io/', source: 'https://github.com/tuist/tuist', - tags: [], + tags: ['opensource'], }, { title: 'uniforms', @@ -823,7 +924,7 @@ const users = [ preview: require('./showcase/uniforms.png'), website: 'https://uniforms.tools/', source: 'https://github.com/vazco/uniforms', - tags: ['design', 'large', 'highlight'], + tags: ['opensource'], }, { title: 'Vector', @@ -831,7 +932,7 @@ const users = [ preview: require('./showcase/vector.png'), website: 'https://vector.dev/', source: 'https://github.com/timberio/vector', - tags: ['highlight', 'design', 'large'], + tags: ['opensource', 'favorite', 'design', 'large'], }, { title: 'Vue NodeGui', @@ -839,7 +940,7 @@ const users = [ preview: require('./showcase/vue-nodegui.png'), website: 'https://vue.nodegui.org/', source: 'https://github.com/nodegui/vue-nodegui', - tags: ['design'], + tags: ['opensource'], }, { title: 'Wasp', @@ -848,7 +949,7 @@ const users = [ preview: require('./showcase/wasp.png'), website: 'https://wasp-lang.dev/', source: 'https://github.com/wasp-lang/wasp', - tags: ['highlight', 'design', 'large'], + tags: ['opensource'], }, { title: 'WebdriverIO', @@ -857,7 +958,7 @@ const users = [ preview: require('./showcase/webdriverio.png'), website: 'https://webdriver.io/', source: 'https://github.com/webdriverio/webdriverio', - tags: ['design', 'large'], + tags: ['opensource', 'design', 'large','favorite'], }, { title: 'Wisdom', @@ -865,28 +966,134 @@ const users = [ preview: require('./showcase/wisdom.png'), website: 'https://developers.getwisdom.io/', source: 'https://github.com/Wisdom/dev-docs', - tags: ['design', 'large'], + tags: ['opensource', 'design', 'product'], }, { title: 'KubeVela', - description: 'KubeVela is a modern application engine that adapts to your application\'s needs, not the other way around.', + description: + "KubeVela is a modern application engine that adapts to your application's needs, not the other way around.", preview: require('./showcase/kubevela.png'), website: 'https://kubevela.io/', source: 'https://github.com/oam-dev/kubevela.io', - tags: ['versioning', 'i18n'], + tags: ['opensource', 'versioning', 'i18n'], }, ]; -users.forEach((user) => { - if ( - !user.preview || - (user.preview instanceof String && - (user.preview.startsWith('http') || user.preview.startsWith('//'))) - ) { +export const TagList = Object.keys(Tags); +function sortUsers() { + let result = Users; + // Sort by site name + result = sortBy(result, (user) => user.title.toLowerCase()); + // Sort by favorite tag, favorites first + result = sortBy(result, (user) => !user.tags.includes('favorite')); + return result; +} + +export const SortedUsers = sortUsers(); + +// Fail-fast on common errors +function ensureUserValid(user) { + function checkFields() { + const keys = Object.keys(user); + const validKeys = [ + 'title', + 'description', + 'preview', + 'website', + 'source', + 'tags', + ]; + const unknownKeys = difference(keys, validKeys); + if (unknownKeys.length > 0) { + throw new Error( + `Site contains unknown attribute names=[${unknownKeys.join(',')}]`, + ); + } + } + + function checkTitle() { + if (!user.title) { + throw new Error('Site title is missing'); + } + } + + function checkDescription() { + if (!user.description) { + throw new Error('Site description is missing'); + } + } + + function checkWebsite() { + if (!user.website) { + throw new Error('Site website is missing'); + } + const isHttpUrl = + user.website.startsWith('http://') || user.website.startsWith('https://'); + if (!isHttpUrl) { + throw new Error( + `Site website does not look like a valid url: ${user.website}`, + ); + } + } + + function checkPreview() { + if ( + !user.preview || + (user.preview instanceof String && + (user.preview.startsWith('http') || user.preview.startsWith('//'))) + ) { + throw new Error( + `Site has bad image preview=[${user.preview}].\nThe image should be hosted on Docusaurus site, and not use remote HTTP or HTTPS URLs`, + ); + } + } + + function checkTags() { + if (!user.tags || !(user.tags instanceof Array) || user.tags.includes('')) { + throw new Error(`Bad showcase tags=[${JSON.stringify(user.tags)}]`); + } + const unknownTags = difference(user.tags, TagList); + if (unknownTags.length > 0) { + throw new Error( + `Unknown tags=[${unknownTags.join( + ',', + )}\nThe available tags are ${TagList.join(',')}`, + ); + } + } + + function checkOpenSource() { + if (typeof user.source === 'undefined') { + throw new Error( + "The source attribute is required.\nIf your Docusaurus site is not open-source, please make it explicit with 'source: null'", + ); + } else { + const hasOpenSourceTag = user.tags.includes('opensource'); + if (user.source === null && hasOpenSourceTag) { + throw new Error( + "You can't add the opensource tag to a site that does not have a link to source code.", + ); + } else if (user.source && !hasOpenSourceTag) { + throw new Error( + "For open-source sites, please add the 'opensource' tag", + ); + } + } + } + + try { + checkFields(); + checkTitle(); + checkDescription(); + checkWebsite(); + checkPreview(); + checkTags(); + checkOpenSource(); + } catch (e) { throw new Error( - `Bad user site image preview = ${user.preview}. The image should be hosted on Docusaurus site, and not use remote HTTP or HTTPS URLs`, + `Showcase site with title=${user.title} contains errors:\n${e.message}`, ); } -}); +} -export default users; +Users.forEach(ensureUserValid); diff --git a/website/src/pages/showcase/index.js b/website/src/pages/showcase/index.js index 411c866536..8778ac8477 100644 --- a/website/src/pages/showcase/index.js +++ b/website/src/pages/showcase/index.js @@ -5,82 +5,190 @@ * LICENSE file in the root directory of this source tree. */ -import React from 'react'; +import React, {useState, useMemo, useCallback, useEffect} from 'react'; -import Image from '@theme/IdealImage'; import Layout from '@theme/Layout'; - +import ShowcaseCheckbox from '@site/src/components/showcase/ShowcaseCheckbox'; +import ShowcaseSelect from '@site/src/components/showcase/ShowcaseSelect'; +import ShowcaseCard from '@site/src/components/showcase/ShowcaseCard'; import clsx from 'clsx'; -import styles from './styles.module.css'; -import users from '../../data/users'; -const TITLE = 'Showcase'; -const DESCRIPTION = - 'See the awesome websites people are building with Docusaurus'; +import {useHistory, useLocation} from '@docusaurus/router'; + +import {toggleListItem} from '../../utils/jsUtils'; +import {SortedUsers, Tags, TagList} from '../../data/users'; + +const TITLE = 'Docusaurus Site Showcase'; +const DESCRIPTION = 'List of websites people are building with Docusaurus'; const EDIT_URL = 'https://github.com/facebook/docusaurus/edit/master/website/src/data/users.js'; +function filterUsers(users, selectedTags, operator) { + if (selectedTags.length === 0) { + return users; + } + return users.filter((user) => { + if (user.tags.length === 0) { + return false; + } + if (operator === 'AND') { + return selectedTags.every((tag) => user.tags.includes(tag)); + } else { + return selectedTags.some((tag) => user.tags.includes(tag)); + } + }); +} + +function useFilteredUsers(users, selectedTags, operator) { + return useMemo(() => filterUsers(users, selectedTags, operator), [ + users, + selectedTags, + operator, + ]); +} + +const TagQueryStringKey = 'tags'; + +function readSearchTags(search) { + return new URLSearchParams(search).getAll(TagQueryStringKey); +} + +function replaceSearchTags(search, newTags) { + const searchParams = new URLSearchParams(search); + searchParams.delete(TagQueryStringKey); + newTags.forEach((tag) => searchParams.append(TagQueryStringKey, tag)); + return searchParams.toString(); +} + +function useSelectedTags() { + // The search query-string is the source of truth! + const location = useLocation(); + const {push} = useHistory(); + + // On SSR / first mount (hydration) no tag is selected + const [selectedTags, setSelectedTags] = useState([]); + + // Sync tags from QS to state (delayed on purpose to avoid SSR/Client hydration mismatch) + useEffect(() => { + const tags = readSearchTags(location.search); + setSelectedTags(tags); + }, [location, setSelectedTags]); + + // Update the QS value + const toggleTag = useCallback( + (tag) => { + const tags = readSearchTags(location.search); + const newTags = toggleListItem(tags, tag); + const newSearch = replaceSearchTags(location.search, newTags); + push({...location, search: newSearch}); + // no need to call setSelectedTags, useEffect will do the sync + }, + [location, push], + ); + + return {selectedTags, toggleTag}; +} + +function ShowcaseHeader() { + return ( +
+

{TITLE}

+

{DESCRIPTION}

+

+ + ๐Ÿ™ Add your site now! + +

+
+ ); +} + +function ShowcaseFilters({selectedTags, toggleTag, operator, setOperator}) { + return ( +
+
+ {TagList.map((tag) => { + const {label, description, icon} = Tags[tag]; + return ( +
+ + {icon} {label} + + ) : ( + label + ) + } + onChange={() => toggleTag(tag)} + checked={selectedTags.includes(tag)} + /> +
+ ); + })} +
+ setOperator(e.target.value)}> + + + +
+
+
+ ); +} + +function ShowcaseCards({filteredUsers}) { + return ( +
+

+ {filteredUsers.length} site{filteredUsers.length > 1 ? 's' : ''} +

+
+ {filteredUsers.length > 0 ? ( +
+ {filteredUsers.map((user) => ( + + ))} +
+ ) : ( +
+

No result

+
+ )} +
+
+ ); +} + function Showcase() { + const {selectedTags, toggleTag} = useSelectedTags(); + const [operator, setOperator] = useState('OR'); + const filteredUsers = useFilteredUsers(SortedUsers, selectedTags, operator); return (
-
-

{TITLE}

-

{DESCRIPTION}

-

- - Add your site! - -

-
-
- {users.map((user) => ( -
-
-
- {user.title} -
-
-
-
-

{user.title}

- - {user.description} - -
-
-
- {(user.website || user.source) && ( -
-
- {user.website && ( - - Website - - )} - {user.source && ( - - Source - - )} -
-
- )} -
-
- ))} -
+ + +
); diff --git a/website/src/pages/showcase/styles.module.css b/website/src/pages/showcase/styles.module.css index b301086c7f..b5c0e33b4a 100644 --- a/website/src/pages/showcase/styles.module.css +++ b/website/src/pages/showcase/styles.module.css @@ -4,12 +4,3 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ - -.showcaseUser { - height: 100%; -} - -:global(.card__image) { - max-height: 175px; - overflow: hidden; -} diff --git a/website/src/utils/jsUtils.js b/website/src/utils/jsUtils.js new file mode 100644 index 0000000000..6ea70d9e5a --- /dev/null +++ b/website/src/utils/jsUtils.js @@ -0,0 +1,27 @@ +// Inspired by https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_difference +export function difference(...arrays) { + return arrays.reduce((a, b) => a.filter((c) => !b.includes(c))); +} + +// Inspired by https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_sortby-and-_orderby +export function sortBy(array, getter) { + function compareBy(getter) { + return (a, b) => + getter(a) > getter(b) ? 1 : getter(b) > getter(a) ? -1 : 0; + } + + const sortedArray = [...array]; + sortedArray.sort(compareBy(getter)); + return sortedArray; +} + +export function toggleListItem(list, item) { + const itemIndex = list.indexOf(item); + if (itemIndex === -1) { + return list.concat(item); + } else { + const newList = [...list]; + newList.splice(itemIndex, 1); + return newList; + } +}