diff --git a/.bin/b.yaml b/.bin/b.yaml index 1efdecb..b373784 100644 --- a/.bin/b.yaml +++ b/.bin/b.yaml @@ -1,2 +1,5 @@ -b: -jq: \ No newline at end of file + +binaries: + jq: + version: jq-1.7 + kubectl: {} \ No newline at end of file diff --git a/.envrc b/.envrc index 0ca6f46..2510a91 100644 --- a/.envrc +++ b/.envrc @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Template form: -# https://github.com/arg-sh/argsh +# https://github.com/fentas/b # curl -sfLS https://envrc.arg.sh > .envrc # This is an example of a .envrc file for use with direnv. # It sets up the environment for the project. diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..f61c2d4 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,67 @@ +name: Deploy Documentation + +on: + push: + branches: [ main ] + paths: + - 'www/apps/docs/**' + - '.github/workflows/docs.yml' + pull_request: + branches: [ main ] + paths: + - 'www/apps/docs/**' + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'yarn' + cache-dependency-path: www/yarn.lock + + - name: Install dependencies + run: | + cd www + yarn install + + - name: Build Docusaurus site + run: | + cd www + yarn run build + + - name: Setup Pages + if: github.ref == 'refs/heads/main' + uses: actions/configure-pages@v4 + + - name: Upload artifact + if: github.ref == 'refs/heads/main' + uses: actions/upload-pages-artifact@v3 + with: + path: www/apps/docs/build + + deploy: + if: github.ref == 'refs/heads/main' + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/README.md b/README.md index 5e81f88..0ede20f 100644 --- a/README.md +++ b/README.md @@ -73,11 +73,12 @@ If none of these are set, `b` will fail. To properly use the `--all` flag, you should create a `b.yaml` file in the binary directory. This file should contain a list of binaries you want to manage. Here is an example: ```yaml -jq: - # pin version - version: jq-1.8.1 -kind: -tilt: +binaries: + jq: + # pin version + version: jq-1.8.1 + kind: + tilt: ``` This will ensure that `jq`, `kind`, and `tilt` are installed and at the correct version. If you don't specify a version, `b` will install the latest version. @@ -119,7 +120,7 @@ Have a look at [pkg/binary](./pkg/binary/) for more details. Have a look at [pkg/binaries](./pkg/binaries/) for prepackaged binaries. -- [argsh](https://github.com/arg-sh/argsh) - Utilities for Bash script quality +- [argsh](https://github.com/fentas/b) - Utilities for Bash script quality - `b` - (Selfupdate) Manage and execute binary files - [cilium](https://github.com/cilium/cilium-cli) - Providing, securing, and observing network connectivity between workloads - [clusterctl](https://github.com/kubernetes-sigs/cluster-api) - Kubernetes cluster lifecycle management diff --git a/logo.svg b/logo.svg new file mode 100644 index 0000000..beefd6c --- /dev/null +++ b/logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pkg/binaries/argsh/argsh.go b/pkg/binaries/argsh/argsh.go index 63b171d..5f22029 100644 --- a/pkg/binaries/argsh/argsh.go +++ b/pkg/binaries/argsh/argsh.go @@ -20,7 +20,7 @@ func Binary(options *binaries.BinaryOptions) *binary.Binary { Tracker: options.Tracker, Version: options.Version, Name: "argsh", - GitHubRepo: "arg-sh/argsh", + GitHubRepo: "fentas/b", GitHubFile: "argsh", VersionF: binary.GithubLatest, VersionLocalF: func(b *binary.Binary) (string, error) { diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index d9cddc4..c0f1785 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -21,7 +21,7 @@ type CmdBinaryOptions struct { IO *streams.IO Binaries []*binary.Binary NoConfig bool - config *state.BinaryList + config *state.State // Flags all bool @@ -152,7 +152,7 @@ func (o *CmdBinaryOptions) Complete(cmd *cobra.Command, args []string) error { } if o.config != nil { - for _, lb := range *o.config { + for _, lb := range o.config.Binaries { for b, do := range o.ensure { if lb.Name == b.Name { b.Version = lb.Version diff --git a/pkg/cli/init.go b/pkg/cli/init.go index 848a0f2..2b50bac 100644 --- a/pkg/cli/init.go +++ b/pkg/cli/init.go @@ -9,7 +9,6 @@ import ( "github.com/fentas/goodies/templates" "github.com/spf13/cobra" - "github.com/fentas/b/pkg/binary" "github.com/fentas/b/pkg/path" "github.com/fentas/b/pkg/state" ) @@ -96,15 +95,7 @@ func (o *InitOptions) createConfigWithSelfReference(configPath string) error { if err := os.MkdirAll(dir, 0755); err != nil { return err } - - // Create config with self-reference to 'b' - config := &state.BinaryList{ - &binary.LocalBinary{ - Name: "b", - }, - } - - return state.SaveConfig(config, configPath) + return state.CreateDefaultConfig(configPath) } // createProjectFiles creates additional project files (.gitignore, .envrc) if needed diff --git a/pkg/cli/install.go b/pkg/cli/install.go index 0dcada1..d308455 100644 --- a/pkg/cli/install.go +++ b/pkg/cli/install.go @@ -187,20 +187,20 @@ func (o *InstallOptions) addToConfig(binaries []*binary.Binary) error { // Load existing config or create new one config := o.Config if config == nil { - config = &state.BinaryList{} + config = &state.State{} } // Add binaries to config for _, b := range binaries { // Check if already exists found := false - for i, existing := range *config { + for i, existing := range config.Binaries { if existing.Name == b.Name { // Update version only if we have a specific version if b.Version != "" && b.Version != "latest" { - (*config)[i].Version = b.Version + config.Binaries[i].Version = b.Version if o.Fix { - (*config)[i].Enforced = b.Version + config.Binaries[i].Enforced = b.Version } } found = true @@ -219,7 +219,7 @@ func (o *InstallOptions) addToConfig(binaries []*binary.Binary) error { entry.Enforced = b.Version } } - *config = append(*config, entry) + config.Binaries = append(config.Binaries, entry) } } diff --git a/pkg/cli/shared.go b/pkg/cli/shared.go index 7c99f46..f72738f 100644 --- a/pkg/cli/shared.go +++ b/pkg/cli/shared.go @@ -13,7 +13,7 @@ import ( type SharedOptions struct { IO *streams.IO Binaries []*binary.Binary - Config *state.BinaryList + Config *state.State // Global flags ConfigPath string @@ -75,7 +75,7 @@ func (o *SharedOptions) GetBinariesFromConfig() []*binary.Binary { } var result []*binary.Binary - for _, lb := range *o.Config { + for _, lb := range o.Config.Binaries { if b, ok := o.lookup[lb.Name]; ok { // Set version from config b.Version = lb.Version diff --git a/pkg/cli/version_test.go b/pkg/cli/version_test.go index 38fab8a..dd4d33a 100644 --- a/pkg/cli/version_test.go +++ b/pkg/cli/version_test.go @@ -97,7 +97,7 @@ func TestVersionOptions_Validate(t *testing.T) { func TestVersionOptions_Run(t *testing.T) { tests := []struct { name string - config *state.BinaryList + config *state.State local bool check bool quiet bool @@ -106,10 +106,12 @@ func TestVersionOptions_Run(t *testing.T) { }{ { name: "run with config", - config: &state.BinaryList{ - &binary.LocalBinary{ - Name: "jq", - Version: "1.7", + config: &state.State{ + Binaries: state.BinaryList{ + &binary.LocalBinary{ + Name: "jq", + Version: "1.7", + }, }, }, local: false, @@ -118,10 +120,12 @@ func TestVersionOptions_Run(t *testing.T) { }, { name: "run local only", - config: &state.BinaryList{ - &binary.LocalBinary{ - Name: "jq", - Version: "1.7", + config: &state.State{ + Binaries: state.BinaryList{ + &binary.LocalBinary{ + Name: "jq", + Version: "1.7", + }, }, }, local: true, diff --git a/pkg/state/config.go b/pkg/state/config.go index b0f6415..bf378da 100644 --- a/pkg/state/config.go +++ b/pkg/state/config.go @@ -10,7 +10,7 @@ import ( ) // LoadConfigFromPath loads configuration from a specific path -func LoadConfigFromPath(configPath string) (*BinaryList, error) { +func LoadConfigFromPath(configPath string) (*State, error) { if _, err := os.Stat(configPath); err != nil { return nil, err } @@ -20,16 +20,16 @@ func LoadConfigFromPath(configPath string) (*BinaryList, error) { return nil, err } - var list BinaryList - if err := yaml.Unmarshal(config, &list); err != nil { + var state State + if err := yaml.Unmarshal(config, &state); err != nil { return nil, err } - return &list, nil + return &state, nil } // LoadConfig loads configuration with automatic discovery -func LoadConfig() (*BinaryList, error) { +func LoadConfig() (*State, error) { configPath, err := path.FindConfigFile() if err != nil { return nil, err @@ -43,7 +43,7 @@ func LoadConfig() (*BinaryList, error) { } // SaveConfig saves the configuration to the specified path -func SaveConfig(config *BinaryList, configPath string) error { +func SaveConfig(config *State, configPath string) error { // Ensure directory exists dir := filepath.Dir(configPath) if err := os.MkdirAll(dir, 0755); err != nil { @@ -60,9 +60,11 @@ func SaveConfig(config *BinaryList, configPath string) error { // CreateDefaultConfig creates a default b.yaml configuration file func CreateDefaultConfig(configPath string) error { - defaultConfig := BinaryList{ - &binary.LocalBinary{ - Name: "b", + defaultConfig := State{ + Binaries: BinaryList{ + &binary.LocalBinary{ + Name: "b", + }, }, } return SaveConfig(&defaultConfig, configPath) diff --git a/pkg/state/types.go b/pkg/state/types.go index a350dae..4ed69d8 100644 --- a/pkg/state/types.go +++ b/pkg/state/types.go @@ -1,9 +1,39 @@ +// Package state provides state management for b package state import ( "github.com/fentas/b/pkg/binary" ) +type State struct { + Binaries BinaryList `yaml:"binaries"` +} + +// MarshalYAML implements the yaml.Marshaler interface for State +func (s *State) MarshalYAML() (interface{}, error) { + binaries, err := s.Binaries.MarshalYAML() + if err != nil { + return nil, err + } + + return map[string]interface{}{ + "binaries": binaries, + }, nil +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface for State +func (s *State) UnmarshalYAML(unmarshal func(interface{}) error) error { + type Alias State + var aux Alias + + if err := unmarshal(&aux); err != nil { + return err + } + + *s = State(aux) + return nil +} + type BinaryList []*binary.LocalBinary func (list *BinaryList) UnmarshalYAML(unmarshal func(interface{}) error) error { @@ -27,15 +57,12 @@ func (list *BinaryList) MarshalYAML() (interface{}, error) { result := make(map[string]interface{}) for _, b := range *list { if b.Name != "" { - // Only include version if it's set and not empty - if b.Version != "" && b.Version != "latest" { - // Create a simple map with only the version field + if b.Enforced != "" { result[b.Name] = map[string]string{ - "version": b.Version, + "version": b.Enforced, } } else { - // Just the key with no value (null) - result[b.Name] = nil + result[b.Name] = &struct{}{} } } } diff --git a/test/TESTING_PLAN.md b/test/TESTING_PLAN.md index 68e0897..270a2b4 100644 --- a/test/TESTING_PLAN.md +++ b/test/TESTING_PLAN.md @@ -1,4 +1,4 @@ -# Testing Plan for B CLI Tool +# Testing Plan for b CLI Tool ## Overview diff --git a/test/e2e/main_test.go b/test/e2e/main_test.go index 46aa8b9..29f16f1 100644 --- a/test/e2e/main_test.go +++ b/test/e2e/main_test.go @@ -99,7 +99,7 @@ func TestE2E_InitWorkflow(t *testing.T) { } configStr := string(configContent) - if !strings.Contains(configStr, "b: null") { + if !strings.Contains(configStr, "b: {}") { t.Error("Config file does not contain self-reference to 'b' binary") } @@ -129,10 +129,11 @@ func TestE2E_ConfigDiscovery(t *testing.T) { } configPath := filepath.Join(configDir, "b.yaml") - configContent := []byte(`jq: - version: "1.7" -kubectl: - version: "latest" + configContent := []byte(` +binaries: + jq: + version: "jq-1.7" + kubectl: {} `) err = os.WriteFile(configPath, configContent, 0644) if err != nil { @@ -140,7 +141,7 @@ kubectl: } // Build the CLI - binaryPath := filepath.Join(os.TempDir(), "b-e2e-test") + binaryPath := filepath.Join(tempDir, "b-e2e-test") defer os.Remove(binaryPath) cmd := exec.Command("go", "build", "-o", binaryPath, "./cmd/b") diff --git a/test/testutil/helpers.go b/test/testutil/helpers.go index 2b964ce..c35febc 100644 --- a/test/testutil/helpers.go +++ b/test/testutil/helpers.go @@ -170,7 +170,7 @@ func ChangeDir(t *testing.T, dir string) { } // CreateTestProject creates a test project structure -func CreateTestProject(t *testing.T, config *state.BinaryList) string { +func CreateTestProject(t *testing.T, config *state.State) string { t.Helper() dir := TempDir(t) diff --git a/www/.eslintrc.js b/www/.eslintrc.js new file mode 100644 index 0000000..7850080 --- /dev/null +++ b/www/.eslintrc.js @@ -0,0 +1,10 @@ +module.exports = { + root: true, + // This tells ESLint to load the config from the package `eslint-config-docs` + extends: ["docs"], + settings: { + next: { + rootDir: ["apps/*/"], + }, + }, +}; \ No newline at end of file diff --git a/www/.gitignore b/www/.gitignore new file mode 100644 index 0000000..8e0d006 --- /dev/null +++ b/www/.gitignore @@ -0,0 +1,11 @@ +.vercel +build +node_modules +.yarn/* +.yarn/install-state.gz +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions +.turbo diff --git a/www/.prettierrc b/www/.prettierrc new file mode 100644 index 0000000..aa4d695 --- /dev/null +++ b/www/.prettierrc @@ -0,0 +1,8 @@ +{ + "endOfLine": "auto", + "semi": false, + "singleQuote": false, + "tabWidth": 2, + "trailingComma": "es5", + "arrowParens": "always" +} diff --git a/www/.yarnrc.yml b/www/.yarnrc.yml new file mode 100644 index 0000000..1b1262b --- /dev/null +++ b/www/.yarnrc.yml @@ -0,0 +1 @@ +nmMode: hardlinks-local \ No newline at end of file diff --git a/www/README.md b/www/README.md new file mode 100644 index 0000000..004d40c --- /dev/null +++ b/www/README.md @@ -0,0 +1,11 @@ +# Web precense + +This is based on https://github.com/fentas/b/tree/develop/www ❤️ + +## Documentation Resources + +This directory holds the code and content of both the [documentation](https://arg.sh/) and [API reference](https://arg.sh/api/admin). + +## docs Directory + +The `docs` directory holds the code and content of the documentation website. The website is built with [Docusaurus v2](https://docusaurus.io/). \ No newline at end of file diff --git a/www/apps/docs/.content.eslintrc.js b/www/apps/docs/.content.eslintrc.js new file mode 100644 index 0000000..fc843a3 --- /dev/null +++ b/www/apps/docs/.content.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + root: true, + extends: [ + "docs/content" + ], +} diff --git a/www/apps/docs/.env.sample b/www/apps/docs/.env.sample new file mode 100644 index 0000000..c681af3 --- /dev/null +++ b/www/apps/docs/.env.sample @@ -0,0 +1,10 @@ +API_URL= +DOCS_ALGOLIA_INDEX_NAME= +API_ALGOLIA_INDEX_NAME= +ALGOLIA_API_KEY= +ALGOLIA_APP_ID= +AI_ASSISTANT_URL= +AI_API_ASSISTANT_TOKEN= +AI_WEBSITE_ID= +AI_API_ASSISTANT_RECAPTCHA_SITE_KEY= +CLOUDINARY_CLOUD_NAME= \ No newline at end of file diff --git a/www/apps/docs/.eslintrc.js b/www/apps/docs/.eslintrc.js new file mode 100644 index 0000000..a910013 --- /dev/null +++ b/www/apps/docs/.eslintrc.js @@ -0,0 +1,8 @@ +module.exports = { + root: true, + extends: ["docs/docusaurus"], + parserOptions: { + project: true, + tsconfigRootDir: __dirname, + }, +} diff --git a/www/apps/docs/.gitignore b/www/apps/docs/.gitignore new file mode 100644 index 0000000..bfab69e --- /dev/null +++ b/www/apps/docs/.gitignore @@ -0,0 +1,29 @@ +# Dependencies +/node_modules + +# Production +/build + +# Generated files +.docusaurus +.cache-loader + +# Misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.yarn/* +.yarn/install-state.gz +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +.turbo \ No newline at end of file diff --git a/www/apps/docs/README.md b/www/apps/docs/README.md new file mode 100644 index 0000000..df6bed7 --- /dev/null +++ b/www/apps/docs/README.md @@ -0,0 +1,13 @@ +# Medusa Documentation + +The Medusa documentation website is built with Docusaurus v2. You can learn more about contributing [here](https://arg.sh/contribution-guidelines). + +## Components and Theme + +### Extended Theme Components + +Components that extend themes from Docusaurus or other plugins are placed under `src/theme`. + +### Medusa Docs Components + +Components created specifically for the Medusa Docs are placed under the `src/componenets` directory. diff --git a/www/apps/docs/announcement.json b/www/apps/docs/announcement.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/www/apps/docs/announcement.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/www/apps/docs/babel.config.js b/www/apps/docs/babel.config.js new file mode 100644 index 0000000..62aaab6 --- /dev/null +++ b/www/apps/docs/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [require.resolve("@docusaurus/core/lib/babel/preset")], + plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]], +} diff --git a/www/apps/docs/content/about.md b/www/apps/docs/content/about.md new file mode 100644 index 0000000..7d4a970 --- /dev/null +++ b/www/apps/docs/content/about.md @@ -0,0 +1,47 @@ +# About b + + + + + +**b** is a modern binary manager designed to simplify how developers install, manage, and version their command-line tools. + +## Purpose + +`b` was created to solve the common problems developers face when managing development tools across different projects and environments: + +- **Version Conflicts**: Different projects requiring different tool versions +- **Environment Setup**: Complex installation and PATH management +- **Team Consistency**: Ensuring all team members use the same tool versions +- **CI/CD Integration**: Reproducible builds with exact tool dependencies + +## Open Source + +`b` is an open-source project available on [GitHub](https://github.com/fentas/b). We welcome contributions from the community. + +This documentation is built with [Docusaurus](https://docusaurus.io/) and uses design elements inspired by the [Medusa](https://github.com/medusajs/medusa) project. + +## Project Information + +- **Repository**: [github.com/fentas/b](https://github.com/fentas/b) +- **License**: MIT License +- **Language**: Go +- **Maintainer**: fentas + +## Contributing + +We welcome contributions! Please see our [contribution guidelines](./contribution/docs) for more information on how to get involved. + +## Support + +If you encounter issues or have questions: + +- Check our [troubleshooting guide](./troubleshooting) +- Open an issue on [GitHub](https://github.com/fentas/b/issues) +- Join our community discussions + +email: jan.guth@gmail.com + + + + diff --git a/www/apps/docs/content/b/overview.mdx b/www/apps/docs/content/b/overview.mdx new file mode 100644 index 0000000..f2bb24e --- /dev/null +++ b/www/apps/docs/content/b/overview.mdx @@ -0,0 +1,94 @@ +--- +description: "Complete command reference for b" +hide_table_of_contents: true +--- + +import DocCardList from '@theme/DocCardList'; +import Icons from '@theme/Icon'; + +# b reference + +**b** provides a comprehensive set of commands for managing binary dependencies in your projects. This reference covers all available commands, options, and usage patterns. + +## Core + +**b** is built around a subcommand architecture that provides intuitive and powerful binary management capabilities. + +## Quick reference + +Get started quickly with the most common commands: + +```bash +b init # Initialize a new project +b install jq # Install a specific binary +b install --add jq@jq-1.6 # Install and add to b.yaml +b list # List installed binaries +b update # Update all binaries +b search kubectl # Search for available binaries +``` + + \ No newline at end of file diff --git a/www/apps/docs/content/b/subcommands/completion.mdx b/www/apps/docs/content/b/subcommands/completion.mdx new file mode 100644 index 0000000..86b977f --- /dev/null +++ b/www/apps/docs/content/b/subcommands/completion.mdx @@ -0,0 +1,129 @@ +--- +description: "Generate shell completion scripts for b CLI" +--- + +# b completion + +The `b completion` command generates shell completion scripts for the b CLI, enabling tab completion for commands, flags, and arguments in your shell. + +## Synopsis + +```bash +# Generate completion script for your shell +b completion [bash|zsh|fish|powershell] + +# Source the completion script in your current shell session +source <(b completion bash) # for bash +``` + +## Description + +The `completion` command generates shell completion scripts that provide command-line completion for b CLI. This allows you to use the Tab key to automatically complete commands, subcommands, flags, and arguments, making the CLI more user-friendly and efficient. + +## Available Shells + +Completion is available for the following shells: + +- **bash**: For Bash shell users +- **zsh**: For Zsh shell users +- **fish**: For Fish shell users +- **powershell**: For PowerShell users + +## Installation + +### Bash + +Add the following to your `~/.bashrc` or `~/.bash_profile`: + +```bash +# Bash completion for b CLI +source <(b completion bash) +``` + +### Zsh + +Add the following to your `~/.zshrc`: + +```zsh +# Zsh completion for b CLI +if [ $commands[b] ]; then + source <(b completion zsh) +fi +``` + +### Fish + +Save the completion script to your Fish completions directory: + +```bash +# Create completions directory if it doesn't exist +mkdir -p ~/.config/fish/completions + +# Generate and save completion script +b completion fish > ~/.config/fish/completions/b.fish +``` + +### PowerShell + +Add the following to your PowerShell profile (`$PROFILE`): + +```powershell +# PowerShell completion for b CLI +b completion powershell | Out-String | Invoke-Expression +``` + +## Examples + +### Generate Completion Script + +```bash +# Generate bash completion script +b completion bash > /etc/bash_completion.d/b + +# Generate zsh completion script +b completion zsh > "${fpath[1]}/_b" + +# Generate fish completion script +b completion fish > ~/.config/fish/completions/b.fish + +# Generate PowerShell completion script +b completion powershell > ~/.b-completion.ps1 +``` + +### One-time Completion + +For a single session without modifying your shell configuration: + +```bash +# Bash/Zsh +source <(b completion bash) + +# Fish +b completion fish | source +``` + +## Completion Features + +- **Command Completion**: Tab-complete all available commands +- **Flag Completion**: Complete flags for each command +- **Argument Completion**: Context-aware completion for arguments +- **Flag Value Completion**: Smart completion for flag values +- **Descriptions**: Show help text for completions (in supported shells) + +## Shell-Specific Notes + +### Bash + +For better completion experience with bash, consider installing the `bash-completion` package: + +```bash +# On macOS with Homebrew +brew install bash-completion + +# On Ubuntu/Debian +sudo apt-get install bash-completion +``` + +### Zsh + +If you're using Oh My Zsh, the completion should work out of the box after installation. diff --git a/www/apps/docs/content/b/subcommands/init.mdx b/www/apps/docs/content/b/subcommands/init.mdx new file mode 100644 index 0000000..d571c3d --- /dev/null +++ b/www/apps/docs/content/b/subcommands/init.mdx @@ -0,0 +1,89 @@ +--- +description: "Initialize a new project with b" +--- + +# b init + +The `b init` command initializes a new project with **b** configuration, creating the necessary **b.yaml** file and setting up the project structure. + +## Synopsis + +```bash +b init [options] +``` + +## Description + +The `init` command creates a new **b.yaml** configuration file in the current directory and optionally sets up additional project files like `.gitignore` and shell integration scripts. + +## Options + +- `--quiet, -q`: Run in quiet mode with minimal output +- `--help, -h`: Show help information + +## Examples + +### Basic Initialization + +```bash +# Initialize b in current directory +b init +``` + +This creates a basic **b.yaml** file: + +```yaml +version: v1 +binaries: + b: {} +``` + +### Project Structure + +After initialization, your project will have: + +``` +project/ +├── .bin +│ ├── .gitignore +│ └── b.yaml +└── .envrc +``` + +## Configuration File + +The generated **b.yaml** file serves as the central configuration for your project's binary dependencies: + +```yaml +binaries: + # Binary dependencies will be added here + # Example: + # jq: + # version: "1.6" + # kubectl: + # version: "1.28.0" +``` + +## Best Practices + +1. **Version Control**: Always commit the **b.yaml** file to version control +2. **Team Collaboration**: Initialize **b** in shared project repositories +3. **CI/CD**: Use `b init` in CI/CD pipelines before installing dependencies +4. **Documentation**: Document your binary requirements in the project README + +## Integration with Git + +The `init` command automatically creates or updates `.gitignore` to exclude: + +```gitignore +# b +.bin/ +``` + +## Error Handling + +Common issues and solutions: + +- **Permission denied**: Ensure write permissions in the current directory +- **File exists**: The command will not overwrite existing **b.yaml** files +- **Invalid directory**: Run from a valid project directory diff --git a/www/apps/docs/content/b/subcommands/install.mdx b/www/apps/docs/content/b/subcommands/install.mdx new file mode 100644 index 0000000..d594bc8 --- /dev/null +++ b/www/apps/docs/content/b/subcommands/install.mdx @@ -0,0 +1,85 @@ +--- +description: "Install and manage binaries with b CLI" +--- + +# b install + +The `b install` command downloads and installs binaries, managing their versions and updating your project configuration. + +## Synopsis + +```bash +b install [options] [binary[@version]...] +``` + +## Description + +The `install` command downloads binaries from GitHub releases and makes them available in your project. It can install specific versions, update existing installations, and automatically manage your `b.yaml` configuration. + +## Options + +- `--add, -a`: Add installed binaries to b.yaml configuration +- `--force, -f`: Force reinstallation even if already installed +- `--quiet, -q`: Run in quiet mode with minimal output +- `--help, -h`: Show help information + +## Examples + +### Install Specific Binaries + +```bash +# Install latest version +b install jq + +# Install specific version +b install jq@1.6 + +# Install multiple binaries +b install jq kubectl terraform +``` + +### Install and Add to Configuration + +```bash +# Install and add to b.yaml +b install --add jq@1.6 + +# Install multiple with specific versions +b install --add jq@1.6 kubectl@1.28.0 +``` + +### Install from Configuration + +```bash +# Install all binaries from b.yaml +b install + +# Force reinstall all +b install --force +``` + +## Version Resolution + +The install command supports flexible version specifications: + +- `latest`: Always installs the most recent version +- `1.6`: Installs exactly version 1.6 +- `1.6.0`: Installs exactly version 1.6.0 + +## Installation Process + +1. **Version Resolution**: Determines the exact version to install +2. **Download**: Fetches the binary from GitHub releases +3. **Verification**: Validates the downloaded binary +4. **Installation**: Places the binary in the managed directory +5. **PATH Management**: Ensures the binary is available in PATH +6. **Configuration Update**: Updates b.yaml if `--add` is specified + +## Error Handling + +Common issues and solutions: + +- **Version not found**: Check available versions with `b search ` +- **Download failed**: Verify internet connection and GitHub access +- **Permission denied**: Ensure write permissions to installation directory +- **Binary not executable**: The install command automatically sets execute permissions \ No newline at end of file diff --git a/www/apps/docs/content/b/subcommands/list.mdx b/www/apps/docs/content/b/subcommands/list.mdx new file mode 100644 index 0000000..148122f --- /dev/null +++ b/www/apps/docs/content/b/subcommands/list.mdx @@ -0,0 +1,73 @@ +--- +description: "List installed binaries and their versions" +--- + +# b list + +The `b list` command displays all installed binaries and their current versions. + +## Synopsis + +```bash +b list [options] [binary...] +``` + +## Description + +The `list` command shows information about installed binaries, including their versions, installation paths, and configuration status. + +## Options + +- `--quiet, -q`: Show only binary names and versions +- `--help, -h`: Show help information + +## Examples + +### List All Binaries + +```bash +# List all installed binaries +b list +``` + +Output: +``` +jq 1.6 ~/.local/share/b/bin/jq +kubectl 1.28.0 ~/.local/share/b/bin/kubectl +terraform 1.5.7 ~/.local/share/b/bin/terraform +``` + +### List Specific Binaries + +```bash +# List specific binaries +b list jq kubectl +``` + +### Quiet Mode + +```bash +# Show only names and versions +b list --quiet +``` + +Output: +``` +jq 1.6 +kubectl 1.28.0 +terraform 1.5.7 +``` + +## Output Format + +The default output includes: +- **Binary Name**: The name of the installed binary +- **Version**: The currently installed version +- **Path**: The full path to the binary + +## Configuration Status + +The list command shows which binaries are: +- ✅ **Configured**: Listed in b.yaml +- ⚠️ **Installed but not configured**: Installed but not in b.yaml +- ❌ **Configured but not installed**: Listed in b.yaml but not installed diff --git a/www/apps/docs/content/b/subcommands/request.mdx b/www/apps/docs/content/b/subcommands/request.mdx new file mode 100644 index 0000000..dcf2e08 --- /dev/null +++ b/www/apps/docs/content/b/subcommands/request.mdx @@ -0,0 +1,52 @@ +--- +description: "Request new binaries to be added to b CLI" +--- + +# b request + +The `b request` command helps users request new binaries to be added to the b CLI ecosystem by creating GitHub issues with the appropriate template. + +## Synopsis + +```bash +b request [options] +``` + +## Description + +The `request` command streamlines the process of requesting new binaries to be added to the b CLI. It opens your default web browser to create a new GitHub issue with a pre-filled template, making it easy to request new tools. + +## Options + +- `--help, -h`: Show help information + +## Examples + +### Basic Usage + +```bash +# Request a new binary to be added +b request my-cool-tool +``` + +## Request Process + +When you submit a request: + +1. The command collects information about your system +2. Opens a new issue in the b CLI GitHub repository +3. Pre-fills a template with: + - Requested binary name + - Your system information + - Any additional notes you provided + - Version of b CLI you're using + +## Request Guidelines + +For the best chance of your request being accepted: + +1. **Check existing issues** first to avoid duplicates +2. **Provide a clear use case** for the binary +3. **Include relevant links** to the project's homepage +4. **Mention any alternatives** you've considered +5. **Be specific** about versions or features needed \ No newline at end of file diff --git a/www/apps/docs/content/b/subcommands/search.mdx b/www/apps/docs/content/b/subcommands/search.mdx new file mode 100644 index 0000000..19d640d --- /dev/null +++ b/www/apps/docs/content/b/subcommands/search.mdx @@ -0,0 +1,44 @@ +--- +description: "Search for available binaries in the b CLI ecosystem" +--- + +# b search + +The `b search` command helps you discover binaries available in the b CLI ecosystem, showing detailed information about each package including versions, descriptions, and installation commands. + +## Synopsis + +```bash +b search [options] [query] +``` + +## Description + +The `search` command queries the b CLI package registry to find binaries matching your search criteria. It's the best way to discover new tools and check available versions before installation. + +## Options + +- `--output, -o`: Output format (json, yaml, table) +- `--help, -h`: Show help information + +## Examples + +### Basic Search + +```bash +# Search for a specific binary +b search jq + +# Search with partial match +b search py +``` + +### Advanced Search + +```bash +# Show all available packages +b search + +# Get results in JSON format +b search --output json jq +``` diff --git a/www/apps/docs/content/b/subcommands/update.mdx b/www/apps/docs/content/b/subcommands/update.mdx new file mode 100644 index 0000000..8f4fcdc --- /dev/null +++ b/www/apps/docs/content/b/subcommands/update.mdx @@ -0,0 +1,84 @@ +--- +description: "Update installed binaries to their latest versions" +--- + +# b update + +The `b update` command checks for and installs newer versions of previously installed binaries, ensuring your tools stay up-to-date. + +## Synopsis + +```bash +b update [options] [binary...] +``` + +## Description + +The `update` command checks GitHub releases for newer versions of your installed binaries and updates them if available. It respects version constraints in your `b.yaml` configuration while updating to the latest compatible version. + +## Options + +- `--all, -a`: Update all installed binaries +- `--force, -f`: Force update even if already at latest version +- `--quiet, -q`: Run in quiet mode with minimal output +- `--check, -c`: Check for updates without installing +- `--help, -h`: Show help information + +## Examples + +### Check for Updates + +```bash +# Check for updates without installing +b update --check + +# Check specific binaries +b update --check jq kubectl +``` + +### Update Binaries + +```bash +# Update all installed binaries +b update --all + +# Update specific binaries +b update jq kubectl + +# Force update even if at latest version +b update --force jq +``` + +### Update Configuration + +```bash +# Update binaries and save versions to b.yaml +b update --all --save +``` + +## Version Constraints + +When updating, the command respects version constraints in your `b.yaml`: + +- `^1.2.3`: Updates to the latest 1.x.x version +- `~1.2.3`: Updates to the latest 1.2.x version +- `>1.2.3`: Updates to any version above 1.2.3 +- `1.2.3`: Keeps exactly this version (use --force to override) + +## Update Process + +1. **Version Check**: Compares installed versions with latest available +2. **Dependency Resolution**: Ensures compatibility between dependencies +3. **Download**: Fetches updated binaries from GitHub releases +4. **Verification**: Validates the downloaded binaries +5. **Backup**: Creates backups of existing binaries before updating +6. **Update**: Replaces old binaries with new versions + +## Error Handling + +Common issues and solutions: + +- **Update failed**: Check network connection and GitHub access +- **Version conflict**: Review version constraints in b.yaml +- **Permission denied**: Ensure write permissions to installation directory +- **Binary in use**: Close applications using the binary before updating \ No newline at end of file diff --git a/www/apps/docs/content/b/subcommands/version.mdx b/www/apps/docs/content/b/subcommands/version.mdx new file mode 100644 index 0000000..71d994b --- /dev/null +++ b/www/apps/docs/content/b/subcommands/version.mdx @@ -0,0 +1,113 @@ +--- +description: "Display version information for b CLI and installed binaries" +--- + +# b version + +The `b version` command displays version information about the b CLI tool and its installed binaries, helping you track which versions are currently in use. + +## Synopsis + +```bash +b version [options] [binary...] +``` + +## Description + +The `version` command shows detailed version information about the b CLI itself and any installed binaries. It's useful for debugging, reporting issues, and ensuring compatibility between different components. + +## Options + +- `--short, -s`: Show only version numbers +- `--json`: Output in JSON format +- `--all, -a`: Show versions of all installed binaries +- `--help, -h`: Show help information + +## Examples + +### Basic Usage + +```bash +# Show b CLI version +b version + +# Show version in short format +b version --short +``` + +### Check Binary Versions + +```bash +# Check version of specific binaries +b version jq kubectl + +# Show versions of all installed binaries +b version --all + +# Get version information in JSON format +b version --json +``` + +## Version Information + +The version command displays several types of version information: + +1. **CLI Version**: The version of the b CLI tool +2. **Runtime Information**: Go version and build platform +3. **Binary Versions**: For each specified binary: + - Installed version + - Latest available version + - Whether an update is available + - Source of installation + - Last checked/updated timestamp + +## Output Formats + +### Standard Output +``` +b version 1.2.3 (go1.19.5 darwin/amd64) + +jq: jq-1.6 (latest: jq-1.7) [Update available] +kubectl: v1.28.0 (latest: v1.28.0) [Up to date] +``` + +### JSON Output (--json) +```json +{ + "cli": { + "version": "1.2.3", + "goVersion": "go1.19.5", + "platform": "darwin/amd64" + }, + "binaries": { + "jq": { + "installed": "jq-1.6", + "latest": "jq-1.7", + "updateAvailable": true, + "source": "github.com/stedolan/jq" + } + } +} +``` + +## Integration with CI/CD + +The `--short` and `--json` flags are particularly useful in CI/CD pipelines: + +```yaml +# Example GitHub Actions workflow step +- name: Verify binary versions + run: | + if [ "$(b version --short jq)" != "1.6" ]; then + echo "Incorrect jq version" + exit 1 + fi +``` + +## Error Handling + +Common issues and solutions: + +- **Binary not found**: The specified binary is not installed +- **Version check failed**: Could not determine latest version (check network connection) +- **Permission denied**: Insufficient permissions to read version information \ No newline at end of file diff --git a/www/apps/docs/content/contribution/b.md b/www/apps/docs/content/contribution/b.md new file mode 100644 index 0000000..aa644e9 --- /dev/null +++ b/www/apps/docs/content/contribution/b.md @@ -0,0 +1,75 @@ +--- +sidebar_label: "b contribution" +sidebar_position: 2 +--- + +# Contribute by improving b + +In this document, you'll learn how you can contribute to b by improving the codebase. + +## Overview + +b is a binary manager for developers that simplifies installation, versioning, and management of command-line tools. It is written in Go and is used to build the core functionality of b. + +### Binaries + +Binaries are the actual command-line tools that are managed by b. They are stored in the `binaries` directory in the b repository and registered in `cmd/b/main.go`. + +``` +├── cmd +│ └── b +│ └── # All supported binaries are added here +├── pkg +│ ├── binaries +│ │ └── # Binary definitions +│ ├── binary +│ │ └── # Binary management and download logic +│ ├── cli +│ │ └── # CLI subcommands +│ ├── path +│ │ └── # PATH management +│ └── state +│ └── # State management +└── test + ├── e2e + │ └── # End-to-end tests + └── testutil + └── # Test utilities +``` + +## How to contribute + +If you’re adding a new library or contributing to the codebase, you need to fork the repository, create a new branch, and make all changes necessary in your repository. Then, once you’re done, create a PR in the b repository. + +### Base Branch + +When you make an edit to an existing documentation page or fork the repository to make changes to the documentation, you have to create a new branch. + +Documentation contributions always use `main` as the base branch. Make sure to also open your PR against the `main` branch. + +### Pull Request Conventions + +When you create a pull request, prefix the title with `fix:`, `feat:` or `docs:`. + + + +In the body of the PR, explain clearly what the PR does. If the PR solves an issue, use [closing keywords](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) with the issue number. For example, “Closes #1333”. + + + + +## Testing + +Feel free to add tests if you think it's necessary. + +### Coverage + +We strive to have 100% test coverage. When you add a new library or make changes to an existing library, make sure to add tests that cover all functionality. + +## Linting + +To lint the code, run the following command: + +```bash +gofmt -w . +``` diff --git a/www/apps/docs/content/contribution/docs.md b/www/apps/docs/content/contribution/docs.md new file mode 100644 index 0000000..1121d38 --- /dev/null +++ b/www/apps/docs/content/contribution/docs.md @@ -0,0 +1,492 @@ +--- +sidebar_label: "Docs" +sidebar_position: 1 +--- + +# Docs Contribution Guidelines + +Thank you for your interest in contributing to the documentation! You will be helping the open source community and other developers interested in learning more about b and using it. + +:::tip + +This guide is specific to contributing to the documentation. If you’re interested in contributing to b’s codebase, check out the [contributing guidelines in the Medusa GitHub repository](https://github.com/fentas/b/blob/develop/CONTRIBUTING.md). + +::: + +## Documentation Workspace + +b's documentation projects are all part of the documentation yarn workspace, which you can find in the [b repository](https://github.com/fentas/b) under the `www` directory. + +The workspace has the following two directories: + +- `apps`: this directory holds the different documentation websites and projects. + - `docs`: includes the codebase for the main documentation website (the one you're viewing this documentation on). It's built with [Docusaurus](https://docusaurus.io/). +- `packages`: this directory holds the shared packages and components necessary for the development of the projects in the `apps` directory. + - `docs-ui` includes the shared React components between the different apps. + - `eslint-config-docs` includes the shared ESLint configuration between the different apps and packages. + - `tailwind` includes the shared Tailwind CSS configuration between the different apps and packages. + - `tsconfig` includes the shared TypeScript configuration between the different apps and packages. + +--- + +## Documentation Content + +### Main Documentation Website + +The documentation content is written in Markdown format and is located in the [www/apps/docs/content](https://github.com/fentas/b/tree/develop/www/apps/docs/content) directory of the **b** repository. If you’re not familiar with Markdown, check out [this cheat sheet](https://www.markdownguide.org/cheat-sheet/) for a quick start. + +You’ll also find MDX files. MDX files combine the power of Markdown with React. So, the content of the file can contain JSX components and import statements, among other features. You can learn more about [MDX in docusaurus’s guide.](https://docusaurus.io/docs/markdown-features/react). + +## Style Guide + +When you contribute to the documentation content, make sure to follow the [documentation style guide](https://www.notion.so/Style-Guide-Docs-fad86dd1c5f84b48b145e959f36628e0). + +--- + +## How to Contribute + +If you’re fixing errors in an existing documentation page, you can scroll down to the end of the page and click on the “Edit this page” link. You’ll be redirected to the GitHub edit form of that page and you can make edits directly and submit a pull request (PR). + +If you’re adding a new page or contributing to the codebase, you need to fork the repository, create a new branch, and make all changes necessary in your repository. Then, once you’re done, create a PR in the **b** repository. + +### Base Branch + +When you make an edit to an existing documentation page or fork the repository to make changes to the documentation, you have to create a new branch. + +Documentation contributions always use `main` as the base branch. Make sure to also open your PR against the `main` branch. + +### Pull Request Conventions + +When you create a pull request, prefix the title with `docs:`. + + + +In the body of the PR, explain clearly what the PR does. If the PR solves an issue, use [closing keywords](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) with the issue number. For example, “Closes #1333”. + + + +--- + +## Main Documentation Sidebar + +When you add a new page to the documentation, you must add the new page in `www/apps/docs/sidebars.js`. In this file, an object is exported. This object holds more than one sidebar. The properties of the object indicate the internal sidebar name, and the value is an array of sidebar items in that sidebar. + +You can learn more about the syntax used [here](https://docusaurus.io/docs/sidebar/items). + +### Terminology + +When the documentation page is a conceptual or an overview documentation, the label in the sidebar should start with a noun. + +When the documentation page is tutorial documentation, the label in the sidebar should start with a verb. Exceptions to this rule are integration documentation and upgrade guides. + +### Sidebar Icon + +To add an icon to the sidebar item, start by checking if the icon is already exported in the file `www/apps/docs/src/theme/Icon`. If not, you can either export the icon from the [@medusajs/icons](https://docs.medusajs.com/ui/icons/overview), or add the new icon as a React component in the `www/apps/docs/src/theme/Icon/Icon/index.tsx` file, where `` is the camel-case name of your icon. The icon must be added to the React component as an SVG element. + +For example: + +```tsx title="www/docs/src/theme/Icon/Bolt/index.tsx" +import React from "react" +import { IconProps } from "@medusajs/icons/dist/types" + +export default function IconBolt(props: IconProps) { + return ( + + + + ) +} +``` + +Make sure to set the `stroke` or `fill` of the icon to `currentColor` as shown in the example above. The source code for the Sidebar passes the icon a color. So, this ensures the color is correctly used. + +If you added a new icon, add it in the exported object in the file `www/apps/docs/src/theme/Icon/index.ts`, where the property is the kebab-case version of the icon's name, and the value being the component you created. Make sure to add it in the correct alphabetical position as well. For example: + +```ts title="www/docs/src/theme/Icon/index.ts" +import IconBolt from "./Bolt" +import IconBoltSolid from "./BoltSolid" +// other imports + +export default { + // other icons + "bolt": IconBolt, + "bolt-solid": IconBoltSolid, + // other icons +} +``` + +Finally, you can add the icon to the sidebar item by adding a `sidebar_icon` property to the `customProps` property and setting its value to the kebab-cased version of the icon's name. For example: + +```js title="www/docs/sidebars.js" +module.exports = { + // other sidebars + homepage: [ + { + // other properties + customProps: { + sidebar_icon: "book-open", + }, + }, + // other items + ], +} +``` + +### Sidebar Item Types + +There are different sidebar item types used in the documentation: + +- Homepage Items: If a sidebar item is shown under the `homepage` sidebar, you should set the `className` property of the item to `homepage-sidebar-item`. You can use this with other sidebar item types. For example: + + ```js title="www/docs/sidebars.js" + module.exports = { + // other sidebars + homepage: [ + { + type: "doc", + // other properties + className: "homepage-sidebar-item", + }, + // other items + ], + } + ``` + +- Sidebar Title: This item is used as a title to the sidebar, typically added at the top of the sidebar. You typically would also use an icon with it. To use this item, add a `sidebar_is_title` property to the `customProps` object of the item with its value being `true`. For example: + + ```js title="www/docs/sidebars.js" + module.exports = { + // other sidebars + modules: [ + // other items + { + type: "doc", + id: "development/howtos/yq", + label: "How to use yq", + customProps: { + sidebar_is_title: true, + sidebar_icon: "puzzle", + }, + }, + // other items + ], + } + ``` + +- Back Item: This item is used to show a back button, typically at the top of the sidebar. To use this item, add the `sidebar_is_back_link` property to the `customProps` object of the item, with its value set to true. Also, add the `sidebar_icon` property to the `customProps` object with its value set to `back-arrow`. For example: + + ```js title="www/docs/sidebars.js" + module.exports = { + // other sidebars + core: [ + // other items + { + type: "ref", + id: "homepage", + label: "Back to home", + customProps: { + sidebar_is_back_link: true, + sidebar_icon: "back-arrow", + }, + }, + // other items + ], + } + ``` + +- Group Divider Item: This item is used if a sidebar item does not link to any document and is only used to separate between sidebar sections. The item must be of type `html`, and its `value` property holds the text that should be shown in the divider. You must also add in the `customProps` object of the item the property `sidebar_is_group_divider` with its value being `true`. For example: + + ```js title="www/docs/sidebars.js" + module.exports = { + // other sidebars + homepage: [ + // other items + { + type: "html", + value: "Browse Docs", + customProps: { + sidebar_is_group_divider: true, + }, + className: "homepage-sidebar-item", + }, + // other items + ], + } + ``` + +- Group Headline Item: This item is used if a sidebar item does not link to any document and is only used to indicate the beginning of a new section or group in the sidebar. To use this item, set the `type` of the item to `category`, and add the `sidebar_is_group_headline` property to the `customProps` object of the item, with its value set to `true`. For example: + + ```js title="www/docs/sidebars.js" + module.exports = { + // other sidebars + modules: [ + // other items + { + type: "category", + label: "Regions and Currencies", + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + // items within group or section + ], + }, + // other items + ], + } + ``` + +- Soon Item: This item is used to indicate that a certain guide will be added soon, but it does not actually link to any document. To use this item, set the `type` of the item to `link`, its `href` property to `#`, and add to the `customProps` object the property `sidebar_is_soon` with its value set to `true`. For example: + + ```js title="www/docs/sidebars.js" + module.exports = { + // other sidebars + modules: [ + // other items + { + type: "link", + href: "#", + label: "Currencies", + customProps: { + sidebar_is_soon: true, + }, + }, + // other items + ], + } + ``` + +--- + +## Notes and Additional Information + +When displaying notes and additional information on a documentation page, use [Admonitions](https://docusaurus.io/docs/markdown-features/admonitions). Make sure the type of admonition used matches the note’s importance to the current document. + +If the note is something developers have to be careful of doing or not doing, use the `danger` admonition based on how critical it is. + +If the note displays helpful information and tips that may not be in the scope of the documentation page, use the `tip` admonition. + +For all other note types, use the `note` admonition. + +--- + +## Images + +If you are adding images to a documentation page, you can host the image on [Imgur](https://imgur.com) for free to include it in the PR. Our team will later upload it to our image hosting. + +--- + +## Code Blocks + +:::note + +These sections only works in the main documentation website. + +::: + +### Use Tabs with Code Blocks + +To use Tabs with Code Blocks, you have to use [Docusaurus's `Tabs` and `TabItem` components](https://docusaurus.io/docs/markdown-features/code-blocks#multi-language-support-code-blocks). + +You must also pass to the `Tabs` component the prop `isCodeTabs={true}` to ensure correct styling. + +For example: + +~~~md +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + + + ```bash + curl -sL https://get.arg.sh | sudo tee /usr/local/bin/argsh > /dev/null + sudo chmod +x /usr/local/bin/argsh + ``` + + + + + ```bash + curl -sL https://get.arg.sh > .bin/argsh + chmod +x .bin/argsh + ``` + + + +~~~ + +### Add Title to Code Block with Tabs + +If you want to add a title to a code block with tabs, add the `codeTitle` prop to the `Tabs` component. + +For example: + +```md + +``` + +### Add Title to Code Block without Tabs + +To add a title to a code block without tabs: + +~~~md +```js title="src/index.ts" +console.log("hello") +``` +~~~ + +### Remove Report Button + +Some code block don't need a report button. To remove the report button, use the `noReport` metadata. + +For example: + +~~~md +```bash noReport +b install argsh +``` +~~~ + +### Remove Copy Button + +Some code blocks don't need a copy button. To remove the copy button, use the `noCopy` metadata: + +For example: + +~~~md +```bash noCopy +source ~/.bashrc +``` +~~~ + +--- + + +## Linting with Vale + +Argsh uses [Vale](https://vale.sh/) to lint documentation pages and perform checks on incoming PRs into the repository. + +### Result of Vale PR Checks + +You can check the result of running the "lint" action on your PR by clicking the Details link next to it. You can find there all errors that you need to fix. + +### Run Vale Locally + +If you want to check your work locally, you can do that by: + +1. [Installing direnv](https://direnv.net/) and running `direnv allow` in the root directory of the argsh repository. +2. Linting with `vale`: + +```bash +# to lint content for the main documentation +argsh lint --vale error +``` + +### VS Code Extension + +To facilitate writing documentation, you can optionally use the [Vale VS Code extension](https://marketplace.visualstudio.com/items?itemName=chrischinchilla.vale-vscode). This will show you any errors in your documentation while writing it. + +### Linter Exceptions + +If it's needed to break some style guide rules in a document, you can wrap the parts that the linter shouldn't scan with the following comments in the `md` or `mdx` files: + +```md + + +content that shouldn't be scanned for errors here... + + +``` + +You can also disable specific rules. For example: + +```md + + +Argsh supports Bash version 4.3 and later. + + +``` + +If you use this in your PR, you must justify its usage. + +--- + +## Linting with ESLint + +Argsh uses ESlint to lint code blocks both in the content and the code base of the documentation apps. + +### Linting Code/Content with ESLint + +Each PR runs through a check that lints the code in the content files using ESLint. The action's name is `code-docs-eslint`. + +If you want to check code ESLint errors locally and fix them, you can do that by: + +1\. Set up [direnv](https://direnv.net/) then run in the `root` directory: + +```bash +direnv allow +``` + +2\. Then execute the following command anywhere: + +```bash +gofmt -s -w . +``` + +This will fix any fixable errors, and show errors that require your action. + +### ESLint Exceptions + +:::note + +These exceptions only work in the main documentation website. + +::: + +If some code blocks have errors that can't or shouldn't be fixed, you can add the following command before the code block: + +~~~md + + +```js +console.log("This block isn't linted") +``` + +```js +console.log("This block is linted") +``` +~~~ + +You can also disable specific rules. For example: + +~~~md + + +```js +console.log("This block can use semicolons"); +``` + +```js +console.log("This block can't use semi colons") +``` +~~~ + +--- \ No newline at end of file diff --git a/www/apps/docs/content/getting-started.mdx b/www/apps/docs/content/getting-started.mdx new file mode 100644 index 0000000..5bc9122 --- /dev/null +++ b/www/apps/docs/content/getting-started.mdx @@ -0,0 +1,137 @@ +--- +description: 'Install b and get started with binary management for your development projects' +--- + +# Getting Started with b + +Get up and running with **b** in just a few minutes. **b** helps you manage command-line tools and binaries for your development projects with automatic PATH management and version control. + +## Installation + +### Quick Install (Recommended) + +```bash +# Install b +curl -sSL https://github.com/fentas/b/releases/latest/download/install.sh | bash + +# Or using wget +wget -qO- https://github.com/fentas/b/releases/latest/download/install.sh | bash +``` + +### Manual Installation + +1. Download the latest release for your platform from [GitHub Releases](https://github.com/fentas/b/releases) +2. Extract the binary to a directory in your PATH +3. Make it executable: `chmod +x b` + +### Verify Installation + +```bash +b version +``` + +## Quick Start + +### 1. Initialize a Project + +```bash +# Navigate to your project directory +cd my-project + +# Initialize b configuration +b init +``` + +This creates a `b.yaml` file in your project root. + +### 2. Install Tools + +```bash +# Install specific versions +b install jq@jq-1.6 kubectl@v1.28.0 + +# Install latest versions +b install terraform helm + +# Install and add to b.yaml +b install --add docker-compose@v2.20.0 +``` + +### 3. Use Your Tools + +Tools are automatically available in your PATH: + +```bash +jq --version # 1.6 +kubectl version # 1.28.0 +``` + +## Common Workflows + +### Team Collaboration + +Share tool requirements with your team: + +```bash +# Add tools to b.yaml +b install --add jq@jq-1.6 kubectl@v1.28.0 + +# Commit b.yaml to version control +git add b.yaml +git commit -m "Add project tool requirements" + +# Team members can install the same tools +b install # Installs all tools from b.yaml +``` + +### CI/CD Integration + +Use **b** in your CI/CD pipelines: + +```yaml +# .github/workflows/ci.yml +steps: + - uses: actions/checkout@v4 + - name: Install b + run: curl -sSL https://github.com/fentas/b/releases/latest/download/install.sh | bash + - name: Install project tools + run: b install + - name: Run tests + run: | + kubectl version --client + jq --version +``` + +### Version Management + +```bash +# List installed tools +b list + +# Update tools to latest versions +b update + +# Update specific tools +b update kubectl terraform + +# Search for available tools +b search docker +``` + +## Configuration + +The `b.yaml` file defines your project's tool requirements: + +```yaml +binaries: + jq: + version: "jq-1.6" + kubectl: + version: "v1.28.0" + terraform: + version: "latest" +``` + +## Next Steps + +- [CLI Reference](/b/overview) - Complete command reference \ No newline at end of file diff --git a/www/apps/docs/content/glossary.mdx b/www/apps/docs/content/glossary.mdx new file mode 100644 index 0000000..548f136 --- /dev/null +++ b/www/apps/docs/content/glossary.mdx @@ -0,0 +1,71 @@ +--- +description: "Learn about the different terms related to b" +--- + +# Glossary + +This glossary defines key terms and concepts used throughout the **b** documentation. + +## B + +**b** - A modern binary manager for developers that simplifies installation, versioning, and management of command-line tools. + +**b.yaml** - The configuration file that defines binary dependencies for a project. Contains version specifications and metadata. + +**Binary** - An executable command-line tool managed by **b** (e.g., `jq`, `kubectl`, `terraform`). + +**Binary Manager** - A tool that automates the installation, versioning, and PATH management of command-line utilities. + +## C + +**CLI** - Command Line Interface. The text-based interface used to interact with **b**. + +**Configuration Discovery** - The process by which **b** automatically locates and loads the nearest `b.yaml` file in the directory hierarchy. + +**Context-Aware** - **b**'s ability to automatically detect and use project-specific configurations without explicit file paths. + +## D + +**Dependency Management** - The process of managing tool versions and ensuring consistent environments across different systems. + +**Download Cache** - Local storage where **b** keeps downloaded binaries to avoid re-downloading. + +## E + +**Environment Isolation** - Keeping project-specific tool versions separate from system-wide installations. + +**Executable** - A binary file that can be run as a command. + +## G + +**GitHub Releases** - The source from which **b** downloads most binaries, using GitHub's release API. + +**Global Installation** - Installing **b** system-wide, making it available to all users. + +## I + +**Installation Path** - The directory where **b** installs and manages binaries (typically `~/.local/share/b/bin/`). + +**Isolation** - Keeping different projects' tool versions separate to avoid conflicts. + +## P + +**PATH** - The environment variable that tells the shell where to find executable programs. + +**PATH Management** - **b**'s automatic handling of adding managed binaries to the system PATH. + +**Project-Based Configuration** - Using **b.yaml** files to define tool requirements specific to individual projects. + +## S + +**Subcommand** - A secondary command that follows the main **b** command (e.g., `install`, `update`, `list`). + +**Symlink** - A symbolic link that points to the actual binary location, used for PATH management. + +## V + +**Version Constraint** - Specifications in **b.yaml** that define which version of a tool to use (e.g., `"1.6"`, `"latest"`). + +**Version Resolution** - The process of determining which specific version to install based on constraints. + +**Versioning** - Managing different versions of the same tool for different projects or requirements. diff --git a/www/apps/docs/content/homepage.mdx b/www/apps/docs/content/homepage.mdx new file mode 100644 index 0000000..c89f351 --- /dev/null +++ b/www/apps/docs/content/homepage.mdx @@ -0,0 +1,103 @@ +--- +id: homepage +title: b Documentation +description: "b is a binary manager for developers - install, manage, and version your command-line tools effortlessly." +slug: / +hide_table_of_contents: true +hide_footer: true +--- + +import DocCardList from '@theme/DocCardList'; +import DocCard from '@theme/DocCard'; +import Icons from '@theme/Icon'; +import ThemedImage from '@theme/ThemedImage'; + +
+ +
+ +# + +**b** is a modern binary manager that simplifies how developers install, manage, and version their command-line tools. With project-based configuration and automatic PATH management, **b** ensures your development environment is consistent and reproducible. + +## Why b? + +Managing command-line tools across different projects and environments is challenging: + +- **Version Conflicts**: Different projects need different tool versions +- **Environment Setup**: Complex PATH management and installation procedures +- **Team Consistency**: Ensuring everyone uses the same tool versions +- **CI/CD Integration**: Reproducible builds with exact tool versions + +**b** solves these problems by providing: + +✅ **Project-based Configuration** - Each project defines its required tools +✅ **Automatic PATH Management** - Tools are available when you need them +✅ **Version Control** - Lock specific versions for reproducible builds +✅ **Team Collaboration** - Share tool requirements via `b.yaml` +✅ **CI/CD Ready** - Simple integration with build pipelines + + + +## Quick Example + +```bash +# Initialize a new project +b init + +# Install tools for your project +b install jq@1.6 kubectl@1.28.0 + +# Tools are now available +jq --version # 1.6 +kubectl version --client # 1.28.0 +``` + +## Explore b + + \ No newline at end of file diff --git a/www/apps/docs/content/troubleshooting.mdx b/www/apps/docs/content/troubleshooting.mdx new file mode 100644 index 0000000..79acd1f --- /dev/null +++ b/www/apps/docs/content/troubleshooting.mdx @@ -0,0 +1,262 @@ +--- +title: Troubleshooting +description: Common issues and solutions when using b +--- + +# Troubleshooting + +This guide covers common issues you might encounter when using **b** and their solutions. + +## Installation Issues + +### Permission Denied + +**Problem**: Getting permission denied errors during installation. + +**Solution**: +```bash +# Make sure you have write permissions to the installation directory +sudo curl -sSL https://github.com/fentas/b/releases/latest/download/install.sh | bash + +# Or install to a user directory +curl -sSL https://github.com/fentas/b/releases/latest/download/install.sh | bash -s -- --prefix=$HOME/.local +``` + +### Command Not Found + +**Problem**: `b: command not found` after installation. + +**Solution**: +```bash +# Add b to your PATH +echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc +source ~/.bashrc + +# Or for zsh +echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc +source ~/.zshrc +``` + +## Configuration Issues + +### b.yaml Not Found + +**Problem**: `b.yaml configuration file not found` + +**Solution**: +```bash +# Initialize b in your project directory +cd your-project +b init + +# Or create b.yaml manually +cat > b.yaml << EOF +binaries: {} +EOF +``` + +### Invalid Configuration + +**Problem**: YAML parsing errors in **b.yaml** + +**Solution**: +```bash +# Validate your YAML syntax +# Check for proper indentation and structure +cat b.yaml + +# Example valid format: +binaries: + jq: + version: "jq-1.6" + kubectl: + version: "v1.28.0" +``` + +## Binary Installation Issues + +### Download Failures + +**Problem**: Failed to download binary from GitHub releases + +**Solutions**: +```bash +# Check your internet connection +curl -I https://github.com + +# Try with verbose output for debugging +b install --verbose jq + +# Check GitHub API rate limits +curl -H "Accept: application/vnd.github.v3+json" https://api.github.com/rate_limit +``` + +### Version Not Found + +**Problem**: Specified version doesn't exist + +**Solution**: +```bash +# Search for available versions +b search jq + +# Use 'latest' for the most recent version +b install jq@latest + +# Check the project's GitHub releases page +``` + +### Binary Not Executable + +**Problem**: Installed binary is not executable + +**Solution**: +```bash +# Check binary permissions +ls -la ~/.local/share/b/bin/ + +# Make executable if needed +chmod +x ~/.local/share/b/bin/jq + +# Reinstall the binary +b install --force jq +``` + +## PATH and Environment Issues + +### Tools Not Available + +**Problem**: Installed tools are not available in PATH + +**Solution**: +```bash +# Check if b's bin directory is in PATH +echo $PATH | grep -o '[^:]*b[^:]*' + +# Source your shell configuration +source ~/.bashrc # or ~/.zshrc + +# Check b's configuration +b list +``` + +### Conflicting Versions + +**Problem**: System version conflicts with b-managed version + +**Solution**: +```bash +# Check which version is being used +which jq +jq --version + +# Ensure b's bin directory comes first in PATH +export PATH="$HOME/.local/share/b/bin:$PATH" + +# Update your shell configuration +echo 'export PATH="$HOME/.local/share/b/bin:$PATH"' >> ~/.bashrc +``` + +## Performance Issues + +### Slow Downloads + +**Problem**: Binary downloads are very slow + +**Solutions**: +```bash +# Use a different mirror or CDN if available +# Check your network connection +speedtest-cli + +# Try downloading during off-peak hours +# Consider using a VPN if geographical restrictions apply +``` + +### Large Binary Cache + +**Problem**: **b**'s cache directory is taking up too much space + +**Solution**: +```bash +# Check cache size +du -sh ~/.local/share/b/ + +# Clean up old versions (if implemented) +b clean + +# Manually remove old binaries +rm -rf ~/.local/share/b/cache/ +``` + +## CI/CD Issues + +### GitHub Actions Failures + +**Problem**: **b** installation fails in GitHub Actions + +**Solution**: +```yaml +# Use specific version and add error handling +steps: + - name: Install b + run: | + curl -sSL https://github.com/fentas/b/releases/latest/download/install.sh | bash + echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Verify b installation + run: b version + + - name: Install project tools + run: b install +``` + +### Docker Container Issues + +**Problem**: **b** doesn't work properly in Docker containers + +**Solution**: +```dockerfile +# Install b in Dockerfile +RUN curl -sSL https://github.com/fentas/b/releases/latest/download/install.sh | bash +ENV PATH="/root/.local/bin:$PATH" + +# Or use multi-stage build +FROM alpine AS b-installer +RUN apk add --no-cache curl +RUN curl -sSL https://github.com/fentas/b/releases/latest/download/install.sh | bash + +FROM alpine +COPY --from=b-installer /root/.local/bin/b /usr/local/bin/b +``` + +## Getting Help + +If you're still experiencing issues: + +1. **Check the logs**: Run commands with `--verbose` flag for detailed output +2. **Search existing issues**: Check [GitHub Issues](https://github.com/fentas/b/issues) +3. **Create a new issue**: Include your OS, **b** version, and error messages +4. **Join the community**: Participate in discussions and get help from other users + +### Debug Information + +When reporting issues, include this information: + +```bash +# b CLI version +b version + +# Operating system +uname -a + +# Shell information +echo $SHELL +echo $PATH + +# Configuration +cat b.yaml + +# Installed binaries +b list +``` \ No newline at end of file diff --git a/www/apps/docs/docusaurus.config.js b/www/apps/docs/docusaurus.config.js new file mode 100644 index 0000000..f89648c --- /dev/null +++ b/www/apps/docs/docusaurus.config.js @@ -0,0 +1,283 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +import "dotenv/config" +import fs from "fs" +import path from "path" +import { themes as prismThemes } from "prism-react-renderer" +const reverseSidebarItems = require("./src/utils/reverse-sidebar") +const excludeSidebarResults = require("./src/utils/exclude-sidebar-results") +const createLatestReleaseRedirects = require("./src/utils/createLatestReleaseRedirects") + +const announcementBar = JSON.parse(fs.readFileSync("./announcement.json")) + +/** @type {import('@medusajs/docs').MedusaDocusaurusConfig} */ +const config = { + title: "b", + tagline: "Explore and learn how to use b", + url: "https://binary.help", + baseUrl: "/", + onBrokenLinks: "throw", + onBrokenMarkdownLinks: "throw", + favicon: "favicon.ico", + organizationName: "b", + projectName: "b/www", + // experimental_router: "browser", + markdown: { + mdx1Compat: { + comments: true, + admonitions: false, + headingIds: false, + }, + mermaid: true, + }, + plugins: [ + require.resolve("docusaurus-plugin-image-zoom"), + async function tailwindPlugin() { + return { + name: "docusaurus-tailwindcss", + configurePostCss(postcssOptions) { + // Appends TailwindCSS and AutoPrefixer. + postcssOptions.plugins.push(require("tailwindcss")) + postcssOptions.plugins.push(require("autoprefixer")) + return postcssOptions + }, + } + }, + function webpackPlugin() { + return { + name: "custom-webpack-plugin", + configureWebpack() { + return { + devServer: { + client: { + overlay: { + runtimeErrors: (error) => { + if ( + error.message === + "ResizeObserver loop completed with undelivered notifications." + ) { + return false + } + return true + }, + }, + }, + }, + } + }, + } + }, + [ + "./src/plugins/docusaurus-plugin-diagram2code-showcase", + { + directoryPath: path.join(__dirname, "diagrams"), + outputPath: path.join(__dirname, "src", "utils"), + }, + ], + // [ + // '@docusaurus/plugin-client-redirects', + // { + // // redirects: [], + // // createRedirects: createLatestReleaseRedirects, + // // async redirects() { + // // const redirects = await createLatestReleaseRedirects(); + // // return redirects; + // // } + // }, + // ], + ], + themes: ["@docusaurus/theme-mermaid"], + themeConfig: { + mermaid: { + theme: { + light: "base", + dark: "base", + }, + options: { + themeVariables: { + background: "#FFFFFF", + mainBkg: "#FFFFFF", + primaryColor: "#FFFFFF", + primaryTextColor: "#030712", + primaryBorderColor: "#D1D5DB", + nodeBorder: "#D1D5DB", + lineColor: "#11181C", + fontFamily: "Inter", + fontSize: "13px", + tertiaryColor: "#F3F4F6", + tertiaryBorderColor: "#D1D5DB", + tertiaryTextColor: "#030712", + clusterBkg: "#F3F4F6", + }, + }, + }, + image: "img/docs-meta.jpg", + colorMode: { + defaultMode: "light", + disableSwitch: false, + respectPrefersColorScheme: true, + }, + algoliaConfig: { + appId: process.env.ALGOLIA_APP_ID || "temp", + apiKey: process.env.ALGOLIA_API_KEY || "temp", + indexNames: { + docs: process.env.DOCS_ALGOLIA_INDEX_NAME, + api: process.env.API_ALGOLIA_INDEX_NAME, + }, + filters: [ + { + value: "docs", + label: "Docs", + }, + ], + defaultFiltersByPath: [], + defaultFilters: ["docs"], + }, + analytics: { + apiKey: process.env.SEGMENT_API_KEY || "temp", + }, + prism: { + defaultLanguage: "ts", + additionalLanguages: ["bash", "json"], + plugins: ["line-numbers", "show-language"], + theme: { + ...prismThemes.vsDark, + plain: { + ...prismThemes.vsDark.plain, + backgroundColor: "#111827", + }, + }, + }, + zoom: { + selector: ".markdown :not(.no-zoom-img) > img:not(.no-zoom-img)", + }, + navbar: { + hideOnScroll: false, + logo: { + alt: "arg.sh Logo", + src: "img/logo-icon.svg", + srcDark: "img/logo-icon-dark.svg", + width: 20, + height: 20, + }, + items: [ + { + type: "docSidebar", + sidebarId: "homepage", + label: "Docs", + position: "left", + }, + { + type: "search", + position: "right", + }, + ], + }, + navbarActions: [ + { + type: "button", + label: "Report an Issue", + className: "max-[1014px]:hidden", + href: "https://github.com/fentas/b/issues/new?assignees=&labels=type%3A+docs&template=docs.yml", + }, + ], + mobileLogo: { + alt: "arg.sh", + src: "img/logo-mobile.png", + srcDark: "img/logo-mobile-dark.png", + width: 82, + height: 20, + }, + footer: { + copyright: `© ${new Date().getFullYear()} Jan Guth (@fentas). All rights reserved.`, + }, + socialLinks: [ + { + type: "github", + href: "https://github.com/fentas/b", + }, + ], + reportCodeLinkPrefix: + "https://github.com/fentas/b/issues/new?assignees=&labels=type%3A+docs&template=docs.yml", + footerFeedback: { + event: "survey", + }, + docs: { + sidebar: { + hideable: true, + autoCollapseCategories: true, + }, + }, + cloudinaryConfig: { + cloudName: process.env.CLOUDINARY_CLOUD_NAME || "", + flags: ["fl_lossy", "f_auto"], + resize: { + action: "pad", + aspectRatio: "16:9", + }, + roundCorners: 16, + }, + }, + presets: [ + [ + "@docusaurus/preset-classic", + { + docs: { + sidebarPath: require.resolve("./sidebars.js"), + editUrl: "https://github.com/fentas/b/edit/develop/www/apps/docs", + path: "content", + routeBasePath: "/", + remarkPlugins: [ + [require("@docusaurus/remark-plugin-npm2yarn"), { sync: true }], + ], + showLastUpdateTime: true, + // breadcrumbs: false, + async sidebarItemsGenerator({ + defaultSidebarItemsGenerator, + ...args + }) { + const sidebarItems = await defaultSidebarItemsGenerator(args) + return reverseSidebarItems( + excludeSidebarResults(sidebarItems, args.item), + args.item + ) + }, + }, + theme: { + customCss: require.resolve("./src/css/custom.css"), + }, + gtag: { + trackingID: "G-S7G7X3JYS3", + }, + sitemap: { + filename: "sitemap-docs.xml", + }, + // clientRedirects: { + // createRedirects: createLatestReleaseRedirects, + // }, + }, + ], + ], + webpack: { + jsLoader: (isServer) => ({ + loader: require.resolve("swc-loader"), + options: { + jsc: { + parser: { + syntax: "typescript", + tsx: true, + }, + target: "es2017", + }, + module: { + type: isServer ? "commonjs" : "es6", + }, + }, + }), + }, +} + +if (Object.keys(announcementBar).length) { + config.themeConfig.announcementBar = announcementBar +} + +export default config diff --git a/www/apps/docs/package.json b/www/apps/docs/package.json new file mode 100644 index 0000000..b8c649f --- /dev/null +++ b/www/apps/docs/package.json @@ -0,0 +1,83 @@ +{ + "name": "docs", + "version": "0.0.0", + "private": true, + "scripts": { + "docusaurus": "docusaurus", + "dev": "docusaurus clear && docusaurus start", + "dev:monorepo": "yarn dev --port 3001", + "build": "docusaurus build", + "swizzle": "docusaurus swizzle", + "deploy": "docusaurus deploy", + "clear": "docusaurus clear", + "start": "docusaurus serve", + "start:monorepo": "yarn start --port 3001", + "write-translations": "docusaurus write-translations", + "write-heading-ids": "docusaurus write-heading-ids", + "lint": "eslint --ext .js,.jsx,.ts,.tsx . --fix", + "lint:content": "eslint --no-eslintrc -c .content.eslintrc.js content --fix", + "diagram2code:generate": "docusaurus diagram2code:generate" + }, + "dependencies": { + "@babel/preset-react": "^7.18.6", + "@cloudinary/react": "^1.11.2", + "@cloudinary/url-gen": "^1.9.2", + "@docusaurus/core": "^3.1.0", + "@docusaurus/plugin-client-redirects": "^3.8.1", + "@docusaurus/preset-classic": "^3.1.0", + "@docusaurus/remark-plugin-npm2yarn": "^3.1.0", + "@docusaurus/theme-mermaid": "^3.1.0", + "@mdx-js/mdx": "3.0.0", + "@mdx-js/react": "3", + "@medusajs/icons": "^1.2.0", + "@svgr/webpack": "6.2.1", + "@swc/core": "^1.3.102", + "autoprefixer": "^10.4.14", + "clsx": "^1.1.1", + "docs-ui": "*", + "docusaurus-plugin-image-zoom": "^1.0.1", + "dotenv": "^16.0.3", + "eslint": "^8.49.0", + "eslint-config-docs": "*", + "file-loader": "^6.2.0", + "lodash": "^4.17.21", + "mdast-util-mdx": "^3.0.0", + "node-fetch": "2", + "postcss": "^8.4.21", + "prism-react-renderer": "^2.3.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-tooltip": "5.7.4", + "react-transition-group": "^4.4.5", + "tailwind": "*", + "tailwindcss": "^3.3.2", + "tsconfig": "*", + "url-loader": "^4.1.1" + }, + "browserslist": { + "production": [ + ">0.5%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@babel/plugin-proposal-decorators": "^7.22.15", + "@docusaurus/module-type-aliases": "^3.1.0", + "@docusaurus/tsconfig": "^3.1.0", + "@docusaurus/types": "^3.1.0", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@types/react-transition-group": "^4.4.6", + "swc-loader": "^0.2.3", + "typescript": "~5.2.2" + }, + "engines": { + "node": ">=18.0" + } +} diff --git a/www/apps/docs/sidebars.js b/www/apps/docs/sidebars.js new file mode 100644 index 0000000..6dc8a4e --- /dev/null +++ b/www/apps/docs/sidebars.js @@ -0,0 +1,184 @@ +/** + * Custom sidebar definitions for b CLI documentation: + * - To declare a sidebar element as part of the homepage sidebar, add className: 'homepage-sidebar-item' + * - To add an icon: + * - add the icon in www/docs/src/theme/Icon//index.ts as a React SVG element if it doesn't exist, where `` is the camel case name of the icon + * - add the mapping to the icon in www/docs/src/theme/Icon/index.js + * - add in customProps sidebar_icon: 'icon-name' + * - To add a group divider add in customProps sidebar_is_group_divider: true and set the label/value to the title that should appear in the divider. + * - To add a back item, add in customProps: + * - sidebar_is_back_link: true + * - sidebar_icon: `back-arrow` + * - To add a sidebar title, add in customProps sidebar_is_title: true + * - To add a group headline, add in customProps sidebar_is_group_headline: true + * - To add a coming soon link (with a badge), add in customProps sidebar_is_soon: true + * - To add a badge, add in customProps sidebar_badge with its value being the props to pass to the Badge component. + */ + +/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ +module.exports = { + homepage: [ + { + type: "doc", + id: "homepage", + label: "Overview", + customProps: { + sidebar_icon: "book-open", + }, + className: "homepage-sidebar-item", + }, + { + type: "doc", + id: "getting-started", + label: "Getting Started", + customProps: { + sidebar_icon: "rocket-launch", + }, + className: "homepage-sidebar-item", + }, + { + type: "doc", + id: "troubleshooting", + label: "Troubleshooting", + customProps: { + sidebar_icon: "exclamation-circle-solid", + }, + className: "homepage-sidebar-item", + }, + { + type: "html", + value: "Browse Docs", + customProps: { + sidebar_is_group_divider: true, + }, + className: "homepage-sidebar-item", + }, + { + type: "ref", + id: "b/overview", + label: "CLI Reference", + customProps: { + sidebar_icon: "command-line", + }, + className: "homepage-sidebar-item", + }, + { + type: "html", + value: "Additional Resources", + customProps: { + sidebar_is_group_divider: true, + }, + className: "homepage-sidebar-item", + }, + { + type: "doc", + id: "troubleshooting", + label: "Troubleshooting", + customProps: { + sidebar_icon: "bug", + }, + className: "homepage-sidebar-item", + }, + { + type: "category", + link: { + type: "doc", + id: "contribution/docs", + }, + label: "Contribution Guidelines", + customProps: { + sidebar_icon: "document-text", + }, + className: "homepage-sidebar-item", + items: [ + { + type: "autogenerated", + dirName: "contribution", + }, + ], + }, + { + type: "doc", + id: "glossary", + label: "Glossary", + customProps: { + sidebar_icon: "book-open", + }, + className: "homepage-sidebar-item", + }, + { + type: "doc", + id: "about", + label: "About", + customProps: { + sidebar_icon: "light-bulb", + }, + className: "homepage-sidebar-item", + }, + ], + core: [ + { + type: "ref", + id: "homepage", + label: "Back to home", + customProps: { + sidebar_is_back_link: true, + sidebar_icon: "back-arrow", + }, + }, + { + type: "doc", + id: "b/overview", + label: "Overview", + customProps: { + sidebar_is_title: true, + sidebar_icon: "server-stack", + }, + }, + { + type: "category", + label: "Subcommands", + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: "doc", + id: "b/subcommands/init", + label: "init", + }, + { + type: "doc", + id: "b/subcommands/install", + label: "install", + }, + { + type: "doc", + id: "b/subcommands/list", + label: "list", + }, + { + type: "doc", + id: "b/subcommands/update", + label: "update", + }, + { + type: "doc", + id: "b/subcommands/search", + label: "search", + }, + { + type: "doc", + id: "b/subcommands/version", + label: "version", + }, + { + type: "doc", + id: "b/subcommands/request", + label: "request", + }, + ], + }, + ], +} diff --git a/www/apps/docs/src/components/BorderedIcon/index.tsx b/www/apps/docs/src/components/BorderedIcon/index.tsx new file mode 100644 index 0000000..b6235ca --- /dev/null +++ b/www/apps/docs/src/components/BorderedIcon/index.tsx @@ -0,0 +1,33 @@ +import React from "react" +import type { BorderedIconProps as UiBorderedIconProps } from "docs-ui" +import { BorderedIcon as UiBorderedIcon } from "docs-ui" +import { useColorMode } from "@docusaurus/theme-common" + +type BorderedIconProps = { + icon?: { + light: string + dark?: string + } +} & Omit + +const BorderedIcon: React.FC = ({ + icon = null, + ...props +}) => { + const { colorMode } = useColorMode() + + return ( + + ) +} + +export default BorderedIcon diff --git a/www/apps/docs/src/components/CloudinaryImage/index.tsx b/www/apps/docs/src/components/CloudinaryImage/index.tsx new file mode 100644 index 0000000..b73ac78 --- /dev/null +++ b/www/apps/docs/src/components/CloudinaryImage/index.tsx @@ -0,0 +1,106 @@ +import React from "react" +import { useThemeConfig } from "@docusaurus/theme-common" +// @ts-expect-error: wait until docusaurus uses type: module +import { Cloudinary } from "@cloudinary/url-gen" +import MDXImg, { Props as MDXImgProps } from "@theme/MDXComponents/Img" +import { + pad, + imaggaScale, + imaggaCrop, + crop, + fit, + minimumPad, + fill, + scale, + limitFit, + thumbnail, + limitFill, + minimumFit, + limitPad, + fillPad, +} from "@cloudinary/url-gen/actions/resize" +import { byRadius } from "@cloudinary/url-gen/actions/roundCorners" +import { ThemeConfig } from "@medusajs/docs" + +const resizeActions = { + pad: pad, + imaggaScale: imaggaScale, + imaggaCrop: imaggaCrop, + crop: crop, + fit: fit, + minimumPad: minimumPad, + fill: fill, + scale: scale, + limitFit: limitFit, + thumbnail: thumbnail, + limitFill: limitFill, + minimumFit: minimumFit, + limitPad: limitPad, + fillPad: fillPad, +} + +const imageRegex = + /^https:\/\/res.cloudinary.com\/.*\/upload\/v[0-9]+\/(?.*)$/ + +type CloudinaryImageProps = MDXImgProps + +const CloudinaryImage: React.FC = ({ src, ...props }) => { + const { cloudinaryConfig } = useThemeConfig() as ThemeConfig + const matchingRegex = src.match(imageRegex) + if ( + !cloudinaryConfig || + !matchingRegex?.groups || + !matchingRegex.groups.imageId + ) { + // either cloudinary isn't configured or + // could not match url to a cloudinary url + // default to docusaurus's image component + return + } + + const cloudinary = new Cloudinary({ + cloud: { + cloudName: cloudinaryConfig.cloudName, + }, + }) + const image = cloudinary.image( + matchingRegex.groups.imageId.replaceAll("%20", " ") + ) + + cloudinaryConfig.flags?.forEach((flag) => image.addTransformation(flag)) + + if (cloudinaryConfig.roundCorners) { + image.roundCorners(byRadius(cloudinaryConfig.roundCorners)) + } + if (cloudinaryConfig.resize) { + const action = resizeActions[cloudinaryConfig.resize.action] + let resizeAction = action() + if (props.width || props.height) { + if (props.width) { + resizeAction = resizeAction.width(props.width) + } + + if (props.height) { + resizeAction = resizeAction.height(props.height) + } + } else if (cloudinaryConfig.resize.aspectRatio) { + resizeAction = resizeAction.aspectRatio( + cloudinaryConfig.resize.aspectRatio + ) + } else { + if (cloudinaryConfig.resize.width) { + resizeAction = resizeAction.width(cloudinaryConfig.resize.width) + } + + if (cloudinaryConfig.resize.height) { + resizeAction = resizeAction.height(cloudinaryConfig.resize.height) + } + } + + image.resize(resizeAction) + } + + return +} + +export default CloudinaryImage diff --git a/www/apps/docs/src/components/DetailsList/index.tsx b/www/apps/docs/src/components/DetailsList/index.tsx new file mode 100644 index 0000000..29dba99 --- /dev/null +++ b/www/apps/docs/src/components/DetailsList/index.tsx @@ -0,0 +1,34 @@ +import React from "react" +import Details from "../../theme/Details" +import clsx from "clsx" +import { MarkdownContent } from "docs-ui" + +type TroubleshootingSection = { + title: string + content: React.ReactNode +} + +type DetailsListProps = { + sections: TroubleshootingSection[] +} & React.AllHTMLAttributes + +const DetailsList: React.FC = ({ sections }) => { + return ( + <> + {sections.map(({ title, content }, index) => ( +
+ {React.isValidElement(content) && content} + {!React.isValidElement(content) && typeof content === "string" && ( + content + )} +
+ ))} + + ) +} + +export default DetailsList diff --git a/www/apps/docs/src/components/Diagram2CodeSpecs/index.tsx b/www/apps/docs/src/components/Diagram2CodeSpecs/index.tsx new file mode 100644 index 0000000..9a1d946 --- /dev/null +++ b/www/apps/docs/src/components/Diagram2CodeSpecs/index.tsx @@ -0,0 +1,54 @@ +import React from "react" +import { Diagram2CodeSpec } from "@medusajs/docs" +import Tabs from "@theme/Tabs" +import TabItem from "@theme/TabItem" +import { specs } from "../../utils/specs" +import CodeBlock from "../../theme/CodeBlock" +import Mermaid from "@docusaurus/theme-mermaid/lib/theme/Mermaid/index.js" +import Heading from "@theme/Heading" + +type WorkflowReferenceProps = { + specName: string +} + +const Diagram2CodeSpecs = ({ specName }: WorkflowReferenceProps) => { + if (!Object.hasOwn(specs, specName)) { + return <> + } + const specsData: Diagram2CodeSpec = specs[specName] + + const transformTitle = (title: string): string => { + return title + .split("-") + .map((word) => `${word.charAt(0).toUpperCase()}${word.substring(1)}`) + .join(" ") + } + + return ( + <> + {!Object.keys(specsData).length && No diagrams found} + {Object.entries(specsData).map(([name, diagram2code]) => ( + + {transformTitle(name)} + + + + + {diagram2code.code && ( + + {diagram2code.code} + + )} + + + ))} + + ) +} + +export default Diagram2CodeSpecs diff --git a/www/apps/docs/src/components/DocSidebarItemIcon/index.tsx b/www/apps/docs/src/components/DocSidebarItemIcon/index.tsx new file mode 100644 index 0000000..40bfc51 --- /dev/null +++ b/www/apps/docs/src/components/DocSidebarItemIcon/index.tsx @@ -0,0 +1,48 @@ +import React from "react" +import icons, { IconName } from "../../theme/Icon" +import BorderedIcon from "../BorderedIcon" +import clsx from "clsx" + +type DocSidebarItemIconProps = { + icon?: IconName + is_title?: boolean + is_disabled?: boolean +} & React.HTMLAttributes + +const DocSidebarItemIcon: React.FC = ({ + icon, + is_title, + is_disabled, +}) => { + let IconComponent = undefined + if (icon) { + IconComponent = icons[icon] + } + + return ( + <> + {is_title && ( + + )} + {!is_title && IconComponent && ( + + )} + + ) +} + +export default DocSidebarItemIcon diff --git a/www/apps/docs/src/components/Feedback/index.tsx b/www/apps/docs/src/components/Feedback/index.tsx new file mode 100644 index 0000000..fb352db --- /dev/null +++ b/www/apps/docs/src/components/Feedback/index.tsx @@ -0,0 +1,28 @@ +import React from "react" +import { + Feedback as UiFeedback, + type FeedbackProps as UiFeedbackProps, + GITHUB_ISSUES_PREFIX, +} from "docs-ui" +import useIsBrowser from "@docusaurus/useIsBrowser" +import { useLocation } from "@docusaurus/router" +import clsx from "clsx" + +type FeedbackProps = Omit + +const Feedback = (props: FeedbackProps) => { + const isBrowser = useIsBrowser() + const location = useLocation() + + return ( + + ) +} + +export default Feedback diff --git a/www/apps/docs/src/components/Footer/SocialLinks/index.tsx b/www/apps/docs/src/components/Footer/SocialLinks/index.tsx new file mode 100644 index 0000000..31949b1 --- /dev/null +++ b/www/apps/docs/src/components/Footer/SocialLinks/index.tsx @@ -0,0 +1,44 @@ +import React from "react" +import IconTwitter from "@site/src/theme/Icon/Twitter" +import IconGitHub from "@site/src/theme/Icon/GitHub" +import IconDiscord from "@site/src/theme/Icon/Discord" +import IconLinkedIn from "@site/src/theme/Icon/LinkedIn" +import { SocialLink } from "@medusajs/docs" +import Link from "@docusaurus/Link" + +type SocialLinksProps = { + links?: SocialLink[] +} & React.HTMLAttributes + +const SocialLinks: React.FC = ({ links = [] }) => { + const socialIcons = { + twitter: ( + + ), + github: ( + + ), + discord: ( + + ), + linkedin: ( + + ), + } + + return ( +
+ {links.map((link) => ( + + {socialIcons[link.type]} + + ))} +
+ ) +} + +export default SocialLinks diff --git a/www/apps/docs/src/components/Glossary/index.tsx b/www/apps/docs/src/components/Glossary/index.tsx new file mode 100644 index 0000000..1f680c8 --- /dev/null +++ b/www/apps/docs/src/components/Glossary/index.tsx @@ -0,0 +1,50 @@ +import React, { useMemo } from "react" +import Heading from "@theme/Heading" +import { GlossaryType, getGlossary } from "../../utils/glossary" +import Link from "@docusaurus/Link" + +type GlossaryProps = React.HTMLAttributes + +type GroupedGlossary = { + [letter: string]: GlossaryType[] +} + +const Glossary: React.FC = (props) => { + const groupedGlossary: GroupedGlossary = useMemo(() => { + const glossary = getGlossary() + glossary.sort((a, b) => { + return a.title.localeCompare(b.title) + }) + const grouped: GroupedGlossary = {} + glossary.forEach((glossaryItem) => { + const firstChar = glossaryItem.title.charAt(0).toLowerCase() + grouped[firstChar] = [...(grouped[firstChar] || []), glossaryItem] + }) + + return grouped + }, []) + + return ( +
+ {Object.entries(groupedGlossary).map(([letter, glossary], index) => ( + + + {letter.toUpperCase()} + +
    + {glossary.map((glossaryItem, index) => ( +
  • + + {glossaryItem.title} + + : {glossaryItem.content} +
  • + ))} +
+
+ ))} +
+ ) +} + +export default Glossary diff --git a/www/apps/docs/src/components/LargeCard/index.tsx b/www/apps/docs/src/components/LargeCard/index.tsx new file mode 100644 index 0000000..91b2a5c --- /dev/null +++ b/www/apps/docs/src/components/LargeCard/index.tsx @@ -0,0 +1,98 @@ +import React from "react" +import clsx from "clsx" +import Link from "@docusaurus/Link" +import { Badge } from "docs-ui" +import BorderedIcon from "../BorderedIcon" +import { IconProps } from "@medusajs/icons/dist/types" + +type LargeCardProps = { + Icon: React.FC + image: { + light: string + dark?: string + } + title: string + action?: { + href?: string + } + isSoon?: boolean + className?: string +} & React.HTMLAttributes + +const LargeCard: React.FC = ({ + Icon, + image, + title, + action: { href } = { + href: "", + }, + isSoon = false, + className = "", + children, +}) => { + return ( +
+
+ {isSoon && ( + + Guide coming soon + + )} + {(Icon || image) && ( + + )} +
+ + {title} + +
+
+ {children} +
+
+ {href && ( + + )} +
+ ) +} + +export default LargeCard diff --git a/www/apps/docs/src/components/LargeCardList/index.tsx b/www/apps/docs/src/components/LargeCardList/index.tsx new file mode 100644 index 0000000..32b40ed --- /dev/null +++ b/www/apps/docs/src/components/LargeCardList/index.tsx @@ -0,0 +1,30 @@ +import React from "react" +import clsx from "clsx" + +type LargeCardListProps = { + colSize?: string + className?: string +} & React.HTMLAttributes + +const LargeCardList: React.FC = ({ + colSize = "4", + className, + children, +}) => { + return ( +
+ {children} +
+ ) +} + +export default LargeCardList diff --git a/www/apps/docs/src/components/MobileLogo/index.tsx b/www/apps/docs/src/components/MobileLogo/index.tsx new file mode 100644 index 0000000..45a16aa --- /dev/null +++ b/www/apps/docs/src/components/MobileLogo/index.tsx @@ -0,0 +1,79 @@ +import React from "react" +import Link from "@docusaurus/Link" +import useBaseUrl from "@docusaurus/useBaseUrl" +import useDocusaurusContext from "@docusaurus/useDocusaurusContext" +import { useThemeConfig, type NavbarLogo } from "@docusaurus/theme-common" +import ThemedImage from "@theme/ThemedImage" +import type { Props } from "@theme/Logo" +import { ThemeConfig } from "@medusajs/docs" + +function LogoThemedImage({ + logo, + alt, + imageClassName, +}: { + logo: NavbarLogo + alt: string + imageClassName?: string +}) { + const sources = { + light: useBaseUrl(logo.src), + dark: useBaseUrl(logo.srcDark || logo.src), + } + const themedImage = ( + + ) + + // Is this extra div really necessary? + // introduced in https://github.com/facebook/docusaurus/pull/5666 + return imageClassName ? ( +
{themedImage}
+ ) : ( + themedImage + ) +} + +export default function MobileLogo(props: Omit): JSX.Element { + const { + siteConfig: { title }, + } = useDocusaurusContext() + const { + navbar: { title: navbarTitle }, + mobileLogo: logo, + } = useThemeConfig() as ThemeConfig + + const { imageClassName, titleClassName, ...propsRest } = props + const logoLink = useBaseUrl(logo?.href || "/") + + // If visible title is shown, fallback alt text should be + // an empty string to mark the logo as decorative. + const fallbackAlt = navbarTitle ? "" : title + + // Use logo alt text if provided (including empty string), + // and provide a sensible fallback otherwise. + const alt = logo?.alt ?? fallbackAlt + + return ( + + {logo && ( + + )} + {navbarTitle != null && {navbarTitle}} + + ) +} diff --git a/www/apps/docs/src/components/Navbar/Actions/index.tsx b/www/apps/docs/src/components/Navbar/Actions/index.tsx new file mode 100644 index 0000000..79de22d --- /dev/null +++ b/www/apps/docs/src/components/Navbar/Actions/index.tsx @@ -0,0 +1,80 @@ +import React from "react" +import { NavbarAction } from "@medusajs/docs" +import Icon from "../../../theme/Icon" +import clsx from "clsx" +import { Button, Tooltip } from "docs-ui" +import Link from "@docusaurus/Link" + +type NavbarActionsProps = { + items: NavbarAction[] + className?: string +} & React.HTMLAttributes + +const NavbarActions: React.FC = ({ + items = [], + className = "", +}) => { + return ( +
+ {items.map((item, index) => { + // eslint-disable-next-line no-case-declarations + const ItemIconElm = item.Icon + const ItemIcon = item.icon ? Icon[item.icon] : null + switch (item.type) { + case "link": + return ( + + + {item.label} + {ItemIconElm} + {ItemIcon && } + + + ) + case "button": + return ( + + + + ) + default: + return <> + } + })} +
+ ) +} + +export default NavbarActions diff --git a/www/apps/docs/src/components/ParameterTypes/Items/index.tsx b/www/apps/docs/src/components/ParameterTypes/Items/index.tsx new file mode 100644 index 0000000..ede8aa6 --- /dev/null +++ b/www/apps/docs/src/components/ParameterTypes/Items/index.tsx @@ -0,0 +1,291 @@ +import { + CopyButton, + DetailsSummary, + ExpandableNotice, + FeatureFlagNotice, + InlineCode, + MarkdownContent, +} from "docs-ui" +import React, { useEffect, useMemo, useRef, useState } from "react" +import Details from "../../../theme/Details" +import clsx from "clsx" +import { Parameter } from ".." +import { + ArrowDownLeftMini, + ArrowsPointingOutMini, + Link, + TriangleRightMini, +} from "@medusajs/icons" +import IconFlagMini from "../../../theme/Icon/FlagMini" +import decodeStr from "../../../utils/decode-str" +import { useLocation } from "@docusaurus/router" +import useDocusaurusContext from "@docusaurus/useDocusaurusContext" +import isInView from "../../../utils/is-in-view" + +type CommonProps = { + level?: number + expandUrl?: string + sectionTitle?: string +} + +type ParameterTypesItemProps = { + parameter: Parameter + elementKey: number +} & CommonProps & + React.AllHTMLAttributes + +const ParameterTypesItem = ({ + parameter, + level = 1, + expandUrl, + elementKey, + sectionTitle, +}: ParameterTypesItemProps) => { + const location = useLocation() + + const { + siteConfig: { url }, + } = useDocusaurusContext() + + const groupName = useMemo(() => { + switch (level) { + case 1: + return "group/parameterOne" + case 2: + return "group/parameterTwo" + case 3: + return "group/parameterThree" + case 4: + return "group/parameterFour" + } + }, [level]) + const borderForGroupName = useMemo(() => { + switch (level) { + case 1: + return "group-open/parameterOne:border-solid group-open/parameterOne:border-0 group-open/parameterOne:border-b" + case 2: + return "group-open/parameterTwo:border-solid group-open/parameterTwo:border-0 group-open/parameterTwo:border-b" + case 3: + return "group-open/parameterThree:border-solid group-open/parameterThree:border-0 group-open/parameterThree:border-b" + case 4: + return "group-open/parameterFour:border-solid group-open/parameterFour:border-0 group-open/parameterFour:border-b" + } + }, [level]) + const rotateForGroupName = useMemo(() => { + switch (level) { + case 1: + return "group-open/parameterOne:rotate-90" + case 2: + return "group-open/parameterTwo:rotate-90" + case 3: + return "group-open/parameterThree:rotate-90" + case 4: + return "group-open/parameterFour:rotate-90" + } + }, [level]) + function getItemClassNames(details = true) { + return clsx( + "odd:[&:not(:first-child):not(:last-child)]:!border-y last:not(:first-child):!border-t", + "first:!border-t-0 first:not(:last-child):!border-b last:!border-b-0 even:!border-y-0", + details && groupName, + !details && borderForGroupName + ) + } + const formatId = (str: string) => { + return str.replaceAll(" ", "_") + } + const parameterId = useMemo(() => { + return sectionTitle + ? `#${formatId(sectionTitle)}-${formatId( + parameter.name + )}-${level}-${elementKey}` + : "" + }, [sectionTitle, parameter, elementKey]) + const parameterPath = useMemo( + () => `${location.pathname}${parameterId}`, + [location, parameterId] + ) + const parameterUrl = useMemo( + () => `${url}${parameterPath}`, + [url, parameterPath] + ) + + const ref = useRef() + const [isSelected, setIsSelected] = useState(false) + + useEffect(() => { + if (!parameterId.length) { + return + } + + const shouldScroll = location.hash === parameterId + if (shouldScroll && !isSelected && ref.current && !isInView(ref.current)) { + ref.current.scrollIntoView({ + block: "center", + }) + } + + setIsSelected(shouldScroll) + }, [parameterId]) + + function getSummary(parameter: Parameter, nested = true) { + return ( + + + {parameter.description} + + {parameter.defaultValue && ( +

+ Default: {parameter.defaultValue} +

+ )} + + ) : undefined + } + expandable={parameter.children?.length > 0} + hideExpandableIcon={true} + className={clsx( + getItemClassNames(false), + "py-1 pr-1", + level === 1 && "pl-1", + level === 2 && "pl-3", + level === 3 && "pl-[120px]", + level === 4 && "pl-[160px]", + !nested && "cursor-default", + isSelected && "animate-flash animate-bg-surface" + )} + onClick={(e) => { + const targetElm = e.target as HTMLElement + if (targetElm.tagName.toLowerCase() === "a") { + window.location.href = + targetElm.getAttribute("href") || window.location.href + return + } + }} + summaryRef={!nested ? ref : undefined} + id={!nested && parameterId ? parameterId : ""} + > +
+ {nested && ( + + )} + {!nested && level > 1 && ( + + )} + {level === 1 && parameterId.length > 0 && ( + ) => { + e.preventDefault() + e.stopPropagation() + }} + > + + + )} +
+ {decodeStr(parameter.name)} + + + {parameter.type} + + + {parameter.optional === false && ( + + Required + + )} + {parameter.featureFlag && ( + + } + /> + )} + {parameter.expandable && ( + } + /> + )} +
+
+
+ ) + } + + return ( + <> + {parameter.children?.length > 0 && ( +
+ {parameter.children && ( + + )} +
+ )} + {(parameter.children?.length || 0) === 0 && getSummary(parameter, false)} + + ) +} + +type ParameterTypesItemsProps = { + parameters: Parameter[] +} & CommonProps + +const ParameterTypesItems = ({ + parameters, + ...rest +}: ParameterTypesItemsProps) => { + return ( +
+ {parameters.map((parameter, key) => ( + + ))} +
+ ) +} + +export default ParameterTypesItems diff --git a/www/apps/docs/src/components/ParameterTypes/index.tsx b/www/apps/docs/src/components/ParameterTypes/index.tsx new file mode 100644 index 0000000..5c52ad8 --- /dev/null +++ b/www/apps/docs/src/components/ParameterTypes/index.tsx @@ -0,0 +1,45 @@ +import clsx from "clsx" +import React, { Suspense, lazy } from "react" +import { Loading } from "docs-ui" + +export type Parameter = { + name: string + type: string + optional?: boolean + defaultValue?: string + description?: string + featureFlag?: string + expandable: boolean + children?: Parameter[] +} + +type ParameterTypesType = { + parameters: Parameter[] + expandUrl?: string + sectionTitle?: string +} & React.HTMLAttributes + +const ParameterTypesItems = lazy(async () => import("./Items")) + +const ParameterTypes = ({ + parameters, + className, + ...props +}: ParameterTypesType) => { + return ( +
+ }> + + +
+ ) +} + +export default ParameterTypes diff --git a/www/apps/docs/src/components/QueryNote/index.tsx b/www/apps/docs/src/components/QueryNote/index.tsx new file mode 100644 index 0000000..f3d61c1 --- /dev/null +++ b/www/apps/docs/src/components/QueryNote/index.tsx @@ -0,0 +1,29 @@ +import Admonition, { Props as AdmonitionProps } from "@theme/Admonition" +import { useQueryStringValue } from "@docusaurus/theme-common/internal" +import React from "react" + +type QueryNoteProps = { + query: { + key: string + value?: string + } + admonition: AdmonitionProps +} & React.HTMLAttributes + +const QueryNote: React.FC = ({ + query: { key, value = "" }, + admonition, + children, +}) => { + const queryValue = useQueryStringValue(key) + + return ( + <> + {queryValue === value && ( + {children} + )} + + ) +} + +export default QueryNote diff --git a/www/apps/docs/src/components/StructuredData/HowTo/index.tsx b/www/apps/docs/src/components/StructuredData/HowTo/index.tsx new file mode 100644 index 0000000..48c828b --- /dev/null +++ b/www/apps/docs/src/components/StructuredData/HowTo/index.tsx @@ -0,0 +1,44 @@ +import React from "react" +import Head from "@docusaurus/Head" +import { useLocation } from "@docusaurus/router" +import useDocusaurusContext from "@docusaurus/useDocusaurusContext" +import type { TOCItem } from "@docusaurus/mdx-loader" + +type StructuredDataHowToProps = { + toc: readonly TOCItem[] + title: string +} + +const StructuredDataHowTo: React.FC = ({ + toc, + title, +}) => { + const location = useLocation() + const { + siteConfig: { url }, + } = useDocusaurusContext() + const mainUrl = `${url}/${location.pathname}` + + return ( + + + + ) +} + +export default StructuredDataHowTo diff --git a/www/apps/docs/src/components/UiIcon/index.tsx b/www/apps/docs/src/components/UiIcon/index.tsx new file mode 100644 index 0000000..a1dd0a9 --- /dev/null +++ b/www/apps/docs/src/components/UiIcon/index.tsx @@ -0,0 +1,27 @@ +import React from "react" +import ThemedImage from "@theme/ThemedImage" + +type UiIconProps = { + lightIcon: string + darkIcon?: string + alt?: string +} + +const UiIcon: React.FC = ({ + lightIcon, + darkIcon = "", + alt = "", +}) => { + return ( + + ) +} + +export default UiIcon diff --git a/www/apps/docs/src/css/_docusaurus.css b/www/apps/docs/src/css/_docusaurus.css new file mode 100644 index 0000000..20166d2 --- /dev/null +++ b/www/apps/docs/src/css/_docusaurus.css @@ -0,0 +1,287 @@ +/** This CSS file includes tailwind styling definitions for classes defined by docusaurus **/ +html, +body { + @apply h-full; +} + +.cards-grid { + @apply grid gap-x-1 auto-rows-fr; +} + +.cards-grid.grid-6 { + @apply md:grid-cols-2 grid-cols-1; +} + +.cards-grid.grid-4 { + @apply lg:grid-cols-3 md:grid-cols-2 grid-cols-1; +} + +h1 + .cards-grid, +h1 + .card-wrapper, +h2 + .cards-grid, +h2 + .card-wrapper, +h3 + .cards-grid, +h3 + .card-wrapper { + @apply mt-1.5; +} + +.markdown p + img, +.markdown .alert, +.markdown .code-wrapper { + @apply mb-2; +} + +.markdown h2, +.markdown h3, +.markdown p + .code-wrapper, +.markdown p + .tabs-wrapper, +.markdown p + .alert, +.markdown p + section, +video { + @apply mt-2; +} + +.theme-code-block { + @apply border border-solid border-medusa-code-border; +} + +.markdown p + .card-wrapper { + @apply mt-1.5; +} + +.card + p, +details + p { + @apply mt-2; +} + +.card-highlighted { + @apply relative mb-4 bg-medusa-bg-subtle dark:bg-medusa-bg-base hover:bg-medusa-bg-subtle-hover dark:hover:bg-medusa-bg-base-hover; +} + +.col.toc-wrapper { + --ifm-col-width: var(--ifm-toc-width); +} + +.markdown-doc-wrapper--fluid { + @apply max-w-[inherit]; +} + +:global(.docusaurus-mt-lg) { + @apply mt-3; +} + +:global(#__docusaurus) { + @apply min-h-full flex flex-col; +} + +.code-tabs .theme-code-block { + @apply !rounded-t-none !rounded-b +} + +[class*=codeLineNumber] { + @apply text-medusa-code-text-subtle !pl-0; +} + +.prism-code { + @apply xs:max-w-[90%] text-code-body [&_*]:text-code-body xs:after:content-[''] xs:after:rounded xs:after:absolute; + @apply xs:after:right-0 xs:after:top-0 xs:after:w-[calc(10%+24px)] xs:after:h-full xs:after:bg-code-fade; +} + +.prism-code:not(:hover)::-webkit-scrollbar-thumb, +.prism-code:not(:hover)::-webkit-scrollbar-track { + @apply xs:invisible; +} + +.prism-code:hover::-webkit-scrollbar-thumb, +.prism-code:hover::-webkit-scrollbar-track { + @apply xs:opacity-100 +} + +.prism-code { + @apply bg-transparent break-words !outline-none; +} + +.prism-code div { + @apply !outline-none focus:!outline-none active:!outline-none; +} + +.code-tabs { + @apply relative; +} + +.sidebar-desktop nav { + --ifm-scrollbar-track-background-color: transparent !important; + --ifm-scrollbar-thumb-background-color: transparent !important; + --ifm-scrollbar-thumb-hover-background-color: transparent !important; +} + +.main-wrapper { + @apply max-w-xl mx-auto xl:min-w-xl w-full; +} + +.theme-doc-breadcrumbs { + @apply hidden; +} + +article { + @apply max-w-[960px]; +} + +.padding-top--md { + @apply !pt-3; +} + +.margin-bottom--lg { + @apply !mb-1; +} + +details summary { + @apply cursor-pointer; +} + +.reference-table { + @apply !table table-fixed w-full; +} + +.reference-table p { + --ifm-paragraph-margin-bottom: 0; +} + +.reference-table.table-col-4 th, +.reference-table.table-col-4 td { + @apply [&:nth-child(1)]:!w-1/5 [&:nth-child(2)]:!w-1/5 [&:nth-child(3)]:!w-2/5 [&:nth-child(4)]:!w-1/5; +} + +.reference-table th:nth-child(1), +.reference-table td:nth-child(1) { + @apply [&:nth-child(1)]:!w-[30%] [&:nth-child(2)]:!w-[30%] [&:nth-child(3)]:!w-2/5; +} + +.reference-table .tooltip-container code { + @apply whitespace-nowrap; +} + +.reference-table .theme-code-block span { + @apply !max-w-full !break-words !whitespace-break-spaces; +} + +.theme-code-block { + @apply !bg-medusa-code-bg-base; +} + +.reference-table .code-block-numbering { + @apply !block; +} + +.container { + @apply !pt-3 !max-w-full; +} + +.pagination-nav { + @apply hidden; +} + +.theme-doc-footer { + @apply !mt-0 border-0 !border-t border-solid !border-medusa-border-base; + @apply pt-2; +} + +.theme-last-updated { + @apply font-normal not-italic text-compact-small-plus; +} + +.theme-last-updated, +.theme-last-updated b { + @apply !font-normal; +} + +.medium-zoom-overlay { + @apply z-[400]; +} + +.medium-zoom-image--opened { + @apply z-[400]; +} + +details > div { + --docusaurus-details-decoration-color: transparent !important; +} + +.row--justify-end { + @apply justify-end; +} + +.docs-page-container { + @apply !px-0; +} + +.search-result-match { + @apply text-medusa-fg-base; + @apply bg-medusa-bg-highlight py-0.25 px-0; +} + +.navbar { + @apply z-[399] p-0 border-0 border-b border-solid border-medusa-border-base; +} + +html:not(.plugin-redoc) .navbar:not(.navbar-sidebar--show) { + @apply supports-[backdrop-filter]:bg-transparent supports-[backdrop-filter]:backdrop-blur-md; +} + +.navbar__link { + @apply text-compact-small-plus; +} + +.navbar__brand { + @apply mr-0; +} + +.navbar__logo { + @apply w-[82px] h-[20px] lg:w-[20px] lg:h-[20px] flex justify-center items-center !mr-0; +} + +.navbar__item { + @apply p-0 lg:!block !hidden; +} + +h1 { + @apply !text-h1; +} + +h2 { + @apply !text-h2; +} + +h3 { + @apply !text-h3; +} + +h1, h2, h3, h4, h5, h6 { + @apply text-medusa-fg-base; +} + +.markdown > h1:first-child, +.markdown > h2, +.markdown > h3 { + @apply mb-0.5; +} + +.markdown > p { + @apply mb-0.5; +} + +.markdown > p img { + @apply mt-0.5; +} + +.markdown { + @apply mb-0; +} + +*::selection { + @apply bg-medusa-bg-highlight; +} + +.prism-code *::selection, .code-header *::selection { + @apply bg-medusa-code-text-highlight; +} \ No newline at end of file diff --git a/www/apps/docs/src/css/_variables.css b/www/apps/docs/src/css/_variables.css new file mode 100644 index 0000000..c1a00d8 --- /dev/null +++ b/www/apps/docs/src/css/_variables.css @@ -0,0 +1,144 @@ +/* You can override the default Infima variables here. */ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap'); + +:root { + /* Base Styles */ + --ifm-hr-margin-vertical: theme(margin.2); + --ifm-leading: theme(lineHeight.DEFAULT); + --ifm-list-left-padding: theme(spacing.1); + --font-inter: "Inter"; + --font-roboto-mono: "Roboto Mono"; + + /* Colors */ + --ifm-color-primary: theme(colors.medusa.fg.base); + --ifm-background-color: theme(colors.medusa.bg.base.DEFAULT); + --ifm-background-surface-color: theme(colors.medusa.bg.subtle.DEFAULT); + --ifm-color-content: theme(colors.medusa.fg.subtle) !important; + --ifm-color-content-secondary: var(--ifm-color-content); + --ifm-base-border-color: theme(colors.medusa.border.base); + --ifm-strong-border-color: theme(colors.medusa.border.strong); + --ifm-hr-background-color: theme(colors.medusa.border.base); + + /* Fonts */ + --ifm-code-font-size: theme(fontSize.code-label); + --ifm-font-family-base: theme(fontFamily.base); + --ifm-font-family-monospace: theme(fontFamily.monospace); + --ifm-font-size-base: theme(fontSize.medium[0]); + --ifm-line-height-base: theme(fontSize.medium[1].lineHeight); + --ifm-font-weight-base: theme(fontSize.medium[1].fontWeight); + + /* Headings */ + /** Due to how docusaurus styles headings, the styles are applied on h1, h2, and h3 again in _docusaurus **/ + --ifm-color-headers: var(--ifm-color-primary); + --ifm-h1-font-size: theme(fontSize.h1[0]); + --ifm-h1-line-height: theme(fontSize.h1[1].lineHeight); + --ifm-h1-weight: theme(fontSize.h1[1].fontWeight); + --ifm-h2-font-size: theme(fontSize.h2[0]); + --ifm-h2-line-height: theme(fontSize.h2[1].lineHeight); + --ifm-h2-weight: theme(fontSize.h2[1].fontWeight); + --ifm-h3-font-size: theme(fontSize.h3[0]); + --ifm-h3-line-height: theme(fontSize.h3[1].lineHeight); + --ifm-h3-weight: theme(fontSize.h3[1].fontWeight); + --ifm-h4-font-size: var(--ifm-font-size-base); + + /* Links */ + --ifm-link-color: theme(colors.medusa.fg.interactive.DEFAULT); + --ifm-link-hover-color: theme(colors.medusa.fg.interactive.hover); + --ifm-link-decoration: none; + --ifm-link-hover-decoration: none; + + /* Sidebar */ + --ifm-menu-link-padding-vertical: 6px; + --ifm-menu-link-padding-horizontal: theme(margin.1); + --ifm-menu-color: theme(colors.medusa.fg.subtle); + --ifm-menu-color-active: theme(colors.medusa.fg.base); + --ifm-menu-color-background-hover: theme(colors.medusa.bg.base.hover); + --ifm-menu-color-background-active: theme(colors.medusa.bg.base.pressed); + + /* Toc */ + --ifm-toc-border-color: theme(colors.medusa.border.base); + --ifm-toc-link-color: theme(colors.medusa.fg.subtle); + --ifm-toc-padding-horizontal: theme(padding.1); + + /* Navbar */ + --ifm-navbar-background-color: var(--ifm-background-color); + --ifm-navbar-shadow: none; + --ifm-navbar-padding-vertical: 12px; + --ifm-navbar-padding-horizontal: theme(padding[1.5]); + --ifm-navbar-item-padding-vertical: 6px; + --ifm-navbar-item-padding-horizontal: theme(padding[1]); + --ifm-navbar-height: theme(height.navbar); + --ifm-navbar-link-hover-color: theme(colors.medusa.fg.base); + --ifm-navbar-link-color: theme(colors.medusa.fg.subtle); + + /* Inline Code */ + --ifm-code-border-radius: theme(borderRadius.DEFAULT); + --ifm-code-padding-horizontal: theme(padding[0.5]); + --ifm-code-background: theme(colors.medusa.tag.neutral.bg.DEFAULT) !important; + + /* Code Blocks */ + --ifm-pre-background: theme(colors.medusa.code.bg.base.DEFAULT); + --ifm-pre-padding: theme(padding.1); + + /* Tabs */ + --ifm-tabs-color-active: var(--ifm-color-primary); + + /* Tooltip */ + --rt-opacity: theme(opacity.100) !important; + --rt-color-dark: theme(colors.medusa.bg.base.DEFAULT) !important; + --rt-color-white: var(--ifm-color-content) !important; + + /* Footer */ + --ifm-footer-color: theme(colors.medusa.fg.muted); + --ifm-footer-background-color: transparent; + --ifm-footer-padding-horizontal: 0; + --ifm-footer-link-color: var(--ifm-color-content); + + /* Announcement Bar */ + --docusaurus-announcement-bar-height: auto !important; + + /* Tables */ + --ifm-table-border-color: theme(colors.medusa.border.base); + --ifm-table-head-background: var(--ifm-background-surface-color); + --ifm-table-head-color: var(--ifm-color-primary); + --ifm-table-head-font-weight: theme(fontSize.medium-plus[1].fontWeight); + --ifm-table-stripe-background: var(--ifm-background-surface-color) !important; +} + +html[data-theme="dark"] { + /* Colors */ + --ifm-background-color: theme(colors.medusa.bg.subtle.DEFAULT); + --ifm-background-surface-color: theme(colors.medusa.bg.base.DEFAULT); + + /* Sidebar */ + --ifm-menu-color-background-hover: theme(colors.medusa.bg.subtle.hover); + --ifm-menu-color-background-active: theme(colors.medusa.bg.subtle.pressed); + + /* Navbar */ + --ifm-navbar-shadow: none; +} + +@media screen and (min-width: 1441px) { + :root { + /** Table of Content **/ + --ifm-toc-width: calc(2 / 12 * 100%); + } +} + +@media screen and (max-width: 1440px) { + :root { + /** Table of Content **/ + --ifm-toc-width: calc(3 / 12 * 100%); + } +} + +@media (min-width: 997px) { + :root { + --docusaurus-announcement-bar-height: 30px; + } +} + +.alert { + --ifm-code-background: theme(colors.medusa.tag.neutral.bg.DEFAULT) !important; +} \ No newline at end of file diff --git a/www/apps/docs/src/css/components/sidebar.css b/www/apps/docs/src/css/components/sidebar.css new file mode 100644 index 0000000..2d86f29 --- /dev/null +++ b/www/apps/docs/src/css/components/sidebar.css @@ -0,0 +1,156 @@ +.theme-doc-sidebar-container { + --animate-duration: 0.2s; +} + +.sidebar-desktop nav { + --ifm-scrollbar-track-background-color: transparent !important; + --ifm-scrollbar-thumb-background-color: transparent !important; + --ifm-scrollbar-thumb-hover-background-color: transparent !important; +} + +.menu__list-item:not(:first-child):not(.sidebar-title) { + margin-top: 0 !important; +} + +.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) [class*=theme-doc-sidebar-item-], +.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) [class*=theme-doc-sidebar-item-] .menu__link--active { + @apply relative; +} + +.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) [class*=theme-doc-sidebar-item-]:not(.theme-doc-sidebar-item-link-category)::before, +.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) .theme-doc-sidebar-item-category .menu__list-item-collapsible .menu__link--active::before, +.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) > .theme-doc-sidebar-item-link > .menu__link--active::before, +.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) .theme-doc-sidebar-item-category > .menu__list-item-collapsible::before { + @apply content-[''] h-full w-[1px] absolute left-0.5 top-0 z-[1]; +} + +.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) [class*=theme-doc-sidebar-item-] .menu__link--active::before { + @apply !h-[20px]; +} + +.theme-doc-sidebar-item-category-level-1 > .menu__list > .menu__list-item:first-child::before, +.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) .theme-doc-sidebar-item-category:first-child .menu__list-item-collapsible::before, +.theme-doc-sidebar-item-category-level-1 > .menu__list > .menu__list-item:last-child::before { + @apply !h-[28px]; +} + +.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) .theme-doc-sidebar-item-link:first-child::before, +.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) .theme-doc-sidebar-item-category:first-child .menu__list-item-collapsible::before { + @apply !bottom-0 !top-[unset]; +} + +.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) .theme-doc-sidebar-item-category:first-child .menu__list-item-collapsible { + @apply !pb-[6px]; +} + +.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) .theme-doc-sidebar-item-category:first-child .menu__list-item-collapsible .menu__link { + @apply !pb-0; +} + +.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) [class*=theme-doc-sidebar-item-]::before, +.menu__list-item .menu__list:not(.theme-doc-sidebar-menu) .theme-doc-sidebar-item-category:first-child .menu__list-item-collapsible::before { + @apply bg-medusa-border-base; +} + +.theme-doc-sidebar-item-category-level-2 > .menu__list-item-collapsible .menu__link--active::before, +.menu__list:not(.theme-doc-sidebar-menu) > .theme-doc-sidebar-item-link.theme-doc-sidebar-item-link-level-2 > .menu__link--active::before { + @apply bg-medusa-fg-interactive !z-[2] !top-[6px]; +} + +.theme-doc-sidebar-item-category-level-2:only-child > .menu__list-item-collapsible .menu__link--active::before, +.menu__list:not(.theme-doc-sidebar-menu) > .theme-doc-sidebar-item-link.theme-doc-sidebar-item-link-level-2:only-child > .menu__link--active::before, +.menu__list:not(.theme-doc-sidebar-menu) > .theme-doc-sidebar-item-link.theme-doc-sidebar-item-link-level-2:first-child > .menu__link--active::before { + @apply !top-0.5; +} + +.theme-doc-sidebar-item-link-level-1:not(.sidebar-title):not(.homepage-sidebar-item):not(.sidebar-back-link) { + @apply mb-1; +} + +.theme-doc-sidebar-item-link-level-1:not(.sidebar-title):not(.homepage-sidebar-item):not(.sidebar-back-link) .menu__link { + @apply !pl-0; +} + +.theme-doc-sidebar-item-category-level-1.sidebar-group-headline { + @apply mb-1; +} + +[class*="level-2"]:is([class*="theme-doc-sidebar-item"]) .menu__link, +[class*="level-2"]:is([class*="theme-doc-sidebar-item"]).sidebar-group-divider, +[class*="level-3"]:is([class*="theme-doc-sidebar-item"]) .menu__link, +[class*="level-3"]:is([class*="theme-doc-sidebar-item"]).sidebar-group-divider, +[class*="level-4"]:is([class*="theme-doc-sidebar-item"]) .menu__link, +[class*="level-4"]:is([class*="theme-doc-sidebar-item"]).sidebar-group-divider { + @apply py-[6px]; +} + +[class*="level-2"]:is([class*="theme-doc-sidebar-item"]) .menu__link, +[class*="level-2"]:is([class*="theme-doc-sidebar-item"]).sidebar-group-divider { + @apply pl-2; +} + +[class*="level-3"]:is([class*="theme-doc-sidebar-item"]) .menu__link, +[class*="level-3"]:is([class*="theme-doc-sidebar-item"]).sidebar-group-divider { + @apply pl-3; +} + +[class*="level-4"]:is([class*="theme-doc-sidebar-item"]) .menu__link, +[class*="level-4"]:is([class*="theme-doc-sidebar-item"]).sidebar-group-divider { + @apply pl-4; +} + +.homepage-sidebar-item:is([class*="level-1"]):is([class*="theme-doc-sidebar-item"]) > .menu__link, +.homepage-sidebar-item:is([class*="level-1"]):is([class*="theme-doc-sidebar-item"]) > .menu__list-item-collapsible > .menu__link { + @apply !pl-0.5; +} + +.menu__list-item-collapsible { + @apply !rounded; +} + +.menu__list-item-collapsible--active { + @apply !bg-transparent; +} + +.menu__list-item-collapsible .menu__link--sublist:hover { + @apply !bg-medusa-bg-base-hover dark:!bg-medusa-bg-subtle-hover; +} + +.menu__list:not(.theme-doc-sidebar-menu) > .theme-doc-sidebar-item-link:last-child > .menu__link--active::before { + @apply !top-0.25; +} + +.menu__link--sublist-caret:after { + @apply content-none; +} + +.menu__link { + @apply cursor-pointer !rounded text-compact-small-plus; +} + +.theme-doc-sidebar-item-category-level-1 > .menu__list { + @apply mb-0.25; +} + +.menu__list:not(.theme-doc-sidebar-menu), +.theme-doc-sidebar-item-category-level-1 > .menu__list { + @apply !pl-0; +} + +.menu__list .menu__list { + @apply !mt-0; +} + +/** General sidebar styles **/ +.theme-doc-sidebar-container { + @apply z-[398] border-0 border-r border-solid border-medusa-border-base; +} + +/** Mobile Sidebar **/ +.navbar-sidebar__back { + @apply bg-transparent w-fit top-[unset] pl-0 ml-0 mb-1 text-compact-small-plus; +} + +.theme-doc-sidebar-item-link { + @apply flex items-center; +} \ No newline at end of file diff --git a/www/apps/docs/src/css/components/toc.css b/www/apps/docs/src/css/components/toc.css new file mode 100644 index 0000000..16855cf --- /dev/null +++ b/www/apps/docs/src/css/components/toc.css @@ -0,0 +1,60 @@ +.theme-doc-toc-desktop { + @apply pt-3; +} + +.theme-doc-toc-desktop .table-of-contents { + @apply !border-l-0 !p-0 text-[12px] relative overflow-y-hidden; + @apply before:bg-toc dark:before:bg-toc-dark; + @apply before:bg-no-repeat before:bg-[length:14px_10px] before:bg-[center_left]; + @apply before:pl-[22px] before:content-['On_this_page'] before:pb-0 text-compact-small-plus; + @apply after:content-[''] after:absolute after:left-0 after:top-2.5; + @apply after:h-full after:w-[1px] after:bg-medusa-border-base; +} + +.theme-doc-toc-desktop .table-of-contents .table-of-contents__link { + @apply pl-[11px]; +} + +.theme-doc-toc-desktop .table-of-contents > li { + @apply first:mt-1 [&:not(:first-child)]:mt-0 last:mb-0 [&:not(:last-child)]:mb-0.25; +} + +.theme-doc-toc-desktop .table-of-contents li a + ul { + @apply mt-0.25; +} + +.theme-doc-toc-desktop .table-of-contents__link { + @apply relative text-compact-x-small-plus; + @apply hover:text-medusa-fg-base; +} + +.theme-doc-toc-desktop .table-of-contents__link:hover code { + @apply text-medusa-fg-base; +} + +.theme-doc-toc-desktop .table-of-contents__link--active { + @apply text-medusa-fg-base; + @apply after:content-[''] after:absolute after:left-0 after:top-0; + @apply after:h-full after:w-[1px] after:bg-medusa-fg-base; + @apply z-[1]; +} + +.theme-doc-toc-desktop .table-of-contents__link--active code { + @apply text-medusa-fg-base; +} + +.theme-doc-toc-desktop .table-of-contents li { + @apply mx-0; +} + +.theme-doc-toc-desktop .table-of-contents ul li { + @apply mt-0; +} + +.table-of-contents ul { + @apply pl-0; +} + +.table-of-contents ul .table-of-contents__link { + @apply !pl-[27px]; +} \ No newline at end of file diff --git a/www/apps/docs/src/css/components/tooltip.css b/www/apps/docs/src/css/components/tooltip.css new file mode 100644 index 0000000..0a7b180 --- /dev/null +++ b/www/apps/docs/src/css/components/tooltip.css @@ -0,0 +1,10 @@ +.react-tooltip { + @apply !border !border-solid !border-medusa-border-base; + @apply !rounded !text-compact-x-small-plus !shadow-tooltip dark:!shadow-tooltip-dark; + @apply !py-0.4 !px-1 lg:block hidden; +} + +.react-tooltip-arrow { + @apply hidden; +} + diff --git a/www/apps/docs/src/css/custom.css b/www/apps/docs/src/css/custom.css new file mode 100644 index 0000000..b24507f --- /dev/null +++ b/www/apps/docs/src/css/custom.css @@ -0,0 +1,149 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + body { + @apply !overflow-x-hidden; + } + body[data-modal="opened"] { + @apply !overflow-hidden; + } +} + +@layer components { + .sidebar-title { + @apply !m-0 !py-1 !px-0; + @apply lg:first:top-0 lg:[&:not(:first-child)]:top-[20px]; + } + + .sidebar-title .menu__link { + @apply !px-0; + } + + .sidebar-title .menu__link, + .sidebar-title span { + @apply !text-compact-medium-plus text-medusa-fg-base; + } + + .sidebar-title .menu__link--active, + .sidebar-title .menu__link:hover { + @apply !bg-transparent; + } + + .sidebar-group-headline { + @apply mt-1 py-[6px] px-0; + } + + .sidebar-group-headline:not(.theme-doc-sidebar-item-category-level-1) { + @apply mb-[6px]; + } + + .sidebar-group-headline > .menu__link { + @apply text-medusa-fg-base; + } + + .sidebar-group-headline > .menu__list-item-collapsible > .menu__link { + @apply text-medusa-fg-base !p-0; + } + + .sidebar-group-headline > .menu__list-item-collapsible > .menu__link:not([href]) { + @apply cursor-default + } + + .sidebar-group-headline > .menu__list-item-collapsible > .menu__link:hover, + .sidebar-group-headline .menu__list-item-collapsible:hover { + @apply !bg-transparent; + } + + .sidebar-group-headline > .menu__link, + .sidebar-group-headline > .menu__list-item-collapsible > .menu__link { + @apply font-semibold; + } + + .sidebar-group-divider { + @apply pb-[6px] uppercase text-medusa-fg-muted text-compact-x-small-plus; + @apply first:pt-[6px] [&:not(:first-child)]:!pt-1; + } + + .sidebar-divider-line { + @apply h-[1px] w-full bg-medusa-border-base my-1 mx-0; + } + + .sidebar-back-link { + @apply cursor-pointer lg:top-0; + } + + .sidebar-back-link, + .sidebar-title { + @apply lg:sticky lg:bg-medusa-bg-base dark:lg:bg-medusa-bg-subtle lg:z-[100]; + } + + .sidebar-back-link .menu__link { + @apply !p-0 hover:!bg-transparent hover:text-medusa-fg-base; + } + + .sidebar-back-link .menu__link, + .sidebar-back-link span { + @apply text-compact-small-plus; + } + + .sidebar-back-link .sidebar-item-icon { + @apply mr-0.5; + } + + .sidebar-soon-link { + @apply pointer-events-none [&_*]:text-medusa-fg-disabled; + } + + + .sidebar-badge-wrapper { + @apply flex justify-between items-center; + } + + .search-page-input { + @apply rounded border border-solid border-medusa-border-base; + @apply font-base text-medium p-0.75 w-full bg-medusa-bg-subtle; + @apply text-medusa-fg-base mb-1; + @apply transition-[border] duration-200 ease-ease; + @apply focus:border-medusa-border-base; + @apply focus:outline-none !shadow-none; + } + + .code-header { + @apply py-0.75 px-1 bg-medusa-code-bg-header text-medusa-code-text-subtle; + @apply flex justify-between items-center; + @apply rounded-tl rounded-tr rounded-br-none rounded-bl-none border-b-0; + @apply border border-solid border-medusa-code-border border-b-0; + } + + .no-scrollbar::-webkit-scrollbar { + @apply hidden; + } + + .navbar-action-icon-item { + @apply lg:bg-button-neutral lg:bg-medusa-button-neutral lg:dark:bg-button-neutral-dark; + @apply lg:hover:bg-medusa-button-neutral-hover lg:hover:bg-no-image lg:hover:no-underline; + @apply lg:active:bg-medusa-button-neutral-pressed lg:active:bg-no-image; + @apply lg:focus:bg-medusa-button-neutral-pressed lg:focus:bg-no-image; + @apply lg:lg:border lg:border-solid lg:border-medusa-border-base rounded; + @apply w-2 h-2 flex justify-center items-center cursor-pointer; + } + + .btn-clear { + @apply bg-transparent shadow-none border-0 outline-none cursor-pointer; + } +} + +@layer utilities { + .clip { + clip-path: inset(0); + } +} + +@import url('./_variables.css'); + +@import url('./_docusaurus.css'); +@import url('./components/sidebar.css'); +@import url('./components/toc.css'); +@import url('./components/tooltip.css'); \ No newline at end of file diff --git a/www/apps/docs/src/plugins/docusaurus-plugin-diagram2code-showcase/index.js b/www/apps/docs/src/plugins/docusaurus-plugin-diagram2code-showcase/index.js new file mode 100644 index 0000000..f2de7a0 --- /dev/null +++ b/www/apps/docs/src/plugins/docusaurus-plugin-diagram2code-showcase/index.js @@ -0,0 +1,118 @@ +import { exec } from "child_process" +import { readdir, readFile, writeFile } from "fs/promises" +import path from "path" + +export default async function docusaurusPluginDiagram2codeShowcase( + context, + { directoryPath, outputPath, debug = false } +) { + async function readIfExists(filePath) { + try { + return await readFile(filePath, "utf-8") + } catch (e) { + if (debug) { + console.error( + `[Diagram2Code Showcase Plugin] An error occurred while reading ${filePath}: ${e}` + ) + } + return null + } + } + + async function generateSpecs() { + let specs = {} + let diagramSpecDirectories = [] + + try { + // read files under the provided directory path + diagramSpecDirectories = ( + await readdir(directoryPath, { withFileTypes: true }) + ).filter((dirent) => dirent.isDirectory()) + } catch { + console.error( + `Directory ${directoryPath} doesn't exist. Skipping reading diagrams...` + ) + return + } + + await Promise.all( + diagramSpecDirectories.map(async (dirent) => { + const tempSpecs = {} + const specsPath = path.join(directoryPath, dirent.name) + + const specDirents = ( + await readdir(specsPath, { withFileTypes: true }) + ).filter((specDirent) => specDirent.isDirectory()) + await Promise.all( + specDirents.map(async (specDirent) => { + const specPath = path.join(specsPath, specDirent.name) + // read the diagram and code files + const diagram = await readIfExists( + path.join(specPath, "diagram.mermaid") + ) + const code = + (await readIfExists(path.join(specPath, "code.ts"))) || + (await readIfExists(path.join(specPath, "code.tsx"))) || + (await readIfExists(path.join(specPath, "code.js"))) + + if (!diagram) { + return + } + + tempSpecs[specDirent.name] = { + diagram, + code, + } + }) + ) + + if (Object.keys(tempSpecs).length) { + specs[dirent.name] = tempSpecs + } + }) + ) + + // order steps alphabetically + specs = Object.keys(specs) + .sort() + .reduce((accumulator, key) => { + accumulator[key] = specs[key] + + return accumulator + }, {}) + + // store specs in a JavaScript object that can be consumed + const specOutputFilePath = path.join(outputPath, "specs.ts") + await writeFile( + specOutputFilePath, + `export const specs = ${JSON.stringify(specs, null, "\t")}` + ) + + // execute eslint + exec(`eslint ${specOutputFilePath} --fix`) + + return specOutputFilePath + } + + return { + name: "docusaurus-plugin-diagram2code-showcase", + async loadContent() { + await generateSpecs() + }, + extendCli(cli) { + cli + .command("diagram2code:generate") + .description( + "Generate the spec file used to create diagram-to-code showcase" + ) + .action(async () => { + const specFile = await generateSpecs() + // eslint-disable-next-line no-console + console.log(`Generated diagram2code spec file at ${specFile}`) + }) + }, + getPathsToWatch() { + return [directoryPath] + }, + } +} diff --git a/www/apps/docs/src/providers/DocsProviders/index.tsx b/www/apps/docs/src/providers/DocsProviders/index.tsx new file mode 100644 index 0000000..982e3a3 --- /dev/null +++ b/www/apps/docs/src/providers/DocsProviders/index.tsx @@ -0,0 +1,38 @@ +import { + AnalyticsProvider, + ColorModeProvider, + ModalProvider, + NotificationProvider, +} from "docs-ui" +import React from "react" +import { useThemeConfig } from "@docusaurus/theme-common" +import { ThemeConfig } from "@medusajs/docs" +import SearchProvider from "../Search" +import SkipToContent from "@theme/SkipToContent" + +type DocsProvidersProps = { + children?: React.ReactNode +} + +const DocsProviders = ({ children }: DocsProvidersProps) => { + const { + analytics: { apiKey }, + } = useThemeConfig() as ThemeConfig + + return ( + + + + + + + {children} + + + + + + ) +} + +export default DocsProviders diff --git a/www/apps/docs/src/providers/LearningPath/index.tsx b/www/apps/docs/src/providers/LearningPath/index.tsx new file mode 100644 index 0000000..c2bb2b5 --- /dev/null +++ b/www/apps/docs/src/providers/LearningPath/index.tsx @@ -0,0 +1,256 @@ +import useIsBrowser from "@docusaurus/useIsBrowser" +import { getLearningPath } from "@site/src/utils/learning-paths" +import React, { createContext, useContext, useEffect, useState } from "react" +import { useHistory } from "@docusaurus/router" +import { LearningPathFinishType } from "@site/src/components/LearningPath/Finish" +import { useAnalytics } from "docs-ui" + +export type LearningPathType = { + name: string + label: string + description?: string + steps: LearningPathStepType[] + finish?: LearningPathFinishType + notificationId?: string +} + +export type LearningPathStepType = { + title?: string + description?: string + descriptionJSX?: JSX.Element + path?: string +} + +export type LearningPathContextType = { + path?: LearningPathType + setPath: (value: LearningPathType) => void + currentStep: number + setCurrentStep: (value: number) => void + startPath: (path: LearningPathType) => void + updatePath: (data: Pick) => void + endPath: () => void + nextStep: () => void + hasNextStep: () => boolean + previousStep: () => void + hasPreviousStep: () => boolean + goToStep: (stepIndex: number) => void + isCurrentPath: () => boolean + goToCurrentPath: () => void +} + +type LearningPathProviderProps = { + children?: React.ReactNode +} + +const LearningPathContext = createContext(null) + +const LearningPathProvider: React.FC = ({ + children, +}) => { + const [path, setPath] = useState(null) + const [currentStep, setCurrentStep] = useState(-1) + const isBrowser = useIsBrowser() + const history = useHistory() + const { track } = useAnalytics() + + const startPath = (path: LearningPathType) => { + setPath(path) + setCurrentStep(-1) + if (isBrowser) { + localStorage.setItem( + "learning-path", + JSON.stringify({ + pathName: path.name, + currentStep: -1, + }) + ) + } + + track(`learning_path_${path.name}`, { + url: history.location.pathname, + state: `start`, + }) + } + + useEffect(() => { + if (path && currentStep === -1) { + nextStep() + } + }, [path]) + + const endPath = () => { + const didFinish = currentStep === path.steps.length - 1 + const reachedIndex = currentStep === -1 ? 0 : currentStep + track(`learning_path_${path.name}`, { + url: history.location.pathname, + state: !didFinish ? `closed` : `end`, + reachedStep: + path.steps[reachedIndex]?.title || + path.steps[reachedIndex]?.description || + path.steps[reachedIndex]?.descriptionJSX || + reachedIndex, + }) + setPath(null) + setCurrentStep(-1) + if (isBrowser) { + localStorage.removeItem("learning-path") + } + } + + const hasNextStep = () => currentStep !== path?.steps.length - 1 + + const nextStep = () => { + if (!path || !hasNextStep()) { + return + } + const nextStepIndex = currentStep + 1 + setCurrentStep(nextStepIndex) + const newPath = path.steps[nextStepIndex].path + if (isBrowser) { + localStorage.setItem( + "learning-path", + JSON.stringify({ + pathName: path.name, + currentStep: nextStepIndex, + }) + ) + } + if (history.location.pathname !== newPath) { + history.push(newPath) + } + } + + const hasPreviousStep = () => currentStep > 0 + + const previousStep = () => { + if (!path || !hasPreviousStep()) { + return + } + + const previousStepIndex = currentStep - 1 + setCurrentStep(previousStepIndex) + const newPath = path.steps[previousStepIndex].path + if (isBrowser) { + localStorage.setItem( + "learning-path", + JSON.stringify({ + pathName: path.name, + currentStep: previousStepIndex, + }) + ) + } + if (history.location.pathname !== newPath) { + history.push(newPath) + } + } + + const goToStep = (stepIndex: number) => { + if (!path || stepIndex >= path.steps.length) { + return + } + + setCurrentStep(stepIndex) + const newPath = path.steps[stepIndex].path + if (isBrowser) { + localStorage.setItem( + "learning-path", + JSON.stringify({ + pathName: path.name, + currentStep: stepIndex, + }) + ) + } + if (history.location.pathname !== newPath) { + history.push(newPath) + } + } + + const isCurrentPath = () => { + if (!path || currentStep === -1) { + return false + } + + return history.location.pathname === path.steps[currentStep].path + } + + const goToCurrentPath = () => { + if (!path || currentStep === -1) { + return + } + + history.push(path.steps[currentStep].path) + } + + const updatePath = (data: Pick) => { + setPath({ + ...path, + ...data, + }) + } + + const initPath = () => { + if (isBrowser) { + // give query parameters higher precedence over local storage + const queryPathName = new URLSearchParams(history.location.search).get( + "path" + ) + const queryPath = getLearningPath(queryPathName) + if (queryPath) { + startPath(queryPath) + } else { + const storedPath = localStorage.getItem("learning-path") + if (storedPath) { + const storedPathParsed = JSON.parse(storedPath) + const currentPath = getLearningPath(storedPathParsed?.pathName) + if (currentPath) { + setPath(currentPath) + setCurrentStep(storedPathParsed?.currentStep || 0) + } + } + } + } + } + + useEffect(() => { + if (isBrowser && !path) { + initPath() + } + }, [isBrowser]) + + return ( + + {children} + + ) +} + +export default LearningPathProvider + +export const useLearningPath = () => { + const context = useContext(LearningPathContext) + + if (!context) { + throw new Error( + "useLearningPath must be used within a LearningPathProvider" + ) + } + + return context +} diff --git a/www/apps/docs/src/providers/Search/index.tsx b/www/apps/docs/src/providers/Search/index.tsx new file mode 100644 index 0000000..f36fd81 --- /dev/null +++ b/www/apps/docs/src/providers/Search/index.tsx @@ -0,0 +1,73 @@ +import React, { useEffect, useState } from "react" +import { SearchProvider as UiSearchProvider, checkArraySameElms } from "docs-ui" +import { ThemeConfig } from "@medusajs/docs" +import { useThemeConfig } from "@docusaurus/theme-common" +import { useLocalPathname } from "@docusaurus/theme-common/internal" + +type SearchProviderProps = { + children: React.ReactNode +} + +const SearchProvider = ({ children }: SearchProviderProps) => { + const [defaultFilters, setDefaultFilters] = useState([]) + const { algoliaConfig: algolia } = useThemeConfig() as ThemeConfig + const currentPath = useLocalPathname() + + useEffect(() => { + let resultFilters = [] + algolia.defaultFiltersByPath.some((filtersByPath) => { + if (currentPath.startsWith(filtersByPath.path)) { + resultFilters = filtersByPath.filters + } + }) + if (!resultFilters.length && algolia.defaultFilters) { + resultFilters = algolia.defaultFilters + } + if (!checkArraySameElms(defaultFilters, resultFilters)) { + setDefaultFilters(resultFilters) + } + }, [currentPath]) + + return ( + + {children} + + ) +} + +export default SearchProvider diff --git a/www/apps/docs/src/providers/Sidebar/index.tsx b/www/apps/docs/src/providers/Sidebar/index.tsx new file mode 100644 index 0000000..b3e2e7d --- /dev/null +++ b/www/apps/docs/src/providers/Sidebar/index.tsx @@ -0,0 +1,102 @@ +import React, { + createContext, + useCallback, + useContext, + useEffect, + useState, +} from "react" +import { prefersReducedMotion } from "@docusaurus/theme-common" + +type SidebarContextType = { + hasSidebar: boolean + hiddenSidebar: boolean + setHiddenSidebar: (value: boolean) => void + hiddenSidebarContainer: boolean + setHiddenSidebarContainer: (value: boolean) => void + floatingSidebar: boolean + setFloatingSidebar: (value: boolean) => void + onCollapse: () => void +} + +const SidebarContext = createContext(null) + +type SidebarProviderProps = { + sidebarName?: string + children?: React.ReactNode +} + +const SidebarProvider: React.FC = ({ + sidebarName, + children, +}) => { + const [hiddenSidebar, setHiddenSidebar] = useState(false) + const [hiddenSidebarContainer, setHiddenSidebarContainer] = useState(false) + const [floatingSidebar, setFloatingSidebar] = useState(false) + + const toggleSidebar = useCallback(() => { + if (hiddenSidebar) { + setHiddenSidebar(false) + } + // onTransitionEnd won't fire when sidebar animation is disabled + // fixes https://github.com/facebook/docusaurus/issues/8918 + if (!hiddenSidebar && prefersReducedMotion()) { + setHiddenSidebar(true) + } + setHiddenSidebarContainer((value) => !value) + }, [setHiddenSidebarContainer, hiddenSidebar]) + + useEffect(() => { + function isEditingContent(event: KeyboardEvent) { + const element = event.target as HTMLElement + const tagName = element.tagName + return ( + element.isContentEditable || + tagName === "INPUT" || + tagName === "SELECT" || + tagName === "TEXTAREA" + ) + } + + function sidebarShortcut(e: KeyboardEvent) { + if ( + (e.metaKey || e.ctrlKey) && + e.key.toLowerCase() === "i" && + !isEditingContent(e) + ) { + e.preventDefault() + toggleSidebar() + } + } + + window.addEventListener("keydown", sidebarShortcut) + + return () => { + window.removeEventListener("keydown", sidebarShortcut) + } + }) + + return ( + + {children} + + ) +} + +export default SidebarProvider + +export const useSidebar = () => { + const context = useContext(SidebarContext) + + return context +} diff --git a/www/apps/docs/src/theme/Admonition/Icon/Danger.tsx b/www/apps/docs/src/theme/Admonition/Icon/Danger.tsx new file mode 100644 index 0000000..5520694 --- /dev/null +++ b/www/apps/docs/src/theme/Admonition/Icon/Danger.tsx @@ -0,0 +1,19 @@ +import React from "react" +import type { IconProps } from "@medusajs/icons/dist/types" +import { ExclamationCircleSolid } from "@medusajs/icons" +import clsx from "clsx" + +export default function AdmonitionIconDanger({ + className, + ...props +}: IconProps): JSX.Element { + return ( + + ) +} diff --git a/www/apps/docs/src/theme/Admonition/Icon/Info.tsx b/www/apps/docs/src/theme/Admonition/Icon/Info.tsx new file mode 100644 index 0000000..3616e96 --- /dev/null +++ b/www/apps/docs/src/theme/Admonition/Icon/Info.tsx @@ -0,0 +1,7 @@ +import React from "react" +import AdmonitionIconNote from "./Note" +import type { IconProps } from "@medusajs/icons/dist/types" + +export default function AdmonitionIconInfo(props: IconProps): JSX.Element { + return +} diff --git a/www/apps/docs/src/theme/Admonition/Icon/Note.tsx b/www/apps/docs/src/theme/Admonition/Icon/Note.tsx new file mode 100644 index 0000000..7075c76 --- /dev/null +++ b/www/apps/docs/src/theme/Admonition/Icon/Note.tsx @@ -0,0 +1,19 @@ +import React from "react" +import { InformationCircleSolid } from "@medusajs/icons" +import type { IconProps } from "@medusajs/icons/dist/types" +import clsx from "clsx" + +export default function AdmonitionIconNote({ + className, + ...props +}: IconProps): JSX.Element { + return ( + + ) +} diff --git a/www/apps/docs/src/theme/Admonition/Icon/Tip.tsx b/www/apps/docs/src/theme/Admonition/Icon/Tip.tsx new file mode 100644 index 0000000..62e9e96 --- /dev/null +++ b/www/apps/docs/src/theme/Admonition/Icon/Tip.tsx @@ -0,0 +1,7 @@ +import type { IconProps } from "@medusajs/icons/dist/types" +import React from "react" +import AdmonitionIconNote from "./Note" + +export default function AdmonitionIconTip(props: IconProps): JSX.Element { + return +} diff --git a/www/apps/docs/src/theme/Admonition/Icon/Warning.tsx b/www/apps/docs/src/theme/Admonition/Icon/Warning.tsx new file mode 100644 index 0000000..68596b7 --- /dev/null +++ b/www/apps/docs/src/theme/Admonition/Icon/Warning.tsx @@ -0,0 +1,7 @@ +import React from "react" +import AdmonitionIconDanger from "./Danger" +import type { IconProps } from "@medusajs/icons/dist/types" + +export default function AdmonitionIconCaution(props: IconProps): JSX.Element { + return +} diff --git a/www/apps/docs/src/theme/Admonition/Layout/index.tsx b/www/apps/docs/src/theme/Admonition/Layout/index.tsx new file mode 100644 index 0000000..2a23771 --- /dev/null +++ b/www/apps/docs/src/theme/Admonition/Layout/index.tsx @@ -0,0 +1,86 @@ +import React, { type ReactNode } from "react" +import clsx from "clsx" + +import type { Props } from "@theme/Admonition/Layout" + +function AdmonitionContainer({ + className, + children, + type, +}: Pick & { children: ReactNode }) { + return ( +
+
{children}
+
+ ) +} + +function AdmonitionHeading({ icon }: Pick) { + return {icon} +} + +function AdmonitionContent({ + children, + title, + type, +}: Pick) { + return children ? ( +
*:last-child]:mb-0", + "[&>p>code]:px-0.5 [&>p>code]:text-code-label" + )} + > + {title && ( + + {transformAdmonitionTitle(title)} + + )} + {children} +
+ ) : null +} + +export default function AdmonitionLayout(props: Props): JSX.Element { + const { type, icon, children, className, title } = props + return ( + + + + {children} + + + ) +} + +function transformAdmonitionTitle(title: T): T | string { + if (typeof title !== "string") { + return title + } + switch (title) { + case "note": + case "tip": + case "danger": + case "warning": + case "info": + case "caution": + return title.charAt(0).toUpperCase + title.substring(1) + default: + return title + } +} diff --git a/www/apps/docs/src/theme/Admonition/Type/Caution.tsx b/www/apps/docs/src/theme/Admonition/Type/Caution.tsx new file mode 100644 index 0000000..0addec8 --- /dev/null +++ b/www/apps/docs/src/theme/Admonition/Type/Caution.tsx @@ -0,0 +1,34 @@ +import React from "react" +import clsx from "clsx" +import Translate from "@docusaurus/Translate" +import type { Props } from "@theme/Admonition/Type/Caution" +import AdmonitionLayout from "@theme/Admonition/Layout" +import IconWarning from "@theme/Admonition/Icon/Warning" + +const infimaClassName = "alert alert--warning" + +const defaultProps = { + icon: , + title: ( + + Caution + + ), +} + +// TODO remove before v4: Caution replaced by Warning +// see https://github.com/facebook/docusaurus/issues/7558 +export default function AdmonitionTypeCaution(props: Props): JSX.Element { + return ( + + {props.children} + + ) +} diff --git a/www/apps/docs/src/theme/Admonition/Type/Danger.tsx b/www/apps/docs/src/theme/Admonition/Type/Danger.tsx new file mode 100644 index 0000000..7a93418 --- /dev/null +++ b/www/apps/docs/src/theme/Admonition/Type/Danger.tsx @@ -0,0 +1,32 @@ +import React from "react" +import clsx from "clsx" +import Translate from "@docusaurus/Translate" +import type { Props } from "@theme/Admonition/Type/Danger" +import AdmonitionLayout from "@theme/Admonition/Layout" +import IconDanger from "@theme/Admonition/Icon/Danger" + +const infimaClassName = "alert alert--danger" + +const defaultProps = { + icon: , + title: ( + + Danger + + ), +} + +export default function AdmonitionTypeDanger(props: Props): JSX.Element { + return ( + + {props.children} + + ) +} diff --git a/www/apps/docs/src/theme/Admonition/Type/Info.tsx b/www/apps/docs/src/theme/Admonition/Type/Info.tsx new file mode 100644 index 0000000..1823fc7 --- /dev/null +++ b/www/apps/docs/src/theme/Admonition/Type/Info.tsx @@ -0,0 +1,32 @@ +import React from "react" +import clsx from "clsx" +import Translate from "@docusaurus/Translate" +import type { Props } from "@theme/Admonition/Type/Info" +import AdmonitionLayout from "@theme/Admonition/Layout" +import IconInfo from "@theme/Admonition/Icon/Info" + +const infimaClassName = "alert alert--info" + +const defaultProps = { + icon: , + title: ( + + Info + + ), +} + +export default function AdmonitionTypeInfo(props: Props): JSX.Element { + return ( + + {props.children} + + ) +} diff --git a/www/apps/docs/src/theme/Admonition/Type/Note.tsx b/www/apps/docs/src/theme/Admonition/Type/Note.tsx new file mode 100644 index 0000000..bca2b2f --- /dev/null +++ b/www/apps/docs/src/theme/Admonition/Type/Note.tsx @@ -0,0 +1,32 @@ +import React from "react" +import clsx from "clsx" +import Translate from "@docusaurus/Translate" +import type { Props } from "@theme/Admonition/Type/Note" +import AdmonitionLayout from "@theme/Admonition/Layout" +import IconNote from "@theme/Admonition/Icon/Note" + +const infimaClassName = "alert alert--secondary" + +const defaultProps = { + icon: , + title: ( + + Note + + ), +} + +export default function AdmonitionTypeNote(props: Props): JSX.Element { + return ( + + {props.children} + + ) +} diff --git a/www/apps/docs/src/theme/Admonition/Type/Tip.tsx b/www/apps/docs/src/theme/Admonition/Type/Tip.tsx new file mode 100644 index 0000000..66fad7c --- /dev/null +++ b/www/apps/docs/src/theme/Admonition/Type/Tip.tsx @@ -0,0 +1,32 @@ +import React from "react" +import clsx from "clsx" +import Translate from "@docusaurus/Translate" +import type { Props } from "@theme/Admonition/Type/Tip" +import AdmonitionLayout from "@theme/Admonition/Layout" +import IconTip from "@theme/Admonition/Icon/Tip" + +const infimaClassName = "alert alert--success" + +const defaultProps = { + icon: , + title: ( + + Tip + + ), +} + +export default function AdmonitionTypeTip(props: Props): JSX.Element { + return ( + + {props.children} + + ) +} diff --git a/www/apps/docs/src/theme/Admonition/Type/Warning.tsx b/www/apps/docs/src/theme/Admonition/Type/Warning.tsx new file mode 100644 index 0000000..d5445b0 --- /dev/null +++ b/www/apps/docs/src/theme/Admonition/Type/Warning.tsx @@ -0,0 +1,32 @@ +import React from "react" +import clsx from "clsx" +import Translate from "@docusaurus/Translate" +import type { Props } from "@theme/Admonition/Type/Warning" +import AdmonitionLayout from "@theme/Admonition/Layout" +import IconWarning from "@theme/Admonition/Icon/Warning" + +const infimaClassName = "alert alert--warning" + +const defaultProps = { + icon: , + title: ( + + Warning + + ), +} + +export default function AdmonitionTypeWarning(props: Props): JSX.Element { + return ( + + {props.children} + + ) +} diff --git a/www/apps/docs/src/theme/AnnouncementBar/CloseButton/index.tsx b/www/apps/docs/src/theme/AnnouncementBar/CloseButton/index.tsx new file mode 100644 index 0000000..827a7e0 --- /dev/null +++ b/www/apps/docs/src/theme/AnnouncementBar/CloseButton/index.tsx @@ -0,0 +1,33 @@ +import React from "react" +import clsx from "clsx" +import { translate } from "@docusaurus/Translate" +import IconClose from "@theme/Icon/Close" +import type { Props } from "@theme/AnnouncementBar/CloseButton" + +export default function AnnouncementBarCloseButton( + props: Props +): JSX.Element | null { + return ( + + ) +} diff --git a/www/apps/docs/src/theme/AnnouncementBar/Content/index.tsx b/www/apps/docs/src/theme/AnnouncementBar/Content/index.tsx new file mode 100644 index 0000000..698030f --- /dev/null +++ b/www/apps/docs/src/theme/AnnouncementBar/Content/index.tsx @@ -0,0 +1,29 @@ +import React from "react" +import clsx from "clsx" +import { useThemeConfig } from "@docusaurus/theme-common" +import type { Props } from "@theme/AnnouncementBar/Content" + +export default function AnnouncementBarContent( + props: Props +): JSX.Element | null { + const { announcementBar } = useThemeConfig() + const { content } = announcementBar! + return ( +
+
+ Read more +
+ ) +} diff --git a/www/apps/docs/src/theme/AnnouncementBar/index.tsx b/www/apps/docs/src/theme/AnnouncementBar/index.tsx new file mode 100644 index 0000000..fbc4334 --- /dev/null +++ b/www/apps/docs/src/theme/AnnouncementBar/index.tsx @@ -0,0 +1,52 @@ +import React from "react" +import { useThemeConfig } from "@docusaurus/theme-common" +import { useAnnouncementBar } from "@docusaurus/theme-common/internal" +import AnnouncementBarCloseButton from "@theme/AnnouncementBar/CloseButton" +import AnnouncementBarContent from "@theme/AnnouncementBar/Content" + +import clsx from "clsx" +import { Bordered } from "docs-ui" +import { BellAlertSolid } from "@medusajs/icons" +import Link from "@docusaurus/Link" + +export default function AnnouncementBar(): JSX.Element | null { + const { announcementBar } = useThemeConfig() + const { isActive, close } = useAnnouncementBar() + if (!isActive) { + return null + } + const { isCloseable, id } = announcementBar! + return ( +
+ +
+ +
+
+ + {isCloseable && ( + + )} + +
+ ) +} diff --git a/www/apps/docs/src/theme/BackToTopButton/index.tsx b/www/apps/docs/src/theme/BackToTopButton/index.tsx new file mode 100644 index 0000000..97fca7c --- /dev/null +++ b/www/apps/docs/src/theme/BackToTopButton/index.tsx @@ -0,0 +1,33 @@ +import React from "react" +import clsx from "clsx" +import { translate } from "@docusaurus/Translate" +import { useBackToTopButton } from "@docusaurus/theme-common/internal" +import { Button, useNotifications } from "docs-ui" +import { ArrowUpMini } from "@medusajs/icons" + +export default function BackToTopButton(): JSX.Element { + const { shown, scrollToTop } = useBackToTopButton({ threshold: 300 }) + const { notifications } = useNotifications() + + return ( + + ) +} diff --git a/www/apps/docs/src/theme/CodeBlock/Content/String.tsx b/www/apps/docs/src/theme/CodeBlock/Content/String.tsx new file mode 100644 index 0000000..b22b3d0 --- /dev/null +++ b/www/apps/docs/src/theme/CodeBlock/Content/String.tsx @@ -0,0 +1,153 @@ +import React from "react" +import clsx from "clsx" +import { useThemeConfig, usePrismTheme } from "@docusaurus/theme-common" +import { + parseCodeBlockTitle, + parseLanguage, + parseLines, + containsLineNumbers, + useCodeWordWrap, +} from "@docusaurus/theme-common/internal" +import { Highlight, type Language } from "prism-react-renderer" +import Line from "@theme/CodeBlock/Line" +import Container from "@theme/CodeBlock/Container" +import type { Props } from "@theme/CodeBlock/Content/String" +import { Tooltip, CopyButton } from "docs-ui" +import useIsBrowser from "@docusaurus/useIsBrowser" +import { ThemeConfig } from "@medusajs/docs" +import { ExclamationCircleSolid, SquareTwoStackSolid } from "@medusajs/icons" +import Link from "@docusaurus/Link" + +// Prism languages are always lowercase +// We want to fail-safe and allow both "php" and "PHP" +// See https://github.com/facebook/docusaurus/issues/9012 +function normalizeLanguage(language: string | undefined): string | undefined { + return language?.toLowerCase() +} + +export default function CodeBlockString({ + children, + className: blockClassName = "", + metastring, + title: titleProp, + showLineNumbers: showLineNumbersProp, + language: languageProp, + noReport = false, + noCopy = false, +}: Props): JSX.Element { + const { + prism: { defaultLanguage, magicComments }, + reportCodeLinkPrefix = "", + } = useThemeConfig() as ThemeConfig + const language = normalizeLanguage( + languageProp ?? parseLanguage(blockClassName) ?? defaultLanguage + ) + + const prismTheme = usePrismTheme() + const wordWrap = useCodeWordWrap() + const isBrowser = useIsBrowser() + + // We still parse the metastring in case we want to support more syntax in the + // future. Note that MDX doesn't strip quotes when parsing metastring: + // "title=\"xyz\"" => title: "\"xyz\"" + const title = parseCodeBlockTitle(metastring) || titleProp + + const { lineClassNames, code } = parseLines(children, { + metastring, + language, + magicComments, + }) + const showLineNumbers = showLineNumbersProp ?? containsLineNumbers(metastring) + + return ( + + {title &&
{title}
} +
+ + {({ className, tokens, getLineProps, getTokenProps }) => ( + <> +
+                 1 &&
+                      "table p-1 code-block-numbering",
+                    title && "p-1",
+                    !title && tokens.length > 1 && "p-1",
+                    !title && tokens.length === 1 && "py-0.5 pr-0.5 pl-1"
+                  )}
+                >
+                  {tokens.map((line, i) => (
+                     1}
+                    />
+                  ))}
+                
+              
+
1 && "top-1" + )} + > + {!noReport && ( + + + + + + )} + {!noCopy && ( + + + + )} +
+ + )} +
+
+
+ ) +} diff --git a/www/apps/docs/src/theme/CodeBlock/index.tsx b/www/apps/docs/src/theme/CodeBlock/index.tsx new file mode 100644 index 0000000..8297831 --- /dev/null +++ b/www/apps/docs/src/theme/CodeBlock/index.tsx @@ -0,0 +1,101 @@ +import React, { isValidElement, type ReactNode } from "react" +import useIsBrowser from "@docusaurus/useIsBrowser" +import ElementContent from "@theme/CodeBlock/Content/Element" +import StringContent from "@theme/CodeBlock/Content/String" +import type { Props } from "@theme/CodeBlock" +import clsx from "clsx" +import { Badge, BadgeVariant } from "docs-ui" + +/** + * Best attempt to make the children a plain string so it is copyable. If there + * are react elements, we will not be able to copy the content, and it will + * return `children` as-is; otherwise, it concatenates the string children + * together. + */ +function maybeStringifyChildren(children: ReactNode): ReactNode { + if (React.Children.toArray(children).some((el) => isValidElement(el))) { + return children + } + // The children is now guaranteed to be one/more plain strings + return Array.isArray(children) ? children.join("") : (children as string) +} + +export default function CodeBlock({ + children: rawChildren, + noReport = false, + noCopy = false, + ...props +}: Props): JSX.Element { + // The Prism theme on SSR is always the default theme but the site theme can + // be in a different mode. React hydration doesn't update DOM styles that come + // from SSR. Hence force a re-render after mounting to apply the current + // relevant styles. + const isBrowser = useIsBrowser() + const children = maybeStringifyChildren(rawChildren) + const CodeBlockComp = + typeof children === "string" ? StringContent : ElementContent + + const metastringTitleRegex = /title="?([^"]*)"?/ + const metastringBadgeLabelRegex = /badgeLabel="?([^"]*)"?/ + const metastringBadgeColorRegex = /badgeColor="?([^"]*)"?/ + + let title = props.title + delete props.title + + function extractFromMetastring(regex: RegExp): string { + if (!props.metastring) { + return "" + } + + let value = "" + + const matched = props.metastring.match(regex) + if (matched?.length) { + value = matched[1].replace(/^"/, "").replace(/"$/, "") + props.metastring = props.metastring.replace(regex, "") + } + + return value + } + + if (!title) { + title = extractFromMetastring(metastringTitleRegex) + } + + const badge = { + label: extractFromMetastring(metastringBadgeLabelRegex), + color: extractFromMetastring(metastringBadgeColorRegex), + } + + return ( +
+ {(title || badge.label) && ( +
+ {title} + {badge.label && ( + + {badge.label} + + )} +
+ )} + + {children as string} + +
+ ) +} diff --git a/www/apps/docs/src/theme/Details/index.tsx b/www/apps/docs/src/theme/Details/index.tsx new file mode 100644 index 0000000..fe3cc50 --- /dev/null +++ b/www/apps/docs/src/theme/Details/index.tsx @@ -0,0 +1,26 @@ +import React from "react" +import { + Details as UiDetails, + type DetailsProps as UiDetailsProps, +} from "docs-ui" + +export type DetailsProps = { + summary: React.ReactNode + children?: React.ReactNode +} & UiDetailsProps + +export default function Details({ + summary, + children, + ...props +}: DetailsProps): JSX.Element { + return ( + + {children} + + ) +} diff --git a/www/apps/docs/src/theme/DocCard/index.tsx b/www/apps/docs/src/theme/DocCard/index.tsx new file mode 100644 index 0000000..4788a0f --- /dev/null +++ b/www/apps/docs/src/theme/DocCard/index.tsx @@ -0,0 +1,245 @@ +import React, { type ReactNode } from "react" +import clsx from "clsx" +import Link from "@docusaurus/Link" +import { + findFirstSidebarItemLink, + useDocById, +} from "@docusaurus/theme-common/internal" +import isInternalUrl from "@docusaurus/isInternalUrl" +import { translate } from "@docusaurus/Translate" +import { + ModifiedDocCard, + ModifiedDocCardItemCategory, + ModifiedDocCardItemLink, + ModifiedSidebarItem, +} from "@medusajs/docs" +import { Badge } from "docs-ui" +import Icons from "../Icon" +import BorderedIcon from "../../components/BorderedIcon" + +type ModifiedProps = { + item: ModifiedDocCard +} + +function CardContainer({ + href, + children, + className, +}: { + href: string + children: ReactNode + className?: string +}): JSX.Element { + return ( +
+ + {children} + +
+ ) +} + +function CardLayout({ + href, + icon, + title, + description, + html, + containerClassName, + isSoon = false, + badge, +}: ModifiedDocCard): JSX.Element { + const isHighlighted = containerClassName?.includes("card-highlighted") + return ( + +
+ {icon} + {isSoon && Guide coming soon} + {badge && } +
+
*:last-child]:mb-0")}> + + {title} + + {description && ( +

+ {description} +

+ )} + {html && ( +

+ )} +
+
+ ) +} + +function getCardIcon(item: ModifiedSidebarItem): JSX.Element { + if (item.customProps?.themedImage) { + return ( + + ) + } else if (item.customProps?.image) { + return ( + + ) + } else if (item.customProps?.icon) { + return ( + + ) + } else if ( + item.customProps?.iconName && + Object.hasOwn(Icons, item.customProps?.iconName) + ) { + return ( + + ) + } else { + return ( +
+ {isInternalUrl( + "href" in item ? item.href : "value" in item ? item.value : "#" + ) + ? "📄️" + : "🔗"} +
+ ) + } +} + +function CardCategory({ + item, +}: { + item: ModifiedDocCardItemCategory +}): JSX.Element | null { + const href = findFirstSidebarItemLink(item) + const icon = getCardIcon(item) + + // Unexpected: categories that don't have a link have been filtered upfront + if (!href) { + return null + } + + return ( + + ) +} + +function CardLink({ item }: { item: ModifiedDocCardItemLink }): JSX.Element { + const icon = getCardIcon(item) + const doc = useDocById(item.docId ?? undefined) + return ( + + ) +} + +export default function DocCard({ item }: ModifiedProps): JSX.Element { + switch (item.type) { + case "link": + return + case "category": + return + default: + throw new Error(`unknown item type ${JSON.stringify(item)}`) + } +} diff --git a/www/apps/docs/src/theme/DocCardList/index.tsx b/www/apps/docs/src/theme/DocCardList/index.tsx new file mode 100644 index 0000000..368b131 --- /dev/null +++ b/www/apps/docs/src/theme/DocCardList/index.tsx @@ -0,0 +1,39 @@ +import React from "react" +import clsx from "clsx" +import { + useCurrentSidebarCategory, + filterDocCardListItems, +} from "@docusaurus/theme-common" +import DocCard from "@theme/DocCard" +import type { Props } from "@theme/DocCardList" + +type ModifiedProps = { + colSize?: string +} & Props + +function DocCardListForCurrentSidebarCategory({ + className, + ...rest +}: ModifiedProps) { + const category = useCurrentSidebarCategory() + return +} + +export default function DocCardList(props: ModifiedProps): JSX.Element { + const { items, className } = props + if (!items) { + return + } + const filteredItems = filterDocCardListItems(items).filter( + (item) => !item.customProps?.exclude_from_doclist + ) + return ( +
+ {filteredItems.map((item, index) => ( + + ))} +
+ ) +} diff --git a/www/apps/docs/src/theme/DocItem/Content/index.tsx b/www/apps/docs/src/theme/DocItem/Content/index.tsx new file mode 100644 index 0000000..d73d5f2 --- /dev/null +++ b/www/apps/docs/src/theme/DocItem/Content/index.tsx @@ -0,0 +1,66 @@ +import React from "react" +import clsx from "clsx" +import { ThemeClassNames } from "@docusaurus/theme-common" +import { useDoc } from "@docusaurus/theme-common/internal" +import Heading from "@theme/Heading" +import MDXContent from "@theme/MDXContent" +import type { Props } from "@theme/DocItem/Content" +import { DocContextValue } from "@medusajs/docs" +import { Badge, BadgeVariant } from "docs-ui" + +/** + Title can be declared inside md content or declared through + front matter and added manually. To make both cases consistent, + the added title is added under the same div.markdown block + See https://github.com/facebook/docusaurus/pull/4882#issuecomment-853021120 + + We render a "synthetic title" if: + - user doesn't ask to hide it with front matter + - the markdown content does not already contain a top-level h1 heading +*/ +function useSyntheticTitle(): string | null { + const { metadata, frontMatter, contentTitle } = useDoc() + const shouldRender = + !frontMatter.hide_title && typeof contentTitle === "undefined" + if (!shouldRender) { + return null + } + return metadata.title +} + +export default function DocItemContent({ children }: Props): JSX.Element { + const { + frontMatter: { badge }, + } = useDoc() as DocContextValue + const syntheticTitle = useSyntheticTitle() + return ( +
+ {syntheticTitle && ( +
+ + {syntheticTitle} + {badge && ( + + {badge.text} + + )} + + {badge && ( + + {badge.text} + + )} +
+ )} + {children} +
+ ) +} diff --git a/www/apps/docs/src/theme/DocItem/Footer/index.tsx b/www/apps/docs/src/theme/DocItem/Footer/index.tsx new file mode 100644 index 0000000..b8dc30d --- /dev/null +++ b/www/apps/docs/src/theme/DocItem/Footer/index.tsx @@ -0,0 +1,28 @@ +import React from "react" +import Footer from "@theme-original/DocItem/Footer" +import type FooterType from "@theme/DocItem/Footer" +import type { WrapperProps } from "@docusaurus/types" +import { useDoc } from "@docusaurus/theme-common/internal" +import { useThemeConfig } from "@docusaurus/theme-common" +import type { ThemeConfig } from "@medusajs/docs" +import Feedback from "../../../components/Feedback" + +type Props = WrapperProps + +export default function FooterWrapper(props: Props): JSX.Element { + const { metadata } = useDoc() + const { footerFeedback = { event: "" } } = useThemeConfig() as ThemeConfig + return ( + <> + {!metadata.frontMatter?.hide_footer && ( +
+ +
+
+ )} + + ) +} diff --git a/www/apps/docs/src/theme/DocItem/Layout/index.tsx b/www/apps/docs/src/theme/DocItem/Layout/index.tsx new file mode 100644 index 0000000..843581c --- /dev/null +++ b/www/apps/docs/src/theme/DocItem/Layout/index.tsx @@ -0,0 +1,77 @@ +import React from "react" +import clsx from "clsx" +import { useWindowSize } from "@docusaurus/theme-common" +import { useDoc } from "@docusaurus/theme-common/internal" +import DocItemPaginator from "@theme/DocItem/Paginator" +import DocVersionBanner from "@theme/DocVersionBanner" +import DocVersionBadge from "@theme/DocVersionBadge" +import DocItemFooter from "@theme/DocItem/Footer" +import DocItemTOCMobile from "@theme/DocItem/TOC/Mobile" +import DocItemTOCDesktop from "@theme/DocItem/TOC/Desktop" +import DocItemContent from "@theme/DocItem/Content" +import DocBreadcrumbs from "@theme/DocBreadcrumbs" +import Unlisted from "@theme/Unlisted" +import type { Props } from "@theme/DocItem/Layout" +import { useSidebar } from "../../../providers/Sidebar" +import Footer from "@theme/Footer" + +/** + * Decide if the toc should be rendered, on mobile or desktop viewports + */ +function useDocTOC() { + const { frontMatter, toc } = useDoc() + const windowSize = useWindowSize() + + const hidden = frontMatter.hide_table_of_contents + const canRender = !hidden && toc.length > 0 + + const mobile = canRender ? : undefined + + const desktop = + canRender && (windowSize === "desktop" || windowSize === "ssr") ? ( + + ) : undefined + + return { + hidden, + mobile, + desktop, + } +} + +export default function DocItemLayout({ children }: Props): JSX.Element { + const docTOC = useDocTOC() + const { + metadata: { unlisted }, + } = useDoc() + const sidebarContext = useSidebar() + return ( +
+
+ {unlisted && } + +
+
*:first-child]:mt-0")}> + + + {docTOC.mobile} + {children} + +
+ +
+
+
+ {docTOC.desktop && ( +
{docTOC.desktop}
+ )} +
+ ) +} diff --git a/www/apps/docs/src/theme/DocRoot/Layout/Main/index.tsx b/www/apps/docs/src/theme/DocRoot/Layout/Main/index.tsx new file mode 100644 index 0000000..4f65a4f --- /dev/null +++ b/www/apps/docs/src/theme/DocRoot/Layout/Main/index.tsx @@ -0,0 +1,32 @@ +import React from "react" +import clsx from "clsx" +import { useDocsSidebar } from "@docusaurus/theme-common/internal" +import type { Props } from "@theme/DocRoot/Layout/Main" + +import { useSidebar } from "../../../../providers/Sidebar" + +export default function DocRootLayoutMain({ children }: Props): JSX.Element { + const sidebar = useDocsSidebar() + const sidebarContext = useSidebar() + return ( +
+
+ {children} +
+
+ ) +} diff --git a/www/apps/docs/src/theme/DocRoot/Layout/Sidebar/index.tsx b/www/apps/docs/src/theme/DocRoot/Layout/Sidebar/index.tsx new file mode 100644 index 0000000..6ef6e5d --- /dev/null +++ b/www/apps/docs/src/theme/DocRoot/Layout/Sidebar/index.tsx @@ -0,0 +1,105 @@ +import React, { type ReactNode, useRef } from "react" +import clsx from "clsx" +import { ThemeClassNames } from "@docusaurus/theme-common" +import { useDocsSidebar } from "@docusaurus/theme-common/internal" +import { useLocation } from "@docusaurus/router" +import DocSidebar from "@theme/DocSidebar" +import type { Props } from "@theme/DocRoot/Layout/Sidebar" +import { useSidebar } from "../../../../providers/Sidebar" +import { CSSTransition, SwitchTransition } from "react-transition-group" + +// Reset sidebar state when sidebar changes +// Use React key to unmount/remount the children +// See https://github.com/facebook/docusaurus/issues/3414 +function ResetOnSidebarChange({ children }: { children: ReactNode }) { + const sidebar = useDocsSidebar() + return ( + + {children} + + ) +} + +export default function DocRootLayoutSidebar({ + sidebar, + hiddenSidebarContainer, +}: Props): JSX.Element { + const { pathname } = useLocation() + const sidebarContext = useSidebar() + const { name } = useDocsSidebar() + const sidebarRef = useRef(null) + + return ( + + ) +} diff --git a/www/apps/docs/src/theme/DocRoot/Layout/index.tsx b/www/apps/docs/src/theme/DocRoot/Layout/index.tsx new file mode 100644 index 0000000..7771392 --- /dev/null +++ b/www/apps/docs/src/theme/DocRoot/Layout/index.tsx @@ -0,0 +1,35 @@ +import React from "react" +import { useDocsSidebar } from "@docusaurus/theme-common/internal" +import BackToTopButton from "@theme/BackToTopButton" +import DocRootLayoutSidebar from "@theme/DocRoot/Layout/Sidebar" +import DocRootLayoutMain from "@theme/DocRoot/Layout/Main" +import type { Props } from "@theme/DocRoot/Layout" + +import { useSidebar } from "../../../providers/Sidebar" +import clsx from "clsx" + +export default function DocRootLayout({ children }: Props): JSX.Element { + const sidebar = useDocsSidebar() + const sidebarContext = useSidebar() + return ( +
+ +
+ {sidebar && ( + + )} + + {children} + +
+
+ ) +} diff --git a/www/apps/docs/src/theme/DocRoot/index.tsx b/www/apps/docs/src/theme/DocRoot/index.tsx new file mode 100644 index 0000000..e5cc96c --- /dev/null +++ b/www/apps/docs/src/theme/DocRoot/index.tsx @@ -0,0 +1,33 @@ +import React from "react" +import clsx from "clsx" +import { + HtmlClassNameProvider, + ThemeClassNames, +} from "@docusaurus/theme-common" +import { + DocsSidebarProvider, + useDocRootMetadata, +} from "@docusaurus/theme-common/internal" +import DocRootLayout from "@theme/DocRoot/Layout" +import NotFoundContent from "@theme/NotFound/Content" +import type { Props } from "@theme/DocRoot" +import SidebarProvider from "../../providers/Sidebar" + +export default function DocRoot(props: Props): JSX.Element { + const currentDocRouteMetadata = useDocRootMetadata(props) + if (!currentDocRouteMetadata) { + // We only render the not found content to avoid a double layout + // see https://github.com/facebook/docusaurus/pull/7966#pullrequestreview-1077276692 + return + } + const { docElement, sidebarName, sidebarItems } = currentDocRouteMetadata + return ( + + + + {docElement} + + + + ) +} diff --git a/www/apps/docs/src/theme/DocSidebar/Desktop/index.tsx b/www/apps/docs/src/theme/DocSidebar/Desktop/index.tsx new file mode 100644 index 0000000..e573958 --- /dev/null +++ b/www/apps/docs/src/theme/DocSidebar/Desktop/index.tsx @@ -0,0 +1,95 @@ +import React, { useEffect, useRef } from "react" +import clsx from "clsx" +import { useThemeConfig } from "@docusaurus/theme-common" +import AnnouncementBar from "@theme/AnnouncementBar" +import Content from "@theme/DocSidebar/Desktop/Content" +import type { Props } from "@theme/DocSidebar/Desktop" +import useIsBrowser from "@docusaurus/useIsBrowser" +import { useLocation } from "@docusaurus/router" + +function DocSidebarDesktop({ path, sidebar }: Props) { + const { + navbar: { hideOnScroll }, + } = useThemeConfig() + const isBrowser = useIsBrowser() + const sidebarRef = useRef(null) + const location = useLocation() + + useEffect(() => { + function handleScroll() { + if (!sidebarRef.current?.classList.contains("scrolling")) { + sidebarRef.current?.classList.add("scrolling") + const intervalId = setInterval(() => { + if (!sidebarRef.current?.matches(":hover")) { + sidebarRef.current?.classList.remove("scrolling") + clearInterval(intervalId) + } + }, 300) + } + } + + if (isBrowser && sidebarRef.current) { + const navElement = sidebarRef.current.querySelector(".main-sidebar") + navElement.addEventListener("scroll", handleScroll) + + return () => { + navElement?.removeEventListener("scroll", handleScroll) + } + } + }, [isBrowser, sidebarRef.current]) + + useEffect(() => { + const navElement = sidebarRef.current.querySelector(".main-sidebar") + const navElementBoundingRect = navElement.getBoundingClientRect() + + // logic to scroll to current active item + const activeItem = document.querySelector( + ".sidebar-desktop [aria-current=page]" + ) + + if (!activeItem) { + return + } + + const activeItemBoundingReact = activeItem.getBoundingClientRect() + // the extra 160 is due to the sticky elements in the sidebar + const isActiveItemVisible = + activeItemBoundingReact.top >= 0 && + activeItemBoundingReact.bottom + 160 <= navElementBoundingRect.height + + if (!isActiveItemVisible) { + const elementToScrollTo = activeItem + const elementBounding = elementToScrollTo.getBoundingClientRect() + // scroll to element + navElement.scroll({ + // the extra 160 is due to the sticky elements in the sidebar + top: + elementBounding.top - + navElementBoundingRect.top + + navElement.scrollTop - + 160, + behaviour: "smooth", + }) + } + }, [location]) + + return ( +
+ + +
+ ) +} + +export default React.memo(DocSidebarDesktop) diff --git a/www/apps/docs/src/theme/DocSidebarItem/Category/index.tsx b/www/apps/docs/src/theme/DocSidebarItem/Category/index.tsx new file mode 100644 index 0000000..a59c4c0 --- /dev/null +++ b/www/apps/docs/src/theme/DocSidebarItem/Category/index.tsx @@ -0,0 +1,244 @@ +import React, { type ComponentProps, useEffect, useMemo } from "react" +import clsx from "clsx" +import { + ThemeClassNames, + useThemeConfig, + usePrevious, + Collapsible, + useCollapsible, +} from "@docusaurus/theme-common" +import { + isActiveSidebarItem, + findFirstSidebarItemLink, + useDocSidebarItemsExpandedState, + isSamePath, +} from "@docusaurus/theme-common/internal" +import Link from "@docusaurus/Link" +import { translate } from "@docusaurus/Translate" +import useIsBrowser from "@docusaurus/useIsBrowser" +import DocSidebarItems from "@theme/DocSidebarItems" +import type { Props } from "@theme/DocSidebarItem/Category" +import { ModifiedPropSidebarItemCategory } from "@medusajs/docs" +import DocSidebarItemIcon from "../../../components/DocSidebarItemIcon" +import { Badge } from "docs-ui" + +type ModifiedProps = Props & { + item: ModifiedPropSidebarItemCategory +} + +// If we navigate to a category and it becomes active, it should automatically +// expand itself +function useAutoExpandActiveCategory({ + isActive, + collapsed, + updateCollapsed, +}: { + isActive: boolean + collapsed: boolean + updateCollapsed: (b: boolean) => void +}) { + const wasActive = usePrevious(isActive) + useEffect(() => { + const justBecameActive = isActive && !wasActive + if (justBecameActive && collapsed) { + updateCollapsed(false) + } + }, [isActive, wasActive, collapsed, updateCollapsed]) +} + +/** + * When a collapsible category has no link, we still link it to its first child + * during SSR as a temporary fallback. This allows to be able to navigate inside + * the category even when JS fails to load, is delayed or simply disabled + * React hydration becomes an optional progressive enhancement + * see https://github.com/facebookincubator/infima/issues/36#issuecomment-772543188 + * see https://github.com/facebook/docusaurus/issues/3030 + */ +function useCategoryHrefWithSSRFallback( + item: Props["item"] +): string | undefined { + const isBrowser = useIsBrowser() + return useMemo(() => { + if (item.href && !item.linkUnlisted) { + return item.href + } + // In these cases, it's not necessary to render a fallback + // We skip the "findFirstCategoryLink" computation + if (isBrowser || !item.collapsible) { + return undefined + } + return findFirstSidebarItemLink(item) + }, [item, isBrowser]) +} + +function CollapseButton({ + categoryLabel, + onClick, +}: { + collapsed: boolean + categoryLabel: string + onClick: ComponentProps<"button">["onClick"] +}) { + return ( + + ) +} diff --git a/www/apps/docs/src/theme/Footer/Layout/index.tsx b/www/apps/docs/src/theme/Footer/Layout/index.tsx new file mode 100644 index 0000000..e060e4d --- /dev/null +++ b/www/apps/docs/src/theme/Footer/Layout/index.tsx @@ -0,0 +1,51 @@ +import React from "react" +import clsx from "clsx" +import type { Props } from "@theme/Footer/Layout" +import { ThemeConfig } from "@medusajs/docs" +import { useThemeConfig } from "@docusaurus/theme-common" +import SocialLinks from "@site/src/components/Footer/SocialLinks" + +export default function FooterLayout({ + style, + links, + logo, + copyright, +}: Props): JSX.Element { + const { socialLinks } = useThemeConfig() as ThemeConfig + + return ( +
+
+ {(logo || copyright || socialLinks) && ( +
+
+ {logo &&
{logo}
} + {copyright} +
+
+ )} +
+ {socialLinks && } + {links} +
+
+
+ ) +} diff --git a/www/apps/docs/src/theme/Icon/Beaker/index.tsx b/www/apps/docs/src/theme/Icon/Beaker/index.tsx new file mode 100644 index 0000000..432c801 --- /dev/null +++ b/www/apps/docs/src/theme/Icon/Beaker/index.tsx @@ -0,0 +1,27 @@ +import { IconProps } from "@medusajs/icons/dist/types" +import clsx from "clsx" +import React from "react" + +const IconBeaker = (props: IconProps) => { + return ( + + + + ) +} + +export default IconBeaker diff --git a/www/apps/docs/src/theme/Icon/CircleDottedLine/index.tsx b/www/apps/docs/src/theme/Icon/CircleDottedLine/index.tsx new file mode 100644 index 0000000..9420c34 --- /dev/null +++ b/www/apps/docs/src/theme/Icon/CircleDottedLine/index.tsx @@ -0,0 +1,62 @@ +import { IconProps } from "@medusajs/icons/dist/types" +import clsx from "clsx" +import React from "react" + +const IconCircleDottedLine = (props: IconProps) => { + return ( + + + + + + + + + ) +} + +export default IconCircleDottedLine diff --git a/www/apps/docs/src/theme/Icon/Close/index.tsx b/www/apps/docs/src/theme/Icon/Close/index.tsx new file mode 100644 index 0000000..76a363d --- /dev/null +++ b/www/apps/docs/src/theme/Icon/Close/index.tsx @@ -0,0 +1,9 @@ +import React from "react" +import { XMark } from "@medusajs/icons" +import { IconProps } from "@medusajs/icons/dist/types" + +const IconClose = (props: IconProps) => { + return +} + +export default IconClose diff --git a/www/apps/docs/src/theme/Icon/Copy/index.tsx b/www/apps/docs/src/theme/Icon/Copy/index.tsx new file mode 100644 index 0000000..e2d0a16 --- /dev/null +++ b/www/apps/docs/src/theme/Icon/Copy/index.tsx @@ -0,0 +1,9 @@ +import React from "react" +import { SquareTwoStackSolid } from "@medusajs/icons" +import { IconProps } from "@medusajs/icons/dist/types" + +const IconCopy = (props: IconProps) => { + return +} + +export default IconCopy diff --git a/www/apps/docs/src/theme/Icon/DarkMode/index.tsx b/www/apps/docs/src/theme/Icon/DarkMode/index.tsx new file mode 100644 index 0000000..3668187 --- /dev/null +++ b/www/apps/docs/src/theme/Icon/DarkMode/index.tsx @@ -0,0 +1,9 @@ +import { Moon } from "@medusajs/icons" +import { IconProps } from "@medusajs/icons/dist/types" +import React from "react" + +const IconDarkMode = (props: IconProps) => { + return +} + +export default IconDarkMode diff --git a/www/apps/docs/src/theme/Icon/Discord/index.tsx b/www/apps/docs/src/theme/Icon/Discord/index.tsx new file mode 100644 index 0000000..960b8bc --- /dev/null +++ b/www/apps/docs/src/theme/Icon/Discord/index.tsx @@ -0,0 +1,24 @@ +import { IconProps } from "@medusajs/icons/dist/types" +import clsx from "clsx" +import React from "react" + +const IconDiscord = (props: IconProps) => { + return ( + + + + ) +} + +export default IconDiscord diff --git a/www/apps/docs/src/theme/Icon/ExternalLink/index.tsx b/www/apps/docs/src/theme/Icon/ExternalLink/index.tsx new file mode 100644 index 0000000..2fe03f1 --- /dev/null +++ b/www/apps/docs/src/theme/Icon/ExternalLink/index.tsx @@ -0,0 +1,9 @@ +import { ArrowUpRightOnBox } from "@medusajs/icons" +import { IconProps } from "@medusajs/icons/dist/types" +import React from "react" + +const IconExternalLink = (props: IconProps) => { + return +} + +export default IconExternalLink diff --git a/www/apps/docs/src/theme/Icon/FlagMini/index.tsx b/www/apps/docs/src/theme/Icon/FlagMini/index.tsx new file mode 100644 index 0000000..bd3948f --- /dev/null +++ b/www/apps/docs/src/theme/Icon/FlagMini/index.tsx @@ -0,0 +1,27 @@ +import { IconProps } from "@medusajs/icons/dist/types" +import clsx from "clsx" +import React from "react" + +const IconFlagMini = (props: IconProps) => { + return ( + + + + ) +} + +export default IconFlagMini diff --git a/www/apps/docs/src/theme/Icon/GitHub/index.tsx b/www/apps/docs/src/theme/Icon/GitHub/index.tsx new file mode 100644 index 0000000..678d426 --- /dev/null +++ b/www/apps/docs/src/theme/Icon/GitHub/index.tsx @@ -0,0 +1,26 @@ +import { IconProps } from "@medusajs/icons/dist/types" +import clsx from "clsx" +import React from "react" + +const IconGitHub = (props: IconProps) => { + return ( + + + + ) +} + +export default IconGitHub diff --git a/www/apps/docs/src/theme/Icon/LightMode/index.tsx b/www/apps/docs/src/theme/Icon/LightMode/index.tsx new file mode 100644 index 0000000..544e788 --- /dev/null +++ b/www/apps/docs/src/theme/Icon/LightMode/index.tsx @@ -0,0 +1,9 @@ +import React from "react" +import { Sun } from "@medusajs/icons" +import { IconProps } from "@medusajs/icons/dist/types" + +const IconLightMode = (props: IconProps) => { + return +} + +export default IconLightMode diff --git a/www/apps/docs/src/theme/Icon/LinkedIn/index.tsx b/www/apps/docs/src/theme/Icon/LinkedIn/index.tsx new file mode 100644 index 0000000..ba5660f --- /dev/null +++ b/www/apps/docs/src/theme/Icon/LinkedIn/index.tsx @@ -0,0 +1,24 @@ +import { IconProps } from "@medusajs/icons/dist/types" +import clsx from "clsx" +import React from "react" + +const IconLinkedIn = (props: IconProps) => { + return ( + + + + ) +} + +export default IconLinkedIn diff --git a/www/apps/docs/src/theme/Icon/Nextjs/index.tsx b/www/apps/docs/src/theme/Icon/Nextjs/index.tsx new file mode 100644 index 0000000..a105a45 --- /dev/null +++ b/www/apps/docs/src/theme/Icon/Nextjs/index.tsx @@ -0,0 +1,28 @@ +import { IconProps } from "@medusajs/icons/dist/types" +import clsx from "clsx" +import React from "react" + +const IconNextjs = (props: IconProps) => { + return ( + + + + + ) +} + +export default IconNextjs diff --git a/www/apps/docs/src/theme/Icon/PuzzleSolid/index.tsx b/www/apps/docs/src/theme/Icon/PuzzleSolid/index.tsx new file mode 100644 index 0000000..558fbda --- /dev/null +++ b/www/apps/docs/src/theme/Icon/PuzzleSolid/index.tsx @@ -0,0 +1,24 @@ +import { IconProps } from "@medusajs/icons/dist/types" +import clsx from "clsx" +import React from "react" + +const IconPuzzleSolid = (props: IconProps) => { + return ( + + + + ) +} + +export default IconPuzzleSolid diff --git a/www/apps/docs/src/theme/Icon/Twitter/index.tsx b/www/apps/docs/src/theme/Icon/Twitter/index.tsx new file mode 100644 index 0000000..19bdcbc --- /dev/null +++ b/www/apps/docs/src/theme/Icon/Twitter/index.tsx @@ -0,0 +1,24 @@ +import { IconProps } from "@medusajs/icons/dist/types" +import clsx from "clsx" +import React from "react" + +const IconTwitter = (props: IconProps) => { + return ( + + + + ) +} + +export default IconTwitter diff --git a/www/apps/docs/src/theme/Icon/index.tsx b/www/apps/docs/src/theme/Icon/index.tsx new file mode 100644 index 0000000..0b5eaa6 --- /dev/null +++ b/www/apps/docs/src/theme/Icon/index.tsx @@ -0,0 +1,222 @@ +import { + AtSymbol, + AcademicCapSolid, + Adjustments, + ArrowDownLeftMini, + ArrowDownTray, + ArrowUpRightOnBox, + ArrowUturnLeft, + BarsThree, + BellAlert, + BellAlertSolid, + Bolt, + BoltSolid, + BookOpen, + Bug, + BugAntSolid, + BuildingStorefront, + BuildingTax, + BuildingsSolid, + Calendar, + CashSolid, + Channels, + ChannelsSolid, + CheckCircleSolid, + CheckMini, + ChevronDoubleLeftMiniSolid, + ChevronUpDown, + CircleDottedLine, + CircleMiniSolid, + CircleStack, + CircleStackSolid, + ClockSolidMini, + CloudArrowUp, + CogSixTooth, + CogSixToothSolid, + CommandLine, + CommandLineSolid, + ComponentSolid, + ComputerDesktop, + ComputerDesktopSolid, + CreditCardSolid, + CubeSolid, + CurrencyDollar, + CurrencyDollarSolid, + Discord, + DocumentText, + DocumentTextSolid, + EllipseMiniSolid, + ExclamationCircle, + ExclamationCircleSolid, + FlyingBox, + Folder, + FolderOpen, + Gatsby, + GiftSolid, + GlobeEurope, + GlobeEuropeSolid, + InformationCircleSolid, + JavascriptEx, + Key, + KeySolid, + LightBulb, + LightBulbSolid, + Linkedin, + MagnifyingGlass, + Map, + Moon, + Newspaper, + PencilSquareSolid, + Puzzle, + ReactJsEx, + ReceiptPercent, + RocketLaunch, + Server, + ServerSolid, + ServerStack, + ServerStackSolid, + ShoppingCart, + ShoppingCartSolid, + Sparkles, + SparklesSolid, + SquareTwoStackSolid, + SquaresPlus, + SquaresPlusSolid, + Star, + StarSolid, + Stripe, + Sun, + SwatchSolid, + TagSolid, + Text, + Tools, + ToolsSolid, + Twitter, + User, + UsersSolid, + XCircleSolid, + XMark, + XMarkMini, + PhotoSolid, + PlaySolid, + ChatBubbleLeftRightSolid, +} from "@medusajs/icons" +import IconPuzzleSolid from "./PuzzleSolid" +import IconNextjs from "./Nextjs" +import IconFlagMini from "./FlagMini" +import IconBeaker from "./Beaker" +import IconGitHub from "./GitHub" + +const icons = { + "academic-cap-solid": AcademicCapSolid, + adjustments: Adjustments, + alert: ExclamationCircleSolid, + "arrow-down-left-mini": ArrowDownLeftMini, + "arrow-down-tray": ArrowDownTray, + "at-symbol": AtSymbol, + "back-arrow": ArrowUturnLeft, + "bars-three": BarsThree, + beaker: IconBeaker, + bell: BellAlert, + "bell-alert-solid": BellAlertSolid, + bolt: Bolt, + "bolt-solid": BoltSolid, + "book-open": BookOpen, + bug: Bug, + "bug-ant-solid": BugAntSolid, + "building-solid": BuildingsSolid, + "building-storefront": BuildingStorefront, + "building-tax": BuildingTax, + calendar: Calendar, + "cash-solid": CashSolid, + "channels-solid": ChannelsSolid, + channels: Channels, + "chat-bubble-left-right-solid": ChatBubbleLeftRightSolid, + "check-circle-solid": CheckCircleSolid, + "check-mini": CheckMini, + "chevron-double-left-mini-solid": ChevronDoubleLeftMiniSolid, + "chevron-up-down": ChevronUpDown, + "circle-dotted-line": CircleDottedLine, + "circle-mini-solid": CircleMiniSolid, + "circle-stack": CircleStack, + "circle-stack-solid": CircleStackSolid, + "clock-solid-mini": ClockSolidMini, + close: XMark, + "cloud-arrow-up": CloudArrowUp, + "cog-six-tooth": CogSixTooth, + "cog-six-tooth-solid": CogSixToothSolid, + "command-line": CommandLine, + "command-line-solid": CommandLineSolid, + "component-solid": ComponentSolid, + "computer-desktop": ComputerDesktop, + "computer-desktop-solid": ComputerDesktopSolid, + copy: SquareTwoStackSolid, + "credit-card-solid": CreditCardSolid, + "cube-solid": CubeSolid, + "currency-dollar": CurrencyDollar, + "currency-dollar-solid": CurrencyDollarSolid, + "dark-mode": Moon, + discord: Discord, + "document-text": DocumentText, + "document-text-solid": DocumentTextSolid, + "ellipse-mini-solid": EllipseMiniSolid, + "exclamation-circle-solid": ExclamationCircleSolid, + "external-link": ArrowUpRightOnBox, + "flying-box": FlyingBox, + folder: Folder, + "folder-open": FolderOpen, + gatsby: Gatsby, + "gift-solid": GiftSolid, + "flag-mini": IconFlagMini, + github: IconGitHub, + "globe-europe": GlobeEurope, + "globe-europe-solid": GlobeEuropeSolid, + "information-circle-solid": InformationCircleSolid, + javascript: JavascriptEx, + key: Key, + "key-solid": KeySolid, + "light-bulb": LightBulb, + "light-bulb-solid": LightBulbSolid, + "light-mode": Sun, + linkedin: Linkedin, + "magnifying-glass": MagnifyingGlass, + map: Map, + newspaper: Newspaper, + nextjs: IconNextjs, + "pencil-square-solid": PencilSquareSolid, + "photo-solid": PhotoSolid, + "play-solid": PlaySolid, + puzzle: Puzzle, + // TODO change once available in Icons package + "puzzle-solid": IconPuzzleSolid, + react: ReactJsEx, + "receipt-percent": ReceiptPercent, + report: ExclamationCircle, + "rocket-launch": RocketLaunch, + server: Server, + "server-solid": ServerSolid, + "server-stack": ServerStack, + "server-stack-solid": ServerStackSolid, + "shopping-cart": ShoppingCart, + "shopping-cart-solid": ShoppingCartSolid, + sparkles: Sparkles, + "sparkles-solid": SparklesSolid, + "squares-plus": SquaresPlus, + "squares-plus-solid": SquaresPlusSolid, + star: Star, + "star-solid": StarSolid, + stripe: Stripe, + "swatch-solid": SwatchSolid, + "tag-solid": TagSolid, + text: Text, + tools: Tools, + "tools-solid": ToolsSolid, + twitter: Twitter, + user: User, + "users-solid": UsersSolid, + "x-circle-solid": XCircleSolid, + "x-mark": XMark, + "x-mark-mini": XMarkMini, +} +export type IconName = keyof typeof icons +export default icons \ No newline at end of file diff --git a/www/apps/docs/src/theme/Layout/index.tsx b/www/apps/docs/src/theme/Layout/index.tsx new file mode 100644 index 0000000..a422645 --- /dev/null +++ b/www/apps/docs/src/theme/Layout/index.tsx @@ -0,0 +1,75 @@ +import React, { useEffect } from "react" +import clsx from "clsx" +import ErrorBoundary from "@docusaurus/ErrorBoundary" +import { + PageMetadata, + SkipToContentFallbackId, + ThemeClassNames, +} from "@docusaurus/theme-common" +import { useKeyboardNavigation } from "@docusaurus/theme-common/internal" +import SkipToContent from "@theme/SkipToContent" +import Navbar from "@theme/Navbar" +import LayoutProvider from "@theme/Layout/Provider" +import ErrorPageContent from "@theme/ErrorPageContent" +import type { Props } from "@theme/Layout" +import useIsBrowser from "@docusaurus/useIsBrowser" +import { useLocation } from "@docusaurus/router" +import { useAnalytics } from "docs-ui" + +export default function Layout(props: Props): JSX.Element { + const { + children, + wrapperClassName, + // Not really layout-related, but kept for convenience/retro-compatibility + title, + description, + } = props + + useKeyboardNavigation() + const isBrowser = useIsBrowser() + const location = useLocation() + const { track } = useAnalytics() + + useEffect(() => { + if (isBrowser) { + const handlePlay = () => { + track("video_played") + } + + const videos = document.querySelectorAll("video") + videos.forEach((video) => + video.addEventListener("play", handlePlay, { + once: true, + capture: true, + }) + ) + + return () => { + videos.forEach((video) => video.removeEventListener("play", handlePlay)) + } + } + }, [isBrowser, location.pathname]) + + return ( + + + + + + + +
+ }> + {children} + +
+
+ ) +} diff --git a/www/apps/docs/src/theme/MDXComponents/A.tsx b/www/apps/docs/src/theme/MDXComponents/A.tsx new file mode 100644 index 0000000..14887f8 --- /dev/null +++ b/www/apps/docs/src/theme/MDXComponents/A.tsx @@ -0,0 +1,48 @@ +import React, { useMemo } from "react" +import { getGlossaryByPath } from "../../utils/glossary" +import useDocusaurusContext from "@docusaurus/useDocusaurusContext" +import { MedusaDocusaurusContext } from "@medusajs/docs" +import Link from "@docusaurus/Link" +import type { Props } from "@docusaurus/Link" +import clsx from "clsx" +import { Tooltip } from "docs-ui" + +const MDXA = (props: Omit) => { + const { href, children } = props + const { + siteConfig: { url }, + } = useDocusaurusContext() as MedusaDocusaurusContext + + // check if a glossary exists for href + const glossary = useMemo(() => { + return (typeof children === "string" && href.startsWith("/")) || + href.includes(url) + ? getGlossaryByPath(children as string) + : null + }, [href, children]) + + if (!glossary) { + return + } + + return ( + + + {glossary.title} + + {glossary.content} + + } + clickable={true} + > + + + ) +} + +export default MDXA diff --git a/www/apps/docs/src/theme/MDXComponents/Code.tsx b/www/apps/docs/src/theme/MDXComponents/Code.tsx new file mode 100644 index 0000000..b16de38 --- /dev/null +++ b/www/apps/docs/src/theme/MDXComponents/Code.tsx @@ -0,0 +1,17 @@ +import type { ComponentProps } from "react" +import React from "react" +import CodeBlock from "@theme/CodeBlock" +import type { Props } from "@theme/MDXComponents/Code" +import { InlineCode } from "docs-ui" + +export default function MDXCode(props: Omit): JSX.Element { + const shouldBeInline = React.Children.toArray(props.children).every( + (el) => typeof el === "string" && !el.includes("\n") + ) + + return shouldBeInline ? ( + + ) : ( + )} /> + ) +} diff --git a/www/apps/docs/src/theme/MDXComponents/Details.tsx b/www/apps/docs/src/theme/MDXComponents/Details.tsx new file mode 100644 index 0000000..d678659 --- /dev/null +++ b/www/apps/docs/src/theme/MDXComponents/Details.tsx @@ -0,0 +1,27 @@ +import React, { type ComponentProps, type ReactElement } from "react" +import Details from "@theme/Details" +import type { Props } from "@theme/MDXComponents/Details" +import { DetailsSummary } from "docs-ui" + +export default function MDXDetails(props: Omit): JSX.Element { + const items = React.Children.toArray(props.children) + // Split summary item from the rest to pass it as a separate prop to the + // Details theme component + const summary = items.find( + ( + item: ReactElement> + ): item is ReactElement> => { + return ( + React.isValidElement(item) && + (item.type === "summary" || item.type === DetailsSummary) + ) + } + ) + const children = <>{items.filter((item) => item !== summary)} + + return ( +
+ {children} +
+ ) +} diff --git a/www/apps/docs/src/theme/MDXComponents/H1.tsx b/www/apps/docs/src/theme/MDXComponents/H1.tsx new file mode 100644 index 0000000..34b696c --- /dev/null +++ b/www/apps/docs/src/theme/MDXComponents/H1.tsx @@ -0,0 +1,28 @@ +import React from "react" +import type { Props } from "@theme/MDXComponents/Heading" +import MDXHeading from "@theme/MDXComponents/Heading" +import { useDoc } from "@docusaurus/theme-common/internal" +import { DocContextValue } from "@medusajs/docs" +import { Badge, BadgeVariant } from "docs-ui" +import clsx from "clsx" + +const H1 = ({ className, ...props }: Omit) => { + const { + frontMatter: { badge }, + } = useDoc() as DocContextValue + + return ( +
+ + {badge && ( + {badge.text} + )} +
+ ) +} + +export default H1 diff --git a/www/apps/docs/src/theme/MDXComponents/index.tsx b/www/apps/docs/src/theme/MDXComponents/index.tsx new file mode 100644 index 0000000..a068bb7 --- /dev/null +++ b/www/apps/docs/src/theme/MDXComponents/index.tsx @@ -0,0 +1,25 @@ +// Import the original mapper +import MDXComponents from "@theme-original/MDXComponents" +import CloudinaryImage from "@site/src/components/CloudinaryImage" +import MDXA from "./A" +import { Kbd, DetailsSummary } from "docs-ui" +import H1 from "./H1" +import MDXCode from "./Code" +import MDXDetails from "./Details" +import DocCard from "@theme/DocCard" +import DocCardList from "@theme/DocCardList" + +export default { + // Re-use the default mapping + ...MDXComponents, + code: MDXCode, + img: CloudinaryImage, + details: MDXDetails, + Details: MDXDetails, + Summary: DetailsSummary, + a: MDXA, + kbd: Kbd, + h1: H1, + DocCard, + DocCardList, +} diff --git a/www/apps/docs/src/theme/Navbar/ColorModeToggle/index.tsx b/www/apps/docs/src/theme/Navbar/ColorModeToggle/index.tsx new file mode 100644 index 0000000..01b0425 --- /dev/null +++ b/www/apps/docs/src/theme/Navbar/ColorModeToggle/index.tsx @@ -0,0 +1,25 @@ +import React from "react" +import { useColorMode, useThemeConfig } from "@docusaurus/theme-common" +import ColorModeToggle from "@theme/ColorModeToggle" +import type { Props } from "@theme/Navbar/ColorModeToggle" +import clsx from "clsx" + +export default function NavbarColorModeToggle({ + className, +}: Props): JSX.Element | null { + const disabled = useThemeConfig().colorMode.disableSwitch + const { colorMode, setColorMode } = useColorMode() + + if (disabled) { + return null + } + + return ( + + ) +} diff --git a/www/apps/docs/src/theme/Navbar/Content/index.tsx b/www/apps/docs/src/theme/Navbar/Content/index.tsx new file mode 100644 index 0000000..0bc3535 --- /dev/null +++ b/www/apps/docs/src/theme/Navbar/Content/index.tsx @@ -0,0 +1,167 @@ +import React, { type ReactNode } from "react" +import { useThemeConfig } from "@docusaurus/theme-common" +import { + splitNavbarItems, + useNavbarMobileSidebar, +} from "@docusaurus/theme-common/internal" +import NavbarItem, { type Props as NavbarItemConfig } from "@theme/NavbarItem" +import NavbarColorModeToggle from "@theme/Navbar/ColorModeToggle" +import NavbarMobileSidebarToggle from "@theme/Navbar/MobileSidebar/Toggle" +import NavbarLogo from "@theme/Navbar/Logo" +import clsx from "clsx" +import { ThemeConfig } from "@medusajs/docs" +import { useSidebar } from "../../../providers/Sidebar" +import useIsBrowser from "@docusaurus/useIsBrowser" +import { Tooltip } from "docs-ui" +import NavbarActions from "../../../components/Navbar/Actions" +import { ChevronDoubleLeftMiniSolid, Sidebar } from "@medusajs/icons" + +function useNavbarItems() { + // TODO temporary casting until ThemeConfig type is improved + return useThemeConfig().navbar.items as NavbarItemConfig[] +} + +function NavbarItems({ items }: { items: NavbarItemConfig[] }): JSX.Element { + return ( + <> + {items.map((item, i) => ( + + ))} + + ) +} + +function NavbarContentLayout({ + left, + right, +}: { + left: ReactNode + right: ReactNode +}) { + return ( +
+
+ {left} +
+
+ {right} +
+
+ ) +} + +export default function NavbarContent(): JSX.Element { + const mobileSidebar = useNavbarMobileSidebar() + + const items = useNavbarItems() + const [leftItems, rightItems] = splitNavbarItems(items) + const { + navbarActions, + docs: { + sidebar: { hideable }, + }, + } = useThemeConfig() as ThemeConfig + const sidebarContext = useSidebar() + const isBrowser = useIsBrowser() + + const isApple = isBrowser + ? navigator.userAgent.toLowerCase().indexOf("mac") !== 0 + : true + + return ( + + {!mobileSidebar.disabled && } + + + + } + right={ + // TODO stop hardcoding items? + // Ask the user to add the respective navbar items => more flexible +
+ + {hideable && sidebarContext?.hasSidebar && ( + Close sidebar ${isApple ? "⌘" : "Ctrl"} + I` + : `Lock sidebar open ${isApple ? "⌘" : "Ctrl"} + I`, + events: { + onClick: sidebarContext?.onCollapse, + onMouseEnter: () => { + if (!sidebarContext?.hiddenSidebarContainer) { + sidebarContext?.setFloatingSidebar(false) + } else { + sidebarContext?.setFloatingSidebar(true) + } + }, + onMouseLeave: () => { + setTimeout(() => { + if ( + !document.querySelector( + ".theme-doc-sidebar-container:hover" + ) + ) { + sidebarContext?.setFloatingSidebar(false) + } + }, 100) + }, + }, + Icon: !sidebarContext?.hiddenSidebarContainer ? ( + + ) : ( + + ), + buttonType: "icon", + }, + ]} + className="sidebar-toggler" + /> + )} + + button]:!rounded" + )} + /> + + +
+ } + /> + ) +} diff --git a/www/apps/docs/src/theme/Navbar/Layout/index.tsx b/www/apps/docs/src/theme/Navbar/Layout/index.tsx new file mode 100644 index 0000000..f200cd2 --- /dev/null +++ b/www/apps/docs/src/theme/Navbar/Layout/index.tsx @@ -0,0 +1,44 @@ +import React from "react" +import clsx from "clsx" +import { useThemeConfig } from "@docusaurus/theme-common" +import { + useHideableNavbar, + useNavbarMobileSidebar, +} from "@docusaurus/theme-common/internal" +import { translate } from "@docusaurus/Translate" +import NavbarMobileSidebar from "@theme/Navbar/MobileSidebar" +import type { Props } from "@theme/Navbar/Layout" + +export default function NavbarLayout({ children }: Props): JSX.Element { + const { + navbar: { hideOnScroll, style }, + } = useThemeConfig() + const mobileSidebar = useNavbarMobileSidebar() + const { navbarRef, isNavbarVisible } = useHideableNavbar(hideOnScroll) + return ( + + ) +} diff --git a/www/apps/docs/src/theme/Navbar/Logo/index.tsx b/www/apps/docs/src/theme/Navbar/Logo/index.tsx new file mode 100644 index 0000000..15e6192 --- /dev/null +++ b/www/apps/docs/src/theme/Navbar/Logo/index.tsx @@ -0,0 +1,20 @@ +import React from "react" +import Logo from "@theme/Logo" +import MobileLogo from "../../../components/MobileLogo" + +export default function NavbarLogo(): JSX.Element { + return ( + <> + + + + ) +} diff --git a/www/apps/docs/src/theme/Navbar/MobileSidebar/Header/index.tsx b/www/apps/docs/src/theme/Navbar/MobileSidebar/Header/index.tsx new file mode 100644 index 0000000..68a99a0 --- /dev/null +++ b/www/apps/docs/src/theme/Navbar/MobileSidebar/Header/index.tsx @@ -0,0 +1,43 @@ +import React from "react" +import { useNavbarMobileSidebar } from "@docusaurus/theme-common/internal" +import { translate } from "@docusaurus/Translate" +import NavbarColorModeToggle from "@theme/Navbar/ColorModeToggle" +import IconClose from "@theme/Icon/Close" +import NavbarLogo from "@theme/Navbar/Logo" +import clsx from "clsx" + +function CloseButton() { + const mobileSidebar = useNavbarMobileSidebar() + return ( + + ) +} + +export default function NavbarMobileSidebarHeader(): JSX.Element { + return ( +
+ + button]:hover:bg-medusa-button-neutral-hover", + "[&>button]:!rounded" + )} + /> + +
+ ) +} diff --git a/www/apps/docs/src/theme/Navbar/MobileSidebar/Layout/index.tsx b/www/apps/docs/src/theme/Navbar/MobileSidebar/Layout/index.tsx new file mode 100644 index 0000000..2016389 --- /dev/null +++ b/www/apps/docs/src/theme/Navbar/MobileSidebar/Layout/index.tsx @@ -0,0 +1,23 @@ +import React from "react" +import clsx from "clsx" +import { useNavbarSecondaryMenu } from "@docusaurus/theme-common/internal" +import type { Props } from "@theme/Navbar/MobileSidebar/Layout" + +export default function NavbarMobileSidebarLayout({ + primaryMenu, + secondaryMenu, +}: Props): JSX.Element { + const { shown: secondaryMenuShown } = useNavbarSecondaryMenu() + return ( +
+
+
{primaryMenu}
+
{secondaryMenu}
+
+
+ ) +} diff --git a/www/apps/docs/src/theme/Navbar/MobileSidebar/Toggle/index.tsx b/www/apps/docs/src/theme/Navbar/MobileSidebar/Toggle/index.tsx new file mode 100644 index 0000000..015493f --- /dev/null +++ b/www/apps/docs/src/theme/Navbar/MobileSidebar/Toggle/index.tsx @@ -0,0 +1,25 @@ +import React from "react" +import { useNavbarMobileSidebar } from "@docusaurus/theme-common/internal" +import { translate } from "@docusaurus/Translate" +import { Sidebar, XMark } from "@medusajs/icons" + +export default function MobileSidebarToggle(): JSX.Element { + const { toggle, shown } = useNavbarMobileSidebar() + return ( + + ) +} diff --git a/www/apps/docs/src/theme/Navbar/Search/index.tsx b/www/apps/docs/src/theme/Navbar/Search/index.tsx new file mode 100644 index 0000000..a93dcda --- /dev/null +++ b/www/apps/docs/src/theme/Navbar/Search/index.tsx @@ -0,0 +1,10 @@ +import React from "react" +import clsx from "clsx" +import type { Props } from "@theme/Navbar/Search" + +export default function NavbarSearch({ + children, + className, +}: Props): JSX.Element { + return
{children}
+} diff --git a/www/apps/docs/src/theme/NavbarItem/NavbarNavLink.tsx b/www/apps/docs/src/theme/NavbarItem/NavbarNavLink.tsx new file mode 100644 index 0000000..a87c90e --- /dev/null +++ b/www/apps/docs/src/theme/NavbarItem/NavbarNavLink.tsx @@ -0,0 +1,55 @@ +import React from "react" +import Link from "@docusaurus/Link" +import useBaseUrl from "@docusaurus/useBaseUrl" +import { isRegexpStringMatch } from "@docusaurus/theme-common" +import type { Props } from "@theme/NavbarItem/NavbarNavLink" + +export default function NavbarNavLink({ + activeBasePath, + activeBaseRegex, + to, + href, + label, + html, + prependBaseUrlToHref, + ...props +}: Omit): JSX.Element { + // TODO all this seems hacky + // {to: 'version'} should probably be forbidden, in favor of {to: '/version'} + const toUrl = useBaseUrl(to) + const activeBaseUrl = useBaseUrl(activeBasePath) + const normalizedHref = useBaseUrl(href, { forcePrependBaseUrl: true }) + + // Link content is set through html XOR label + const linkContentProps = html + ? { dangerouslySetInnerHTML: { __html: html } } + : { + children: <>{label}, + } + + if (href) { + return ( + + ) + } + + return ( + { + return activeBaseRegex + ? isRegexpStringMatch(activeBaseRegex, location.pathname) + : location.pathname.startsWith(activeBaseUrl) + }, + })} + {...props} + {...linkContentProps} + /> + ) +} diff --git a/www/apps/docs/src/theme/NavbarItem/SearchNavbarItem.tsx b/www/apps/docs/src/theme/NavbarItem/SearchNavbarItem.tsx new file mode 100644 index 0000000..1618b18 --- /dev/null +++ b/www/apps/docs/src/theme/NavbarItem/SearchNavbarItem.tsx @@ -0,0 +1,17 @@ +import React from "react" +import NavbarSearch from "@theme/Navbar/Search" +import type { Props } from "@theme/NavbarItem/SearchNavbarItem" +import { SearchModalOpener } from "docs-ui" +import { useWindowSize } from "@docusaurus/theme-common" + +export default function SearchNavbarItem({ + mobile, +}: Props): JSX.Element | null { + const windowSize = useWindowSize() + + return ( + + + + ) +} diff --git a/www/apps/docs/src/theme/NotFound/Content/index.tsx b/www/apps/docs/src/theme/NotFound/Content/index.tsx new file mode 100644 index 0000000..67b276c --- /dev/null +++ b/www/apps/docs/src/theme/NotFound/Content/index.tsx @@ -0,0 +1,47 @@ +import React from "react" +import clsx from "clsx" +import Translate from "@docusaurus/Translate" +import type { Props } from "@theme/NotFound/Content" +import Heading from "@theme/Heading" +import Link from "@docusaurus/Link" + +export default function NotFoundContent({ className }: Props): JSX.Element { + return ( +
+
+
+ + + Page Not Found + + +

+ + Looks like the page you're looking for has either changed + into a different location or isn't in our documentation + anymore. + +

+

+ If you think this is a mistake, please{" "} + + report this issue on GitHub + +

+
+
+
+ ) +} diff --git a/www/apps/docs/src/theme/NotFound/index.tsx b/www/apps/docs/src/theme/NotFound/index.tsx new file mode 100644 index 0000000..bcd6d94 --- /dev/null +++ b/www/apps/docs/src/theme/NotFound/index.tsx @@ -0,0 +1,21 @@ +import React from "react" +import { translate } from "@docusaurus/Translate" +import { PageMetadata } from "@docusaurus/theme-common" +import Layout from "@theme/Layout" +import NotFoundContent from "@theme/NotFound/Content" +import DocsProviders from "../../providers/DocsProviders" + +export default function Index(): JSX.Element { + const title = translate({ + id: "theme.NotFound.title", + message: "Page Not Found", + }) + return ( + + + + + + + ) +} diff --git a/www/apps/docs/src/theme/SearchBar/index.tsx b/www/apps/docs/src/theme/SearchBar/index.tsx new file mode 100644 index 0000000..2edced1 --- /dev/null +++ b/www/apps/docs/src/theme/SearchBar/index.tsx @@ -0,0 +1,51 @@ +import React, { useEffect } from "react" +import SearchBar from "@theme-original/SearchBar" +import type SearchBarType from "@theme/SearchBar" +import type { WrapperProps } from "@docusaurus/types" +import useIsBrowser from "@docusaurus/useIsBrowser" +import { useLocation } from "@docusaurus/router" +import { useAnalytics } from "docs-ui" + +type Props = WrapperProps + +export default function SearchBarWrapper(props: Props): JSX.Element { + const isBrowser = useIsBrowser() + const location = useLocation() + const { track } = useAnalytics() + + useEffect(() => { + if (isBrowser) { + const trackSearch = (e) => { + if ( + !e.target.classList?.contains("DocSearch-Input") && + !( + e.target.tagName.toLowerCase() === "input" && + e.target.getAttribute("type") === "search" + ) + ) { + return + } + + const query = e.target.value + if (query.length >= 3) { + // send event to segment + track("search", { + query, + }) + } + } + + document.body.addEventListener("keyup", trackSearch) + + return () => { + document.body.removeEventListener("keyup", trackSearch) + } + } + }, [isBrowser, location.pathname]) + + return ( + <> + + + ) +} diff --git a/www/apps/docs/src/theme/SearchTranslations/index.ts b/www/apps/docs/src/theme/SearchTranslations/index.ts new file mode 100644 index 0000000..caee173 --- /dev/null +++ b/www/apps/docs/src/theme/SearchTranslations/index.ts @@ -0,0 +1,12 @@ +import { translate } from "@docusaurus/Translate" +import translations from "@theme-original/SearchTranslations" + +const changedTranslations = { + ...translations, + placeholder: translate({ + id: "theme.SearchModal.placeholder", + message: "Find something", + description: "The placeholder of the input of the DocSearch pop-up modal", + }), +} +export default changedTranslations diff --git a/www/apps/docs/src/theme/TOCItems/index.tsx b/www/apps/docs/src/theme/TOCItems/index.tsx new file mode 100644 index 0000000..2c86e1b --- /dev/null +++ b/www/apps/docs/src/theme/TOCItems/index.tsx @@ -0,0 +1,22 @@ +import React from "react" +import TOCItems from "@theme-original/TOCItems" +import type TOCItemsType from "@theme/TOCItems" +import type { WrapperProps } from "@docusaurus/types" +import { useDoc } from "@docusaurus/theme-common/internal" +import { DocContextValue } from "@medusajs/docs" +import StructuredDataHowTo from "../../components/StructuredData/HowTo" + +type Props = WrapperProps + +export default function TOCItemsWrapper(props: Props): JSX.Element { + const { frontMatter, contentTitle } = useDoc() as DocContextValue + + return ( + <> + + {frontMatter?.addHowToData && ( + + )} + + ) +} diff --git a/www/apps/docs/src/theme/Tabs/index.tsx b/www/apps/docs/src/theme/Tabs/index.tsx new file mode 100644 index 0000000..c216b71 --- /dev/null +++ b/www/apps/docs/src/theme/Tabs/index.tsx @@ -0,0 +1,280 @@ +import React, { + cloneElement, + useRef, + type ReactElement, + useEffect, + useState, +} from "react" +import clsx from "clsx" +import { + useScrollPositionBlocker, + useTabs, + sanitizeTabsChildren, + type TabItemProps, +} from "@docusaurus/theme-common/internal" +import useIsBrowser from "@docusaurus/useIsBrowser" +import type { Props as OldProps } from "@theme/Tabs" + +type TabsCustomProps = { + isCodeTabs?: boolean + codeTitle?: string +} + +type TabListProps = OldProps & ReturnType & TabsCustomProps + +type TabsComponentProp = TabsCustomProps & OldProps + +type TabsProps = { + wrapperClassName?: string + isCodeTabs?: boolean +} & OldProps + +function TabList({ + className, + selectedValue, + selectValue, + tabValues, + isCodeTabs = false, + codeTitle, +}: TabListProps) { + const tabRefs: (HTMLLIElement | null)[] = [] + const { blockElementScrollPositionUntilNextRender } = + useScrollPositionBlocker() + const codeTabSelectorRef = useRef(null) + const codeTabsWrapperRef = useRef(null) + const [tabsTitle, setTabsTitle] = useState(codeTitle) + + const handleTabChange = ( + event: + | React.FocusEvent + | React.MouseEvent + | React.KeyboardEvent + ) => { + const newTab = event.currentTarget + const newTabIndex = tabRefs.indexOf(newTab) + const newTabValue = tabValues[newTabIndex]!.value + + if (newTabValue !== selectedValue) { + blockElementScrollPositionUntilNextRender(newTab) + selectValue(newTabValue) + } + } + + const handleKeydown = (event: React.KeyboardEvent) => { + let focusElement: HTMLLIElement | null = null + + switch (event.key) { + case "Enter": { + handleTabChange(event) + break + } + case "ArrowRight": { + const nextTab = tabRefs.indexOf(event.currentTarget) + 1 + focusElement = tabRefs[nextTab] ?? tabRefs[0]! + break + } + case "ArrowLeft": { + const prevTab = tabRefs.indexOf(event.currentTarget) - 1 + focusElement = tabRefs[prevTab] ?? tabRefs[tabRefs.length - 1]! + break + } + default: + break + } + + focusElement?.focus() + } + + const changeTabSelectorCoordinates = (selectedTab) => { + if (!codeTabSelectorRef?.current || !codeTabsWrapperRef?.current) { + return + } + const selectedTabsCoordinates = selectedTab.getBoundingClientRect() + const tabsWrapperCoordinates = + codeTabsWrapperRef.current.getBoundingClientRect() + codeTabSelectorRef.current.style.left = `${ + selectedTabsCoordinates.left - tabsWrapperCoordinates.left + }px` + codeTabSelectorRef.current.style.width = `${selectedTabsCoordinates.width}px` + codeTabSelectorRef.current.style.height = `${selectedTabsCoordinates.height}px` + } + + useEffect(() => { + if (codeTabSelectorRef?.current && tabRefs.length) { + const selectedTab = tabRefs.find( + (tab) => tab.getAttribute("aria-selected") === "true" + ) + if (selectedTab) { + changeTabSelectorCoordinates(selectedTab) + } + } + }, [codeTabSelectorRef, tabRefs]) + + useEffect(() => { + const selectedTab = tabValues.find((tab) => tab.value === selectedValue) + if (selectedTab?.attributes?.title) { + setTabsTitle(selectedTab.attributes.title as string) + } else { + setTabsTitle(codeTitle) + } + }, [selectedValue]) + + return ( +
+
+ {isCodeTabs && ( + + )} +
    + {tabValues.map(({ value, label, attributes }) => ( +
  • tabRefs.push(tabControl)} + onKeyDown={handleKeydown} + onClick={handleTabChange} + {...attributes} + className={clsx( + isCodeTabs && [ + "text-compact-small-plus py-0.25 border border-solid border-transparent whitespace-nowrap rounded-full [&:not(:first-child)]:ml-0.25", + "z-[2] flex justify-center items-center", + selectedValue !== value && + "text-medusa-code-text-subtle hover:!bg-medusa-code-bg-base", + selectedValue === value && + "text-medusa-code-text-base border border-solid border-medusa-code-border bg-medusa-code-bg-base xs:!border-none xs:!bg-transparent", + attributes?.badge && + "[&_.badge]:ml-0.5 [&_.badge]:py-0.125 [&_.badge]:px-[6px] [&_.badge]:rounded-full pl-0.75 pr-0.25", + !attributes?.badge && "px-0.75", + ], + !isCodeTabs && [ + "[&:not(:last-child)]:mr-0.5 px-0.75 py-[6px] txt-compact-small-plus", + "border-0 rounded-full transition-shadow duration-200 ease-ease", + selectedValue === value && + "text-medusa-fg-base shadow-card-rest dark:shadow-card-rest-dark", + selectedValue !== value && + "text-medusa-fg-subtle hover:text-medusa-fg-base", + "flex gap-0.5", + ], + "!mt-0 cursor-pointer", + attributes?.className + )} + > + {label ?? value} +
  • + ))} +
+
+ {isCodeTabs && ( + + )} +
+ ) +} + +function TabContent({ + lazy, + children, + selectedValue, +}: OldProps & ReturnType) { + const childTabs = (Array.isArray(children) ? children : [children]).filter( + Boolean + ) as ReactElement[] + if (lazy) { + const selectedTabItem = childTabs.find( + (tabItem) => tabItem.props.value === selectedValue + ) + if (!selectedTabItem) { + // fail-safe or fail-fast? not sure what's best here + return null + } + return cloneElement(selectedTabItem) + } + return ( +
+ {childTabs.map((tabItem, i) => + cloneElement(tabItem, { + key: i, + hidden: tabItem.props.value !== selectedValue, + }) + )} +
+ ) +} + +function TabsComponent(props: TabsComponentProp): JSX.Element { + const tabs = useTabs(props) + return ( +
+ + +
+ ) +} + +function checkCodeTabs(props: TabsProps): boolean { + return props.groupId === "npm2yarn" || props.isCodeTabs +} + +export default function Tabs(props: TabsProps): JSX.Element { + const isBrowser = useIsBrowser() + + useEffect(() => { + if (!window.localStorage.getItem("docusaurus.tab.npm2yarn")) { + // set the default + window.localStorage.setItem("docusaurus.tab.npm2yarn", "yarn") + } + }, []) + + const isCodeTabs = checkCodeTabs(props) + + return ( +
+ + {sanitizeTabsChildren(props.children)} + +
+ ) +} diff --git a/www/apps/docs/src/types/global.d.ts b/www/apps/docs/src/types/global.d.ts new file mode 100644 index 0000000..3747492 --- /dev/null +++ b/www/apps/docs/src/types/global.d.ts @@ -0,0 +1,6 @@ +export declare global { + interface Window { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + analytics?: any + } +} diff --git a/www/apps/docs/src/types/index.d.ts b/www/apps/docs/src/types/index.d.ts new file mode 100644 index 0000000..0ea506c --- /dev/null +++ b/www/apps/docs/src/types/index.d.ts @@ -0,0 +1,217 @@ +declare module "@theme/CodeBlock" { + import type { Props as DocusaurusProps } from "@theme/CodeBlock" + + export interface Props extends DocusaurusProps { + readonly noReport?: boolean + readonly noCopy?: boolean + } +} + +declare module "@theme/CodeBlock/Content/String" { + import type { Props as CodeBlockProps } from "@theme/CodeBlock" + + export interface Props extends Omit { + readonly children: string + readonly noReport?: boolean + readonly noCopy?: boolean + } + + export default function CodeBlockStringContent(props: Props): JSX.Element +} + +declare module "@medusajs/docs" { + import type { ThemeConfig as DocusaurusThemeConfig } from "@docusaurus/preset-classic" + import type { DocusaurusConfig } from "@docusaurus/types" + import type { + PropSidebarItemCategory, + PropSidebarItemLink, + PropSidebarItemHtml, + } from "@docusaurus/plugin-content-docs" + import { BadgeProps, ButtonType, ButtonVariants } from "docs-ui" + import { IconProps } from "@medusajs/icons/dist/types" + import { DocContextValue as DocusaurusDocContextValue } from "@docusaurus/theme-common/internal" + import { ReactNode } from "react" + import { NavbarLogo } from "@docusaurus/theme-common" + import type { DocusaurusContext } from "@docusaurus/types" + + type ItemCustomProps = { + customProps?: { + themedImage: { + light: string + dark?: string + } + image?: string + icon?: React.FC + iconName?: string + description?: string + className?: string + isSoon?: boolean + badge: BadgeProps + html?: string + sidebar_icon?: string + sidebar_is_title?: boolean + sidebar_is_soon?: boolean + sidebar_is_group_headline?: boolean + sidebar_is_group_divider?: boolean + sidebar_is_divider_line?: boolean + sidebar_is_back_link?: boolean + sidebar_badge?: BadgeProps + category_id?: string + } + } + + export declare type ModifiedPropSidebarItemCategory = + PropSidebarItemCategory & ItemCustomProps + + export declare type ModifiedPropSidebarItemLink = PropSidebarItemLink & + ItemCustomProps + + export declare type ModifiedPropSidebarItemHtml = PropSidebarItemHtml & + ItemCustomProps + + export declare type ModifiedSidebarItem = + | ModifiedPropSidebarItemCategory + | ModifiedPropSidebarItemLink + | ModifiedPropSidebarItemHtml + + export declare type ModifiedDocCardBase = { + type: string + href: string + icon: ReactNode | string + title: string + description?: string + html?: string + containerClassName?: string + isSoon?: boolean + badge?: BadgeProps + children?: React.ReactNode + } + + export declare type ModifiedDocCardItemLink = { + type: "link" + } & ModifiedDocCardBase & + ModifiedPropSidebarItemLink + + export declare type ModifiedDocCardItemCategory = { + type: "category" + } & ModifiedDocCardBase & + ModifiedPropSidebarItemCategory + + export declare type ModifiedDocCard = + | ModifiedDocCardItemLink + | ModifiedDocCardItemCategory + + export declare type SocialLink = { + href: string + type: string + } + + export declare type NavbarActionBase = { + type: string + title?: string + icon?: string + Icon?: React.ReactElement + className?: string + label?: string + html?: string + } + + export declare type NavbarActionLink = NavbarActionBase & { + type: "link" + href: string + } + + export declare type NavbarActionButton = NavbarActionBase & { + type: "button" + variant?: ButtonVariants + buttonType?: ButtonType + href?: string + events?: { + onClick?: MouseEventHandler + onMouseEnter?: MouseEventHandler + onMouseLeave?: MouseEventHandler + onMouseOver?: MouseEventHandler + } + } + + export declare type NavbarAction = NavbarActionLink | NavbarActionButton + + export declare type OptionType = { + value: string + label: string + isAllOption?: boolean + } + + export declare type ThemeConfig = { + reportCodeLinkPrefix?: string + footerFeedback: { + event: string + } + socialLinks?: SocialLink[] + cloudinaryConfig?: { + cloudName?: string + flags?: string[] + resize?: { + action: string + width?: number + height?: number + aspectRatio?: string + } + roundCorners?: number + } + navbarActions: NavbarAction[] + // resolve type errors + prism: { + magicComments: MagicCommentConfig[] + } + mobileLogo: NavbarLogo + algoliaConfig?: { + apiKey: string + indexNames: { + docs: string + api: string + } + appId: string + filters: OptionType[] + defaultFilters: string[] + defaultFiltersByPath: { + path: string + filters: string[] + }[] + } + analytics?: { + apiKey: string + } + } & DocusaurusThemeConfig + + export declare type MedusaDocusaurusConfig = { + themeConfig: ThemeConfig + } & DocusaurusConfig + + export declare type DocContextValue = { + frontMatter: { + addHowToData?: boolean + badge?: { + variant: string + text: string + } + } + } & DocusaurusDocContextValue + + export declare type MedusaDocusaurusContext = DocusaurusContext & { + siteConfig: MedusaDocusaurusConfig + } + + export declare type Diagram2Code = { + diagram: string + code: string + } + + export declare type Diagram2CodeSpec = { + [k: string]: Diagram2Code + } + + export declare type Diagram2CodeSpecs = { + [k: string]: Diagram2CodeSpec + } +} diff --git a/www/apps/docs/src/utils/createLatestReleaseRedirects.js b/www/apps/docs/src/utils/createLatestReleaseRedirects.js new file mode 100644 index 0000000..c5bd2fe --- /dev/null +++ b/www/apps/docs/src/utils/createLatestReleaseRedirects.js @@ -0,0 +1,63 @@ +const fetch = require('node-fetch'); + +/** + * A map of architectures to their corresponding file names and extensions. + * This makes it easy to add or change architectures in the future. + */ +const ARCHITECTURES = [ + { from: '/amd64', os: 'linux', arch: 'amd64', ext: 'tar.gz' }, + { from: '/arm64', os: 'linux', arch: 'arm64', ext: 'tar.gz' }, + { from: '/windows', os: 'windows', arch: 'amd64', ext: 'zip' }, + // Add other architectures here as needed +]; + +/** + * This function runs at build time to dynamically create redirects. + * It fetches the latest release tag from GitHub and constructs the download URLs. + */ +async function createLatestReleaseRedirects(ex) { + console.log('Fetching latest release tag from GitHub...'); + try { + // We use a HEAD request because we only need the final redirected URL, not the page content. + // This is much faster and more efficient. + const response = await fetch('https://github.com/fentas/b/releases/latest', { + method: 'HEAD', + }); + + // The 'response.url' property will contain the URL after all redirects. + // e.g., 'https://github.com/fentas/b/releases/tag/v2.3.0' + const latestUrl = response.url; + + // Extract the tag (e.g., 'v2.3.0') from the URL. + const tag = latestUrl.split('/').pop(); + + if (!tag || !tag.startsWith('v')) { + console.error(`Error: Could not parse a valid release tag from URL: ${latestUrl}`); + // Return an empty array to prevent build failure. + return []; + } + + console.log(`Successfully fetched latest release tag: ${tag}`); + + // Construct the base URL for downloads for the latest tag. + const downloadBaseUrl = `https://github.com/fentas/b/releases/download/${tag}`; + + // Create a redirect object for each architecture. + const redirects = ARCHITECTURES.map(({ from, os, arch, ext }) => { + return { + from: from, + to: `${downloadBaseUrl}/b-${os}-${arch}.${ext}`, + }; + }); + + console.log('Generated redirects:', redirects); + return redirects; + + } catch (error) { + console.error('Failed to fetch latest release from GitHub.', error); + // Return an empty array on error to ensure the Docusaurus build can complete. + return []; + } +} + +module.exports = createLatestReleaseRedirects \ No newline at end of file diff --git a/www/apps/docs/src/utils/decode-str.ts b/www/apps/docs/src/utils/decode-str.ts new file mode 100644 index 0000000..20e9cfe --- /dev/null +++ b/www/apps/docs/src/utils/decode-str.ts @@ -0,0 +1,8 @@ +export default function decodeStr(str: string) { + return str + .replaceAll("<", "<") + .replaceAll("{", "{") + .replaceAll("}", "}") + .replaceAll(">", ">") + .replaceAll("\\|", "|") +} diff --git a/www/apps/docs/src/utils/exclude-sidebar-results.js b/www/apps/docs/src/utils/exclude-sidebar-results.js new file mode 100644 index 0000000..4925583 --- /dev/null +++ b/www/apps/docs/src/utils/exclude-sidebar-results.js @@ -0,0 +1,17 @@ +function excludeSidebarResults(sidebarItems, categoryItem) { + const results = [] + sidebarItems.forEach((item) => { + if (item.type === "category") { + results.push({ + ...item, + items: excludeSidebarResults(item.items, categoryItem), + }) + } else if (!item.customProps?.exclude_from_auto_sidebar) { + return results.push(item) + } + }) + + return results +} + +module.exports = excludeSidebarResults diff --git a/www/apps/docs/src/utils/filter-list-items.ts b/www/apps/docs/src/utils/filter-list-items.ts new file mode 100644 index 0000000..f5d6407 --- /dev/null +++ b/www/apps/docs/src/utils/filter-list-items.ts @@ -0,0 +1,33 @@ +import { + PropSidebarItem, + PropSidebarItemLink, +} from "@docusaurus/plugin-content-docs" + +export default function filterListItems( + items: PropSidebarItemLink[], + pathPattern: string | RegExp +): PropSidebarItemLink[] { + if (!items.length) { + return items + } + + const pattern = new RegExp(pathPattern) + + return items.filter((item: PropSidebarItemLink) => pattern.test(item.href)) +} + +/** + * Flatting a sidebar list moving items from category + * to links + */ +export function flattenList(items: PropSidebarItem[]): PropSidebarItem[] { + const newItems = items.map((item: PropSidebarItem) => { + if (item.type !== "category") { + return item + } + + return item.items + }) + + return newItems.flat() +} diff --git a/www/apps/docs/src/utils/get-first-category-item.ts b/www/apps/docs/src/utils/get-first-category-item.ts new file mode 100644 index 0000000..4191064 --- /dev/null +++ b/www/apps/docs/src/utils/get-first-category-item.ts @@ -0,0 +1,25 @@ +import { + findSidebarCategory, + useDocsSidebar, +} from "@docusaurus/theme-common/internal" +import { PropSidebarItem } from "@docusaurus/plugin-content-docs" + +type Options = { + categoryLabel?: string + categoryCustomId?: string +} + +export function getCategoryItems({ categoryLabel, categoryCustomId }: Options) { + return findSidebarCategory( + useDocsSidebar().items, + (item) => + item.label === categoryLabel || + item.customProps.category_id === categoryCustomId + )?.items +} + +export default function getFirstCategoryItem( + options: Options +): PropSidebarItem | undefined { + return getCategoryItems(options)?.[0] +} diff --git a/www/apps/docs/src/utils/get-os-shortcut.ts b/www/apps/docs/src/utils/get-os-shortcut.ts new file mode 100644 index 0000000..7229c8b --- /dev/null +++ b/www/apps/docs/src/utils/get-os-shortcut.ts @@ -0,0 +1,8 @@ +export default function getOsShortcut() { + const isMacOs = + typeof navigator !== "undefined" + ? navigator.userAgent.toLowerCase().indexOf("mac") !== 0 + : true + + return isMacOs ? "⌘" : "Ctrl" +} diff --git a/www/apps/docs/src/utils/glossary.ts b/www/apps/docs/src/utils/glossary.ts new file mode 100644 index 0000000..8e2985e --- /dev/null +++ b/www/apps/docs/src/utils/glossary.ts @@ -0,0 +1,17 @@ +export type GlossaryType = { + matchTextRegex: RegExp + ignoreTextRegex?: RegExp + title: string + content: string + referenceLink: string +} + +const glossary: GlossaryType[] = [] + +export const getGlossary = () => [...glossary] + +export const getGlossaryByPath = (path: string): GlossaryType | undefined => { + return glossary.find( + (g) => g.matchTextRegex.test(path) && !g.ignoreTextRegex?.test(path) + ) +} diff --git a/www/apps/docs/src/utils/is-in-view.ts b/www/apps/docs/src/utils/is-in-view.ts new file mode 100644 index 0000000..67649a3 --- /dev/null +++ b/www/apps/docs/src/utils/is-in-view.ts @@ -0,0 +1,10 @@ +export default function isInView(element: Element): boolean { + const rect = element.getBoundingClientRect() + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= + (window.innerHeight || document.documentElement.clientHeight) && + rect.right <= (window.innerWidth || document.documentElement.clientWidth) + ) +} diff --git a/www/apps/docs/src/utils/reverse-sidebar.js b/www/apps/docs/src/utils/reverse-sidebar.js new file mode 100644 index 0000000..4d3b6c7 --- /dev/null +++ b/www/apps/docs/src/utils/reverse-sidebar.js @@ -0,0 +1,27 @@ +function reverseSidebarItems(sidebarItems, categoryItem) { + let result = sidebarItems + if (categoryItem.customProps?.reverse) { + // Reverse items in categories + result = result.map((item) => { + if (item.type === "category") { + return { ...item, items: reverseSidebarItems(item.items, categoryItem) } + } + return item + }) + // Reverse items at current level + // use localeCompare since the reverse array method doesn't account for + // numeric strings + result.sort((a, b) => { + const aToCompare = a.id || a.href || a.value || "" + const bToCompare = b.id || b.href || b.value || "" + const comparison = aToCompare.localeCompare(bToCompare, undefined, { + numeric: true, + }) + + return comparison < 0 ? 1 : comparison > 0 ? -1 : 0 + }) + } + return result +} + +export default reverseSidebarItems diff --git a/www/apps/docs/src/utils/specs.ts b/www/apps/docs/src/utils/specs.ts new file mode 100644 index 0000000..c148667 --- /dev/null +++ b/www/apps/docs/src/utils/specs.ts @@ -0,0 +1 @@ +export const specs = {} diff --git a/www/apps/docs/static/.nojekyll b/www/apps/docs/static/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/www/apps/docs/static/android-chrome-192x192.png b/www/apps/docs/static/android-chrome-192x192.png new file mode 100644 index 0000000..96eb6c2 Binary files /dev/null and b/www/apps/docs/static/android-chrome-192x192.png differ diff --git a/www/apps/docs/static/android-chrome-512x512.png b/www/apps/docs/static/android-chrome-512x512.png new file mode 100644 index 0000000..f9d9166 Binary files /dev/null and b/www/apps/docs/static/android-chrome-512x512.png differ diff --git a/www/apps/docs/static/apple-touch-icon.png b/www/apps/docs/static/apple-touch-icon.png new file mode 100644 index 0000000..f8bcfc2 Binary files /dev/null and b/www/apps/docs/static/apple-touch-icon.png differ diff --git a/www/apps/docs/static/favicon.ico b/www/apps/docs/static/favicon.ico new file mode 100644 index 0000000..d636b88 Binary files /dev/null and b/www/apps/docs/static/favicon.ico differ diff --git a/www/apps/docs/static/icon.svg b/www/apps/docs/static/icon.svg new file mode 100644 index 0000000..b6a3820 --- /dev/null +++ b/www/apps/docs/static/icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/www/apps/docs/static/img/.bak/admin-icon-dark.svg b/www/apps/docs/static/img/.bak/admin-icon-dark.svg new file mode 100644 index 0000000..b8b63d4 --- /dev/null +++ b/www/apps/docs/static/img/.bak/admin-icon-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/www/apps/docs/static/img/.bak/admin-icon.svg b/www/apps/docs/static/img/.bak/admin-icon.svg new file mode 100644 index 0000000..48a2194 --- /dev/null +++ b/www/apps/docs/static/img/.bak/admin-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/www/apps/docs/static/img/.bak/announcement-bg.svg b/www/apps/docs/static/img/.bak/announcement-bg.svg new file mode 100644 index 0000000..e74f491 --- /dev/null +++ b/www/apps/docs/static/img/.bak/announcement-bg.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/www/apps/docs/static/img/.bak/bell-dark.png b/www/apps/docs/static/img/.bak/bell-dark.png new file mode 100644 index 0000000..2ec20b3 Binary files /dev/null and b/www/apps/docs/static/img/.bak/bell-dark.png differ diff --git a/www/apps/docs/static/img/.bak/cart-icon-dark.svg b/www/apps/docs/static/img/.bak/cart-icon-dark.svg new file mode 100644 index 0000000..7c2ef9b --- /dev/null +++ b/www/apps/docs/static/img/.bak/cart-icon-dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/www/apps/docs/static/img/.bak/cart-icon.svg b/www/apps/docs/static/img/.bak/cart-icon.svg new file mode 100644 index 0000000..98ac450 --- /dev/null +++ b/www/apps/docs/static/img/.bak/cart-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/www/apps/docs/static/img/.bak/dev-env-icon-dark.svg b/www/apps/docs/static/img/.bak/dev-env-icon-dark.svg new file mode 100644 index 0000000..caad3c7 --- /dev/null +++ b/www/apps/docs/static/img/.bak/dev-env-icon-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/www/apps/docs/static/img/.bak/dev-env-icon.svg b/www/apps/docs/static/img/.bak/dev-env-icon.svg new file mode 100644 index 0000000..149d9df --- /dev/null +++ b/www/apps/docs/static/img/.bak/dev-env-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/www/apps/docs/static/img/.bak/docs-meta.jpg b/www/apps/docs/static/img/.bak/docs-meta.jpg new file mode 100644 index 0000000..33e2011 Binary files /dev/null and b/www/apps/docs/static/img/.bak/docs-meta.jpg differ diff --git a/www/apps/docs/static/img/.bak/fast-delivery-icon-dark.svg b/www/apps/docs/static/img/.bak/fast-delivery-icon-dark.svg new file mode 100644 index 0000000..00da319 --- /dev/null +++ b/www/apps/docs/static/img/.bak/fast-delivery-icon-dark.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/www/apps/docs/static/img/.bak/fast-delivery-icon.svg b/www/apps/docs/static/img/.bak/fast-delivery-icon.svg new file mode 100644 index 0000000..b538501 --- /dev/null +++ b/www/apps/docs/static/img/.bak/fast-delivery-icon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/www/apps/docs/static/img/.bak/favicon.ico b/www/apps/docs/static/img/.bak/favicon.ico new file mode 100644 index 0000000..bebbf17 Binary files /dev/null and b/www/apps/docs/static/img/.bak/favicon.ico differ diff --git a/www/apps/docs/static/img/.bak/gatsby-icon-dark.svg b/www/apps/docs/static/img/.bak/gatsby-icon-dark.svg new file mode 100644 index 0000000..92a8c31 --- /dev/null +++ b/www/apps/docs/static/img/.bak/gatsby-icon-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/www/apps/docs/static/img/.bak/gatsby-icon.svg b/www/apps/docs/static/img/.bak/gatsby-icon.svg new file mode 100644 index 0000000..0017f78 --- /dev/null +++ b/www/apps/docs/static/img/.bak/gatsby-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/www/apps/docs/static/img/.bak/learning-path-img.png b/www/apps/docs/static/img/.bak/learning-path-img.png new file mode 100644 index 0000000..1536b31 Binary files /dev/null and b/www/apps/docs/static/img/.bak/learning-path-img.png differ diff --git a/www/apps/docs/static/img/.bak/light-beam.png b/www/apps/docs/static/img/.bak/light-beam.png new file mode 100644 index 0000000..e1c07d0 Binary files /dev/null and b/www/apps/docs/static/img/.bak/light-beam.png differ diff --git a/www/apps/docs/static/img/.bak/link-icon-light.svg b/www/apps/docs/static/img/.bak/link-icon-light.svg new file mode 100644 index 0000000..d8c0811 --- /dev/null +++ b/www/apps/docs/static/img/.bak/link-icon-light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/www/apps/docs/static/img/.bak/link-icon.svg b/www/apps/docs/static/img/.bak/link-icon.svg new file mode 100644 index 0000000..1a29eba --- /dev/null +++ b/www/apps/docs/static/img/.bak/link-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/www/apps/docs/static/img/.bak/logo-icon-dark.png b/www/apps/docs/static/img/.bak/logo-icon-dark.png new file mode 100644 index 0000000..1d155bd Binary files /dev/null and b/www/apps/docs/static/img/.bak/logo-icon-dark.png differ diff --git a/www/apps/docs/static/img/.bak/logo-icon.png b/www/apps/docs/static/img/.bak/logo-icon.png new file mode 100644 index 0000000..40efd0a Binary files /dev/null and b/www/apps/docs/static/img/.bak/logo-icon.png differ diff --git a/www/apps/docs/static/img/.bak/logo-mobile-dark.png b/www/apps/docs/static/img/.bak/logo-mobile-dark.png new file mode 100644 index 0000000..7355634 Binary files /dev/null and b/www/apps/docs/static/img/.bak/logo-mobile-dark.png differ diff --git a/www/apps/docs/static/img/.bak/logo-mobile.png b/www/apps/docs/static/img/.bak/logo-mobile.png new file mode 100644 index 0000000..4dea652 Binary files /dev/null and b/www/apps/docs/static/img/.bak/logo-mobile.png differ diff --git a/www/apps/docs/static/img/.bak/magnifying-glass-dark.svg b/www/apps/docs/static/img/.bak/magnifying-glass-dark.svg new file mode 100644 index 0000000..ae81c80 --- /dev/null +++ b/www/apps/docs/static/img/.bak/magnifying-glass-dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/www/apps/docs/static/img/.bak/magnifying-glass.svg b/www/apps/docs/static/img/.bak/magnifying-glass.svg new file mode 100644 index 0000000..c46314a --- /dev/null +++ b/www/apps/docs/static/img/.bak/magnifying-glass.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/www/apps/docs/static/img/.bak/nextjs-icon-dark.svg b/www/apps/docs/static/img/.bak/nextjs-icon-dark.svg new file mode 100644 index 0000000..ce51ce8 --- /dev/null +++ b/www/apps/docs/static/img/.bak/nextjs-icon-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/www/apps/docs/static/img/.bak/nextjs-icon.svg b/www/apps/docs/static/img/.bak/nextjs-icon.svg new file mode 100644 index 0000000..2492f21 --- /dev/null +++ b/www/apps/docs/static/img/.bak/nextjs-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/www/apps/docs/static/img/.bak/payment-accepted-icon-dark.svg b/www/apps/docs/static/img/.bak/payment-accepted-icon-dark.svg new file mode 100644 index 0000000..2cad4d9 --- /dev/null +++ b/www/apps/docs/static/img/.bak/payment-accepted-icon-dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/www/apps/docs/static/img/.bak/payment-accepted-icon.svg b/www/apps/docs/static/img/.bak/payment-accepted-icon.svg new file mode 100644 index 0000000..279d781 --- /dev/null +++ b/www/apps/docs/static/img/.bak/payment-accepted-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/www/apps/docs/static/img/.bak/search-hit-arrow-light.svg b/www/apps/docs/static/img/.bak/search-hit-arrow-light.svg new file mode 100644 index 0000000..8b119c3 --- /dev/null +++ b/www/apps/docs/static/img/.bak/search-hit-arrow-light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/www/apps/docs/static/img/.bak/search-hit-arrow.svg b/www/apps/docs/static/img/.bak/search-hit-arrow.svg new file mode 100644 index 0000000..5738538 --- /dev/null +++ b/www/apps/docs/static/img/.bak/search-hit-arrow.svg @@ -0,0 +1,4 @@ + + + + diff --git a/www/apps/docs/static/img/.bak/search-hit-light.svg b/www/apps/docs/static/img/.bak/search-hit-light.svg new file mode 100644 index 0000000..0c8b06d --- /dev/null +++ b/www/apps/docs/static/img/.bak/search-hit-light.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/www/apps/docs/static/img/.bak/search-hit.png b/www/apps/docs/static/img/.bak/search-hit.png new file mode 100644 index 0000000..3099be9 Binary files /dev/null and b/www/apps/docs/static/img/.bak/search-hit.png differ diff --git a/www/apps/docs/static/img/.bak/search-hit.svg b/www/apps/docs/static/img/.bak/search-hit.svg new file mode 100644 index 0000000..c7ee94e --- /dev/null +++ b/www/apps/docs/static/img/.bak/search-hit.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/www/apps/docs/static/img/.bak/search-no-result-light.svg b/www/apps/docs/static/img/.bak/search-no-result-light.svg new file mode 100644 index 0000000..5de948e --- /dev/null +++ b/www/apps/docs/static/img/.bak/search-no-result-light.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/www/apps/docs/static/img/.bak/search-no-result.svg b/www/apps/docs/static/img/.bak/search-no-result.svg new file mode 100644 index 0000000..ed49a6f --- /dev/null +++ b/www/apps/docs/static/img/.bak/search-no-result.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/www/apps/docs/static/img/.bak/server-icon-dark.svg b/www/apps/docs/static/img/.bak/server-icon-dark.svg new file mode 100644 index 0000000..23680ed --- /dev/null +++ b/www/apps/docs/static/img/.bak/server-icon-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/www/apps/docs/static/img/.bak/server-icon.svg b/www/apps/docs/static/img/.bak/server-icon.svg new file mode 100644 index 0000000..2a7cf43 --- /dev/null +++ b/www/apps/docs/static/img/.bak/server-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/www/apps/docs/static/img/.bak/stardust.png b/www/apps/docs/static/img/.bak/stardust.png new file mode 100644 index 0000000..a3fd965 Binary files /dev/null and b/www/apps/docs/static/img/.bak/stardust.png differ diff --git a/www/apps/docs/static/img/.bak/stripe-icon-dark.svg b/www/apps/docs/static/img/.bak/stripe-icon-dark.svg new file mode 100644 index 0000000..ff2c081 --- /dev/null +++ b/www/apps/docs/static/img/.bak/stripe-icon-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/www/apps/docs/static/img/.bak/stripe-icon.svg b/www/apps/docs/static/img/.bak/stripe-icon.svg new file mode 100644 index 0000000..1d5dd40 --- /dev/null +++ b/www/apps/docs/static/img/.bak/stripe-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/www/apps/docs/static/img/.bak/tee-icon-dark.svg b/www/apps/docs/static/img/.bak/tee-icon-dark.svg new file mode 100644 index 0000000..3537f92 --- /dev/null +++ b/www/apps/docs/static/img/.bak/tee-icon-dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/www/apps/docs/static/img/.bak/tee-icon.svg b/www/apps/docs/static/img/.bak/tee-icon.svg new file mode 100644 index 0000000..fc03a4c --- /dev/null +++ b/www/apps/docs/static/img/.bak/tee-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/www/apps/docs/static/img/.bak/terminal-icon-dark.svg b/www/apps/docs/static/img/.bak/terminal-icon-dark.svg new file mode 100644 index 0000000..f27e06d --- /dev/null +++ b/www/apps/docs/static/img/.bak/terminal-icon-dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/www/apps/docs/static/img/.bak/terminal-icon.svg b/www/apps/docs/static/img/.bak/terminal-icon.svg new file mode 100644 index 0000000..7c9051d --- /dev/null +++ b/www/apps/docs/static/img/.bak/terminal-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/www/apps/docs/static/img/.bak/user-guide-dark.svg b/www/apps/docs/static/img/.bak/user-guide-dark.svg new file mode 100644 index 0000000..40771ae --- /dev/null +++ b/www/apps/docs/static/img/.bak/user-guide-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/www/apps/docs/static/img/.bak/user-guide.svg b/www/apps/docs/static/img/.bak/user-guide.svg new file mode 100644 index 0000000..7462995 --- /dev/null +++ b/www/apps/docs/static/img/.bak/user-guide.svg @@ -0,0 +1,3 @@ + + + diff --git a/www/apps/docs/static/img/diagrams-bg.png b/www/apps/docs/static/img/diagrams-bg.png new file mode 100644 index 0000000..33d55ab Binary files /dev/null and b/www/apps/docs/static/img/diagrams-bg.png differ diff --git a/www/apps/docs/static/img/logo-icon-dark.svg b/www/apps/docs/static/img/logo-icon-dark.svg new file mode 100644 index 0000000..b6a3820 --- /dev/null +++ b/www/apps/docs/static/img/logo-icon-dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/www/apps/docs/static/img/logo-icon.svg b/www/apps/docs/static/img/logo-icon.svg new file mode 100644 index 0000000..6788bea --- /dev/null +++ b/www/apps/docs/static/img/logo-icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/www/apps/docs/static/img/side-menu-light.svg b/www/apps/docs/static/img/side-menu-light.svg new file mode 100644 index 0000000..b204a38 --- /dev/null +++ b/www/apps/docs/static/img/side-menu-light.svg @@ -0,0 +1,3 @@ + + + diff --git a/www/apps/docs/static/img/side-menu.svg b/www/apps/docs/static/img/side-menu.svg new file mode 100644 index 0000000..1f785a5 --- /dev/null +++ b/www/apps/docs/static/img/side-menu.svg @@ -0,0 +1,3 @@ + + + diff --git a/www/apps/docs/static/img/small-squares-bg-light.svg b/www/apps/docs/static/img/small-squares-bg-light.svg new file mode 100644 index 0000000..b857078 --- /dev/null +++ b/www/apps/docs/static/img/small-squares-bg-light.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/www/apps/docs/static/img/small-squares-bg.svg b/www/apps/docs/static/img/small-squares-bg.svg new file mode 100644 index 0000000..5e7d44a --- /dev/null +++ b/www/apps/docs/static/img/small-squares-bg.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/www/apps/docs/static/img/squares-bg-light.svg b/www/apps/docs/static/img/squares-bg-light.svg new file mode 100644 index 0000000..6277534 --- /dev/null +++ b/www/apps/docs/static/img/squares-bg-light.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/www/apps/docs/static/img/squares-bg.svg b/www/apps/docs/static/img/squares-bg.svg new file mode 100644 index 0000000..3713461 --- /dev/null +++ b/www/apps/docs/static/img/squares-bg.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/www/apps/docs/static/manifest.webmanifest b/www/apps/docs/static/manifest.webmanifest new file mode 100644 index 0000000..19e8c3f --- /dev/null +++ b/www/apps/docs/static/manifest.webmanifest @@ -0,0 +1,14 @@ +{ + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/www/apps/docs/static/robots.txt b/www/apps/docs/static/robots.txt new file mode 100644 index 0000000..1dae859 --- /dev/null +++ b/www/apps/docs/static/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: http://arg.sh/sitemap.xml \ No newline at end of file diff --git a/www/apps/docs/tailwind.config.js b/www/apps/docs/tailwind.config.js new file mode 100644 index 0000000..2867fa0 --- /dev/null +++ b/www/apps/docs/tailwind.config.js @@ -0,0 +1,30 @@ +import coreConfig from "tailwind" + +/** @type {import('tailwindcss').Config} */ +module.exports = { + ...coreConfig, + // prefix: "tw-", + corePlugins: { + preflight: false, // disable Tailwind's reset + }, + content: [ + ...coreConfig.content, + "./src/**/*.{js,jsx,ts,tsx}", + "./content/**/*.{mdx,md}", + "docusaurus.config.js", + "sidebars.js", + ], + theme: { + ...coreConfig.theme, + extend: { + ...coreConfig.theme.extend, + screens: { + ...coreConfig.theme.screens, + xs: "576px", + lg: "996px", + xl: "1419px", + xxl: "1440px", + }, + }, + }, +} diff --git a/www/ignore-build-script.sh b/www/ignore-build-script.sh new file mode 100644 index 0000000..f47d02d --- /dev/null +++ b/www/ignore-build-script.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +echo "VERCEL_ENV: $VERCEL_ENV" +echo "VERCEL_GIT_COMMIT_REF: $VERCEL_GIT_COMMIT_REF" + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +echo "SCRIPT_DIR: $SCRIPT_DIR" + +$(git diff HEAD^ HEAD --quiet ${SCRIPT_DIR}) +diffResult=$? + +echo "DIFF RESULT: $diffResult" + +if [[ ("$VERCEL_ENV" == "production" && $diffResult -eq 1) || "$VERCEL_GIT_COMMIT_REF" = "docs/"* ]] ; then + # Proceed with the build + echo "✅ - Build can proceed" + exit 1; +else + # Don't build + echo "🛑 - Build cancelled: Conditions don't match" + exit 0; +fi \ No newline at end of file diff --git a/www/package.json b/www/package.json new file mode 100644 index 0000000..cec223d --- /dev/null +++ b/www/package.json @@ -0,0 +1,34 @@ +{ + "name": "docs-root", + "private": true, + "workspaces": { + "packages": [ + "apps/*", + "packages/*" + ] + }, + "scripts": { + "build": "turbo run build", + "build:docs": "turbo run build --filter=docs", + "start": "turbo run start:monorepo", + "dev": "turbo run dev:monorepo", + "lint": "turbo run lint", + "lint:content": "turbo run lint:content" + }, + "dependencies": { + "autoprefixer": "10.4.18", + "eslint": "^8.57.0", + "postcss": "8.4.35", + "prettier": "^3.2.5", + "tailwindcss": "3.4.1", + "tsconfig": "*", + "turbo": "latest" + }, + "engines": { + "node": ">=18.17.0" + }, + "resolutions": { + "string-width": "4.2.3", + "jackspeak": "2.1.1" + } +} diff --git a/www/packages/docs-ui/.eslintrc.js b/www/packages/docs-ui/.eslintrc.js new file mode 100644 index 0000000..c95714e --- /dev/null +++ b/www/packages/docs-ui/.eslintrc.js @@ -0,0 +1,10 @@ +module.exports = { + root: true, + extends: [ + "docs" + ], + parserOptions: { + project: true, + tsconfigRootDir: __dirname, + }, +} diff --git a/www/packages/docs-ui/.gitignore b/www/packages/docs-ui/.gitignore new file mode 100644 index 0000000..76add87 --- /dev/null +++ b/www/packages/docs-ui/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/www/packages/docs-ui/package.json b/www/packages/docs-ui/package.json new file mode 100644 index 0000000..20ea400 --- /dev/null +++ b/www/packages/docs-ui/package.json @@ -0,0 +1,74 @@ +{ + "name": "docs-ui", + "version": "0.0.0", + "private": true, + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "main": "./dist/cjs/index.js", + "types": "./dist/cjs/index.d.ts", + "module": "./dist/esm/index.js", + "exports": { + ".": { + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + }, + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + } + } + }, + "sideEffects": false, + "files": [ + "dist/**" + ], + "scripts": { + "build": "yarn clean && yarn build:js:cjs && yarn build:js:esm", + "build:js:cjs": "tsc --project tsconfig.cjs.json && tsc-alias -p tsconfig.cjs.json", + "build:js:esm": "tsc --project tsconfig.esm.json && tsc-alias -p tsconfig.esm.json", + "clean": "rimraf dist", + "dev": "yarn build:js:cjs && yarn build:js:esm" + }, + "devDependencies": { + "@types/react": "^18.2.65", + "@types/react-dom": "^18.2.21", + "@types/react-google-recaptcha": "^2.1.9", + "clsx": "^2.1.0", + "cpy-cli": "^5.0.0", + "eslint-config-docs": "*", + "next": "latest", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "rimraf": "^5.0.5", + "tailwind": "*", + "tailwindcss": "^3.4.1", + "tsc-alias": "^1.8.8", + "tsup": "^8.0.2", + "typescript": "^5.4.2" + }, + "peerDependencies": { + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "next": "*", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "dependencies": { + "@medusajs/icons": "^1.2.0", + "@medusajs/ui": "^2.4.1", + "@octokit/request": "^8.1.1", + "@react-hook/resize-observer": "^1.2.6", + "@segment/analytics-next": "^1.66.0", + "algoliasearch": "^4.22.1", + "prism-react-renderer": "2.3.1", + "react-google-recaptcha": "^3.1.0", + "react-instantsearch": "^7.7.0", + "react-markdown": "^8.0.7", + "react-tooltip": "^5.26.3", + "react-transition-group": "^4.4.5", + "react-uuid": "^2.0.0" + } +} diff --git a/www/packages/docs-ui/postcss.config.js b/www/packages/docs-ui/postcss.config.js new file mode 100644 index 0000000..73368e6 --- /dev/null +++ b/www/packages/docs-ui/postcss.config.js @@ -0,0 +1 @@ +module.exports = require("tailwind/postcss.config") diff --git a/www/packages/docs-ui/src/components/Badge/index.tsx b/www/packages/docs-ui/src/components/Badge/index.tsx new file mode 100644 index 0000000..3a33e67 --- /dev/null +++ b/www/packages/docs-ui/src/components/Badge/index.tsx @@ -0,0 +1,41 @@ +import React from "react" +import clsx from "clsx" + +export type BadgeVariant = + | "purple" + | "orange" + | "green" + | "blue" + | "red" + | "neutral" + +export type BadgeProps = { + className?: string + variant: BadgeVariant +} & React.HTMLAttributes + +export const Badge = ({ className, variant, children }: BadgeProps) => { + return ( + + {children} + + ) +} diff --git a/www/packages/docs-ui/src/components/Bordered/index.tsx b/www/packages/docs-ui/src/components/Bordered/index.tsx new file mode 100644 index 0000000..ebe6723 --- /dev/null +++ b/www/packages/docs-ui/src/components/Bordered/index.tsx @@ -0,0 +1,21 @@ +import clsx from "clsx" +import React from "react" + +export type BorderedProps = { + wrapperClassName?: string +} & React.HTMLAttributes + +export const Bordered = ({ wrapperClassName, children }: BorderedProps) => { + return ( + + {children} + + ) +} diff --git a/www/packages/docs-ui/src/components/BorderedIcon/index.tsx b/www/packages/docs-ui/src/components/BorderedIcon/index.tsx new file mode 100644 index 0000000..ffd66a6 --- /dev/null +++ b/www/packages/docs-ui/src/components/BorderedIcon/index.tsx @@ -0,0 +1,51 @@ +import React from "react" +import { Bordered } from "@/components/Bordered" +import clsx from "clsx" +import { IconProps } from "@medusajs/icons/dist/types" + +export type BorderedIconProps = { + icon?: string + IconComponent?: React.FC | null + wrapperClassName?: string + iconWrapperClassName?: string + iconClassName?: string + iconColorClassName?: string +} & React.HTMLAttributes + +export const BorderedIcon = ({ + icon = "", + IconComponent = null, + wrapperClassName, + iconWrapperClassName, + iconClassName, + iconColorClassName = "", +}: BorderedIconProps) => { + return ( + + + {!IconComponent && ( + + )} + {IconComponent && ( + + )} + + + ) +} diff --git a/www/packages/docs-ui/src/components/Button/index.tsx b/www/packages/docs-ui/src/components/Button/index.tsx new file mode 100644 index 0000000..6977e09 --- /dev/null +++ b/www/packages/docs-ui/src/components/Button/index.tsx @@ -0,0 +1,79 @@ +import clsx from "clsx" +import React from "react" + +export type ButtonVariants = "primary" | "secondary" | "clear" + +export type ButtonType = "default" | "icon" + +export type ButtonProps = { + isSelected?: boolean + disabled?: boolean + variant?: ButtonVariants + className?: string + buttonType?: ButtonType + buttonRef?: React.LegacyRef +} & React.HTMLAttributes + +export const Button = ({ + className, + children, + variant = "primary", + buttonType = "default", + buttonRef, + ...props +}: ButtonProps) => { + const variantClasses = { + primary: [ + "py-[5px] px-docs_0.75 rounded-docs_sm cursor-pointer", + "bg-button-inverted bg-medusa-button-inverted dark:bg-button-inverted-dark", + "hover:bg-medusa-button-inverted-hover hover:bg-no-image hover:no-underline", + "active:bg-medusa-button-inverted-pressed active:bg-no-image", + "focus:bg-medusa-button-inverted-pressed focus:bg-no-image", + "shadow-button-colored active:shadow-button-colored-focused focus:shadow-button-colored-focused transition-shadow", + "dark:shadow-button-colored-dark dark:active:shadow-button-colored-focused-dark dark:focus:shadow-button-colored-focused-dark", + "disabled:!bg-no-image disabled:bg-medusa-bg-disabled", + "disabled:cursor-not-allowed disabled:border-medusa-border-base", + "text-compact-small-plus text-medusa-fg-on-inverted", + "[&_a]:text-medusa-fg-on-inverted", + "disabled:text-medusa-fg-disabled", + "[&_a]:disabled:text-medusa-fg-disabled", + "border border-medusa-border-loud", + "select-none", + ], + secondary: [ + "py-[5px] px-docs_0.75 rounded-docs_sm cursor-pointer", + "bg-button-neutral bg-medusa-button-neutral dark:bg-button-neutral-dark", + "hover:bg-medusa-button-neutral-hover hover:bg-no-image hover:no-underline", + "active:bg-medusa-button-neutral-pressed active:bg-no-image", + "focus:bg-medusa-button-neutral-pressed focus:bg-no-image", + "disabled:!bg-no-image disabled:bg-medusa-bg-disabled", + "disabled:cursor-not-allowed", + "border border-solid border-medusa-border-base", + "text-compact-small-plus text-medusa-fg-base", + "[&_a]:text-medusa-fg-base", + "shadow-button-neutral focus:shadow-button-neutral-focused active:shadow-button-neutral-focused transition-shadow", + "dark:shadow-button-neutral dark:focus:shadow-button-neutral-focused dark:active:shadow-button-neutral-focused", + "select-none", + ], + clear: [ + "bg-transparent shadow-none border-0 outline-none cursor-pointer text-fg-medusa-subtle", + ], + } + + return ( + + ) +} diff --git a/www/packages/docs-ui/src/components/Card/index.tsx b/www/packages/docs-ui/src/components/Card/index.tsx new file mode 100644 index 0000000..f59ccbe --- /dev/null +++ b/www/packages/docs-ui/src/components/Card/index.tsx @@ -0,0 +1,50 @@ +import React from "react" +import { ArrowUpRightOnBox } from "@medusajs/icons" +import clsx from "clsx" +import { Link } from "@/components" + +export type CardProps = { + icon?: React.ReactNode + title: string + text?: string + href?: string + className?: string +} + +export const Card = ({ icon, title, text, href, className }: CardProps) => { + return ( +
+ {icon} +
+
+ + {title} + + {text && ( + + {text} + + )} +
+ + {href && ( + <> + + + + )} +
+
+ ) +} diff --git a/www/packages/docs-ui/src/components/CodeBlock/index.tsx b/www/packages/docs-ui/src/components/CodeBlock/index.tsx new file mode 100644 index 0000000..1fc5dbc --- /dev/null +++ b/www/packages/docs-ui/src/components/CodeBlock/index.tsx @@ -0,0 +1,124 @@ +"use client" + +import React from "react" +import clsx from "clsx" +import { HighlightProps, Highlight, themes } from "prism-react-renderer" +import { CopyButton, useColorMode } from "docs-ui" +import { SquareTwoStackSolid } from "@medusajs/icons" + +export type CodeBlockProps = { + source: string + lang?: string + className?: string + collapsed?: boolean +} & Omit + +export const CodeBlock = ({ + source, + lang = "", + className, + collapsed = false, + ...rest +}: CodeBlockProps) => { + const { colorMode } = useColorMode() + + if (!source.length) { + return <> + } + + return ( +
+ + {({ + className: preClassName, + style, + tokens, + getLineProps, + getTokenProps, + }) => ( + <> +
+               1 && "pt-docs_1 pr-docs_1",
+                  tokens.length <= 1 && "!py-docs_0.5 px-docs_1"
+                )}
+              >
+                {tokens.map((line, i) => {
+                  const lineProps = getLineProps({ line, key: i })
+                  return (
+                    
+                      {tokens.length > 1 && (
+                        
+                          {i + 1}
+                        
+                      )}
+                      
+                        {line.map((token, key) => (
+                          
+                        ))}
+                      
+                    
+                  )
+                })}
+              
+            
+ + + )} +
+
+ ) +} diff --git a/www/packages/docs-ui/src/components/CodeMdx/index.tsx b/www/packages/docs-ui/src/components/CodeMdx/index.tsx new file mode 100644 index 0000000..e2101f8 --- /dev/null +++ b/www/packages/docs-ui/src/components/CodeMdx/index.tsx @@ -0,0 +1,29 @@ +import React from "react" +import { CodeBlock, InlineCode } from "@/components" + +export type CodeMdxProps = { + className?: string + children?: React.ReactNode +} + +// due to how mdx handles code blocks +// it is required that a code block specify a language +// to be considered a block. Otherwise, it will be +// considered as inline code +export const CodeMdx = ({ className, children }: CodeMdxProps) => { + if (!children) { + return <> + } + + const match = /language-(\w+)/.exec(className || "") + + const codeContent = Array.isArray(children) + ? (children[0] as string) + : (children as string) + + if (match) { + return + } + + return {codeContent} +} diff --git a/www/packages/docs-ui/src/components/CodeTabs/index.tsx b/www/packages/docs-ui/src/components/CodeTabs/index.tsx new file mode 100644 index 0000000..47b0ad4 --- /dev/null +++ b/www/packages/docs-ui/src/components/CodeTabs/index.tsx @@ -0,0 +1,129 @@ +"use client" + +import React, { useCallback, useEffect, useMemo, useRef } from "react" +import clsx from "clsx" +import { CodeBlock, CodeBlockProps } from "@/components" +import { useTabs, BaseTabType, useScrollPositionBlocker } from "@/hooks" + +export type TabType = { + code?: CodeBlockProps + codeBlock?: React.ReactNode +} & BaseTabType + +export type CodeTabsProps = { + tabs: TabType[] + className?: string + group?: string +} + +export const CodeTabs = ({ + tabs, + className, + group = "client", +}: CodeTabsProps) => { + const { selectedTab, changeSelectedTab } = useTabs({ + tabs, + group, + }) + const tabRefs: (HTMLButtonElement | null)[] = useMemo(() => [], []) + const codeTabSelectorRef = useRef(null) + const codeTabsWrapperRef = useRef(null) + const { blockElementScrollPositionUntilNextRender } = + useScrollPositionBlocker() + + const changeTabSelectorCoordinates = useCallback( + (selectedTabElm: HTMLElement) => { + if (!codeTabSelectorRef?.current || !codeTabsWrapperRef?.current) { + return + } + const selectedTabsCoordinates = selectedTabElm.getBoundingClientRect() + const tabsWrapperCoordinates = + codeTabsWrapperRef.current.getBoundingClientRect() + codeTabSelectorRef.current.style.left = `${ + selectedTabsCoordinates.left - tabsWrapperCoordinates.left + }px` + codeTabSelectorRef.current.style.width = `${selectedTabsCoordinates.width}px` + codeTabSelectorRef.current.style.height = `${selectedTabsCoordinates.height}px` + }, + [] + ) + + useEffect(() => { + if (codeTabSelectorRef?.current && tabRefs.length) { + const selectedTabElm = tabRefs.find( + (tab) => tab?.getAttribute("aria-selected") === "true" + ) + if (selectedTabElm) { + changeTabSelectorCoordinates( + selectedTabElm.parentElement || selectedTabElm + ) + } + } + }, [codeTabSelectorRef, tabRefs, changeTabSelectorCoordinates, selectedTab]) + + return ( +
+ +
    + {tabs.map((tab, index) => ( +
  • + +
  • + ))} +
+ <> + {selectedTab?.code && ( + + )} + {selectedTab?.codeBlock && <>{selectedTab.codeBlock}} + +
+ ) +} diff --git a/www/packages/docs-ui/src/components/CopyButton/index.tsx b/www/packages/docs-ui/src/components/CopyButton/index.tsx new file mode 100644 index 0000000..39274b7 --- /dev/null +++ b/www/packages/docs-ui/src/components/CopyButton/index.tsx @@ -0,0 +1,44 @@ +"use client" + +import React from "react" +import clsx from "clsx" +import { Tooltip } from "@/components" +import { useCopy } from "../../hooks" + +export type CopyButtonProps = { + text: string + buttonClassName?: string + tooltipClassName?: string + tooltipText?: string + onCopy?: (e: React.MouseEvent) => void +} & Omit, "onCopy"> + +export const CopyButton = ({ + text, + buttonClassName = "", + tooltipClassName = "", + tooltipText = "Copy to Clipboard", + children, + className, + onCopy, +}: CopyButtonProps) => { + const { isCopied, handleCopy } = useCopy(text) + + return ( + + { + onCopy?.(e) + handleCopy() + }} + > + {children} + + + ) +} diff --git a/www/packages/docs-ui/src/components/Details/Summary/index.tsx b/www/packages/docs-ui/src/components/Details/Summary/index.tsx new file mode 100644 index 0000000..226c096 --- /dev/null +++ b/www/packages/docs-ui/src/components/Details/Summary/index.tsx @@ -0,0 +1,71 @@ +import React from "react" +import clsx from "clsx" +import { PlusMini } from "@medusajs/icons" + +export type DetailsSummaryProps = { + title?: React.ReactNode + subtitle?: React.ReactNode + badge?: React.ReactNode + expandable?: boolean + open?: boolean + className?: string + titleClassName?: string + hideExpandableIcon?: boolean + summaryRef?: React.LegacyRef +} & Omit, "title"> + +export const DetailsSummary = ({ + title, + subtitle, + children, + badge, + expandable = true, + open = false, + className, + titleClassName, + hideExpandableIcon = false, + summaryRef, + ...rest +}: DetailsSummaryProps) => { + return ( + + + + {title || children} + + {subtitle && ( + + {subtitle} + + )} + + {(badge || expandable) && ( + + {badge} + {expandable && !hideExpandableIcon && ( + + )} + + )} + + ) +} diff --git a/www/packages/docs-ui/src/components/Details/index.tsx b/www/packages/docs-ui/src/components/Details/index.tsx new file mode 100644 index 0000000..cbeec2a --- /dev/null +++ b/www/packages/docs-ui/src/components/Details/index.tsx @@ -0,0 +1,131 @@ +"use client" + +import React, { Suspense, cloneElement, useRef, useState } from "react" +import { Loading } from "@/components/Loading" +import clsx from "clsx" +import { CSSTransition } from "react-transition-group" +import { DetailsSummary } from "./Summary" + +export type DetailsProps = { + openInitial?: boolean + summaryContent?: React.ReactNode + summaryElm?: React.ReactNode + heightAnimation?: boolean +} & React.HTMLAttributes + +export const Details = ({ + openInitial = false, + summaryContent, + summaryElm, + children, + heightAnimation = false, + ...props +}: DetailsProps) => { + const [open, setOpen] = useState(openInitial) + const [showContent, setShowContent] = useState(openInitial) + const ref = useRef(null) + + const handleToggle = (e: React.MouseEvent) => { + const targetElm = e.target as HTMLElement + if (targetElm.tagName.toLowerCase() === "a") { + window.location.href = + targetElm.getAttribute("href") || window.location.href + return + } + if (targetElm.tagName.toLowerCase() === "code") { + return + } + if (open) { + setShowContent(false) + } else { + setOpen(true) + setShowContent(true) + } + } + + return ( +
{ + event.preventDefault() + }} + onToggle={(event) => { + // this is to avoid event propagation + // when details are nested, which is a bug + // in react. Learn more here: + // https://github.com/facebook/react/issues/22718 + event.stopPropagation() + }} + className={clsx( + "border-medusa-border-base border-y border-solid border-x-0", + "overflow-hidden [&>summary]:relative", + props.className + )} + > + {summaryContent && ( + + )} + {summaryElm && + cloneElement(summaryElm as React.ReactElement, { + open, + onClick: handleToggle, + })} + { + if (heightAnimation) { + node.classList.add("transition-[height]") + node.style.height = `0px` + } else { + node.classList.add( + "!mb-docs_2", + "!mt-0", + "translate-y-docs_1", + "transition-transform" + ) + } + }} + onEntering={(node: HTMLElement) => { + if (heightAnimation) { + node.style.height = `${node.scrollHeight}px` + } + }} + onEntered={(node: HTMLElement) => { + if (heightAnimation) { + node.style.height = `auto` + } + }} + onExit={(node: HTMLElement) => { + if (heightAnimation) { + node.style.height = `${node.scrollHeight}px` + } else { + node.classList.add("transition-transform", "!-translate-y-docs_1") + setTimeout(() => { + setOpen(false) + }, 100) + } + }} + onExiting={(node: HTMLElement) => { + if (heightAnimation) { + node.style.height = `0px` + setTimeout(() => { + setOpen(false) + }, 100) + } + }} + > + }> + {children} + + +
+ ) +} diff --git a/www/packages/docs-ui/src/components/ExpandableNotice/index.tsx b/www/packages/docs-ui/src/components/ExpandableNotice/index.tsx new file mode 100644 index 0000000..89f53ca --- /dev/null +++ b/www/packages/docs-ui/src/components/ExpandableNotice/index.tsx @@ -0,0 +1,34 @@ +import React from "react" +import { Badge, Link, Tooltip } from "@/components" + +export type ExpandableNoticeProps = { + type: "request" | "method" + link: string + badgeContent?: React.ReactNode + badgeClassName?: string +} + +export const ExpandableNotice = ({ + type = "request", + link, + badgeContent = "expandable", + badgeClassName, +}: ExpandableNoticeProps) => { + return ( + + If this {type} accepts an expand{" "} + {type === "request" ? "parameter" : "property or option"}, +
this field can be expanded into an + object. + + } + clickable + > + + {badgeContent} + +
+ ) +} diff --git a/www/packages/docs-ui/src/components/FeatureFlagNotice/index.tsx b/www/packages/docs-ui/src/components/FeatureFlagNotice/index.tsx new file mode 100644 index 0000000..d2ffaa0 --- /dev/null +++ b/www/packages/docs-ui/src/components/FeatureFlagNotice/index.tsx @@ -0,0 +1,40 @@ +import React from "react" +import { Badge, Link, Tooltip } from "@/components" + +export type FeatureFlagNoticeProps = { + featureFlag: string + type?: "endpoint" | "parameter" + tooltipTextClassName?: string + badgeClassName?: string + badgeContent?: React.ReactNode +} + +export const FeatureFlagNotice = ({ + featureFlag, + type = "endpoint", + tooltipTextClassName, + badgeClassName, + badgeContent = "feature flag", +}: FeatureFlagNoticeProps) => { + return ( + + To use this {type}, make sure to +
+ + enable its feature flag: {featureFlag} + + + } + clickable + > + + {badgeContent} + +
+ ) +} diff --git a/www/packages/docs-ui/src/components/Feedback/Solutions/index.tsx b/www/packages/docs-ui/src/components/Feedback/Solutions/index.tsx new file mode 100644 index 0000000..b451215 --- /dev/null +++ b/www/packages/docs-ui/src/components/Feedback/Solutions/index.tsx @@ -0,0 +1,92 @@ +"use client" + +import React, { useEffect, useState } from "react" +import { request } from "@octokit/request" +import { Link } from "@/components/Link" + +export type SolutionsProps = { + feedback: boolean + message?: string +} + +export type GitHubSearchItem = { + url: string + html_url: string + title: string + [key: string]: unknown +} + +export const Solutions = ({ feedback, message }: SolutionsProps) => { + const [possibleSolutionsQuery, setPossibleSolutionsQuery] = + useState("") + const [possibleSolutions, setPossibleSolutions] = useState< + GitHubSearchItem[] + >([]) + + function constructQuery(searchQuery: string) { + return `${searchQuery} repo:fentas/b is:closed is:issue` + } + + async function searchGitHub(query: string) { + return request(`GET /search/issues`, { + q: query, + sort: "updated", + per_page: 3, + }) + } + + useEffect(() => { + if (!feedback) { + let query = constructQuery( + // Github does not allow queries longer than 256 characters + message ? message.substring(0, 256) : document.title + ) + searchGitHub(query) + .then(async (result) => { + if (!result.data.items.length && message) { + query = constructQuery(document.title) + result = await searchGitHub(query) + } + + setPossibleSolutionsQuery(query) + setPossibleSolutions(result.data.items) + }) + .catch((err) => console.error(err)) + } else { + setPossibleSolutionsQuery("") + setPossibleSolutions([]) + } + }, [feedback, message]) + + return ( + <> + {possibleSolutions.length > 0 && ( +
+ + If you faced a problem, here are some possible solutions from + GitHub: + +
    + {possibleSolutions.map((solution) => ( +
  • + + {solution.title} + +
  • + ))} +
+ + Explore more issues in{" "} + + the GitHub repository + + +
+ )} + + ) +} diff --git a/www/packages/docs-ui/src/components/Feedback/index.tsx b/www/packages/docs-ui/src/components/Feedback/index.tsx new file mode 100644 index 0000000..874d435 --- /dev/null +++ b/www/packages/docs-ui/src/components/Feedback/index.tsx @@ -0,0 +1,268 @@ +"use client" + +import React, { useRef, useState } from "react" +import { CSSTransition, SwitchTransition } from "react-transition-group" +import { Solutions } from "./Solutions" +import { ExtraData, useAnalytics } from "@/providers/Analytics" +import clsx from "clsx" +import { TextArea } from "@/components/TextArea" +import { Label } from "@/components/Label" +import { Button } from "docs-ui" +import { Details } from "@/components/Details" +import { InputText } from "@/components/Input/Text" + +export type FeedbackProps = { + event: string + pathName: string + reportLink?: string + question?: string + positiveBtn?: string + negativeBtn?: string + positiveQuestion?: string + negativeQuestion?: string + submitBtn?: string + submitMessage?: string + showPossibleSolutions?: boolean + className?: string + extraData?: ExtraData + vertical?: boolean + showLongForm?: boolean +} & React.HTMLAttributes + +export const Feedback = ({ + event, + pathName, + reportLink, + question = "Was this section helpful?", + positiveBtn = "Yes", + negativeBtn = "No", + positiveQuestion = "What was most helpful?", + negativeQuestion = "What can we improve?", + submitBtn = "Submit", + submitMessage = "Thank you for helping improve our documentation!", + showPossibleSolutions = true, + className = "", + extraData = {}, + vertical = false, + showLongForm = false, +}: FeedbackProps) => { + const [showForm, setShowForm] = useState(false) + const [submittedFeedback, setSubmittedFeedback] = useState(false) + const [loading, setLoading] = useState(false) + const inlineFeedbackRef = useRef(null) + const inlineQuestionRef = useRef(null) + const inlineMessageRef = useRef(null) + const [positiveFeedback, setPositiveFeedback] = useState(false) + const [message, setMessage] = useState("") + const [steps, setSteps] = useState("") + const [medusaVersion, setMedusaVersion] = useState("") + const [errorFix, setErrorFix] = useState("") + const [contactInfo, setContactInfo] = useState("") + const nodeRef: React.RefObject = submittedFeedback + ? inlineMessageRef + : showForm + ? inlineQuestionRef + : inlineFeedbackRef + const { loaded, track } = useAnalytics() + + function handleFeedback(e: React.MouseEvent) { + if (!loaded) { + return + } + const feedback = (e.target as Element).classList.contains("positive") + setPositiveFeedback(feedback) + setShowForm(true) + submitFeedback(e, feedback) + } + + function submitFeedback( + e: React.MouseEvent, + feedback = false + ) { + if (showForm) { + setLoading(true) + } + track( + event, + { + url: pathName, + label: document.title, + feedback: + (feedback !== null && feedback) || + (feedback === null && positiveFeedback) + ? "yes" + : "no", + message: message?.length ? message : null, + os: window.navigator.userAgent, + steps, + medusaVersion, + errorFix, + contactInfo, + ...extraData, + }, + function () { + if (showForm) { + setLoading(false) + resetForm() + } + } + ) + } + + function resetForm() { + setShowForm(false) + setSubmittedFeedback(true) + } + + return ( +
+ + { + nodeRef.current?.addEventListener("transitionend", done, false) + }} + classNames={{ + enter: "animate-fadeIn animation-fill-forwards animate-fast", + exit: "animate-fadeOut animation-fill-forwards animate-fast", + }} + > + <> + {!showForm && !submittedFeedback && ( +
+ +
+ + + {reportLink && ( + + )} +
+
+ )} + {showForm && !submittedFeedback && ( +
+ +