├── .cljfmt.edn ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .dir-locals.el ├── .github ├── FUNDING.yml └── workflows │ ├── continuous-deployment-workflow.yml │ ├── continuous-integration-workflow.yml │ └── docs-workflow.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── README.md ├── bb.edn ├── build.clj ├── deps.edn ├── docs ├── CreateAComponentPage.md ├── CreatingANewComponent.md ├── ai-docs.md └── release-procedure.md ├── externs └── detect-element-resize-externs.js ├── karma.conf.js ├── license.txt ├── package-lock.json ├── package.json ├── run └── resources │ └── public │ ├── assets │ ├── css │ │ ├── bootstrap.css │ │ ├── chosen-sprite.png │ │ ├── chosen-sprite@2x.png │ │ ├── material-design-iconic-font.min.css │ │ ├── re-com-demo.css │ │ └── re-com.css │ ├── fonts │ │ ├── Material-Design-Iconic-Font.eot │ │ ├── Material-Design-Iconic-Font.svg │ │ ├── Material-Design-Iconic-Font.ttf │ │ ├── Material-Design-Iconic-Font.woff │ │ └── Material-Design-Iconic-Font.woff2 │ └── scripts │ │ └── detect-element-resize.js │ ├── demo │ ├── architecture-LOD.jpg │ ├── h-box-demo-words.png │ ├── heart-parts.jpg │ └── light-bulb-shapes.jpg │ ├── index_dev.html │ └── index_prod.html ├── scripts ├── add-at-macro │ ├── README.md │ ├── add_at_macro.clj │ └── test │ │ ├── add_at_macro_test.clj │ │ └── test_runner.clj └── kwargs-to-map │ ├── README.md │ ├── kwargs_to_map.clj │ └── test │ ├── kwargs_to_map_test.clj │ └── test_runner.clj ├── shadow-cljs.edn ├── src ├── deps.cljs ├── re_com │ ├── alert.cljs │ ├── args.cljs │ ├── bar_tabs.cljs │ ├── bar_tabs │ │ └── theme.cljs │ ├── box.cljs │ ├── buttons.cljs │ ├── checkbox.cljs │ ├── close_button.cljs │ ├── config.cljs │ ├── core.clj │ ├── core.cljs │ ├── datepicker.cljs │ ├── daterange.cljs │ ├── debug.cljs │ ├── dmm_tracker.cljs │ ├── dropdown.cljs │ ├── dropdown │ │ └── theme.cljs │ ├── error_modal.cljs │ ├── error_modal │ │ └── theme.cljs │ ├── horizontal_tabs.cljs │ ├── horizontal_tabs │ │ └── theme.cljs │ ├── input_text.cljs │ ├── input_time.cljs │ ├── modal_panel.cljs │ ├── multi_select.cljs │ ├── nested_grid.cljs │ ├── nested_grid │ │ ├── parts.cljs │ │ ├── theme.cljs │ │ └── util.cljc │ ├── part.cljs │ ├── pill_tabs.cljs │ ├── pill_tabs │ │ └── theme.cljs │ ├── popover.cljs │ ├── progress_bar.cljs │ ├── radio_button.cljs │ ├── selection_list.cljs │ ├── simple_v_table.cljs │ ├── slider.cljs │ ├── splits.cljs │ ├── tabs.cljs │ ├── tag_dropdown.cljs │ ├── text.cljs │ ├── theme.cljs │ ├── theme │ │ ├── blue_modern.cljs │ │ ├── default.cljs │ │ └── util.cljs │ ├── throbber.cljs │ ├── tour.cljs │ ├── tree_select.cljs │ ├── tree_select │ │ └── theme.cljs │ ├── typeahead.cljs │ ├── util.cljs │ ├── v_table.cljs │ ├── validate.clj │ └── validate.cljs ├── re_demo │ ├── alert_box.cljs │ ├── alert_list.cljs │ ├── border.cljs │ ├── box.cljs │ ├── button.cljs │ ├── checkbox.cljs │ ├── config.cljs │ ├── core.cljs │ ├── customization.cljs │ ├── datepicker.cljs │ ├── daterange.cljs │ ├── debug.cljs │ ├── dropdown.cljs │ ├── dropdowns.cljs │ ├── gap.cljs │ ├── h_box.cljs │ ├── hyperlink.cljs │ ├── hyperlink_href.cljs │ ├── info_button.cljs │ ├── input_text.cljs │ ├── input_time.cljs │ ├── introduction.cljs │ ├── label.cljs │ ├── layout.cljs │ ├── line.cljs │ ├── md_circle_icon_button.cljs │ ├── md_icon_button.cljs │ ├── modal_panel.cljs │ ├── multi_select.cljs │ ├── nested_grid.cljs │ ├── nested_v_grid.cljs │ ├── p.cljs │ ├── parts.cljs │ ├── popover_dialog_demo.cljs │ ├── popovers.cljs │ ├── progress_bar.cljs │ ├── radio_button.cljs │ ├── row_button.cljs │ ├── scroller.cljs │ ├── selection_list.cljs │ ├── simple_v_table.cljs │ ├── simple_v_table_periodic_table.cljs │ ├── simple_v_table_sales.cljs │ ├── slider.cljs │ ├── splits.cljs │ ├── tabs.cljs │ ├── tag_dropdown.cljs │ ├── theme.cljs │ ├── throbber.cljs │ ├── title.cljs │ ├── tour.cljs │ ├── tree_select.cljs │ ├── typeahead.cljs │ ├── utils.clj │ ├── utils.cljs │ ├── v_box.cljs │ ├── v_table.cljs │ ├── v_table_demo.cljs │ ├── v_table_renderers.cljs │ └── v_table_sections.cljs └── user.clj └── test └── re_com ├── box_test.cljs ├── dropdown_test.cljs ├── misc_test.cljs ├── nested_grid_test.cljs ├── part_test.cljs ├── selection_list_test.cljs ├── time_test.cljs └── validate_test.cljs /.cljfmt.edn: -------------------------------------------------------------------------------- 1 | {:extra-indents {re-com.util/part [[:inner 0]] 2 | part [[:inner 0]]}} 3 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # [Choice] Java version (use -bullseye variants on local arm64/Apple Silicon): 11, 17, 11-bullseye, 17-bullseye, 11-buster, 17-buster 2 | 3 | # MT: removed this ARG stuff and went with hardcoded version instead because it was giving errors. 4 | # ARG VARIANT=${templateOption:imageVariant} 5 | # FROM mcr.microsoft.com/devcontainers/java:${VARIANT} 6 | 7 | FROM mcr.microsoft.com/devcontainers/java:21 8 | 9 | # [Optional] Clojure version 10 | ARG CLOJURE_VERSION=1.10.3 11 | 12 | # [Optional] Clojure tools version 13 | ARG CLOJURE_CLI_VERSION=1.10.3.1075 14 | 15 | # [Optional] Leiningen version 16 | ARG LEININGEN_VERSION="stable" 17 | 18 | # [Optional] POLYLITH version 19 | ARG POLYLITH_VERSION="0.2.13-alpha" 20 | 21 | ARG INSTALL_CLOJURE_CLI="true" 22 | # # [Option] Install Clojure CLI tool 23 | # ARG INSTALL_CLOJURE_CLI="${templateOption:installClojureCli}" 24 | 25 | ARG INSTALL_LEININGEN="true" 26 | # # [Option] Install Leiningen 27 | # ARG INSTALL_LEININGEN="${templateOption:installLeiningen}" 28 | 29 | ARG INSTALL_POLYLITH="true" 30 | # # [Option] Install Polylith 31 | # ARG INSTALL_POLYLITH="${templateOption:installPolylith}" 32 | 33 | RUN if [ "${INSTALL_CLOJURE_CLI}" = "true" ]; then \ 34 | apt-get update \ 35 | && apt-get -y install rlwrap \ 36 | && curl -OL "https://download.clojure.org/install/linux-install-${CLOJURE_CLI_VERSION}.sh" \ 37 | && chmod +x linux-install-${CLOJURE_CLI_VERSION}.sh \ 38 | && /linux-install-${CLOJURE_CLI_VERSION}.sh \ 39 | && rm /linux-install-${CLOJURE_CLI_VERSION}.sh \ 40 | && su vscode -c "clj --version"; fi 41 | 42 | 43 | RUN if [ "${INSTALL_LEININGEN}" = "true" ]; then \ 44 | curl -OL "https://raw.githubusercontent.com/technomancy/leiningen/${LEININGEN_VERSION}/bin/lein" \ 45 | && chmod +x lein \ 46 | && mv lein /usr/local/sbin; fi 47 | 48 | # Cache Clojure and dependencies 49 | RUN if [ "${INSTALL_LEININGEN}" = "true" ]; then \ 50 | su vscode -c " cd ~ \ 51 | && echo '(defproject dummy \"\" :dependencies [[org.clojure/clojure \"'${CLOJURE_VERSION}'\"]])' > project.clj \ 52 | && lein deps \ 53 | && rm project.clj"; fi 54 | 55 | RUN if [ "${INSTALL_POLYLITH}" = "true" ]; then \ 56 | curl -OL "https://github.com/polyfy/polylith/releases/download/v${POLYLITH_VERSION}/poly-${POLYLITH_VERSION}.jar" \ 57 | && mkdir -p /usr/local/polylith \ 58 | && mv poly-$POLYLITH_VERSION.jar /usr/local/polylith \ 59 | && echo '#!/bin/sh\nARGS=""\nwhile [ "$1" != "" ] ; do\n ARGS="$ARGS $1"\n shift\ndone\nexec "java" $JVM_OPTS "-jar" "/usr/local/polylith/poly-'$POLYLITH_VERSION'.jar" $ARGS\n' > /usr/local/sbin/poly \ 60 | && chmod +x /usr/local/sbin/poly \ 61 | && /usr/local/sbin/poly version; fi 62 | 63 | # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 64 | #ARG NODE_VERSION="${templateOption:nodeVersion}" 65 | ARG NODE_VERSION="16" 66 | RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi 67 | 68 | # [Optional] Uncomment this section to install additional OS packages. 69 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 70 | # && apt-get -y install --no-install-recommends 71 | 72 | # [Optional] Uncomment this line to install global node packages. 73 | # RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 74 | 75 | # Clean up package lists 76 | RUN apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* 77 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "re-com Development", 3 | "build": { 4 | "dockerfile": "Dockerfile", 5 | "args": { 6 | // Options 7 | "CLOJURE_VERSION": "1.10.3" 8 | } 9 | }, 10 | 11 | // Configure tool-specific properties. 12 | "customizations": { 13 | // Configure properties specific to VS Code. 14 | "vscode": { 15 | // Set *default* container specific settings.json values on container create. 16 | "settings": { 17 | }, 18 | 19 | // Add the IDs of extensions you want to be installed when the container is created. 20 | "extensions": [ 21 | "vscjava.vscode-java-pack", 22 | "borkdude.clj-kondo", 23 | "betterthantomorrow.calva" 24 | ] 25 | } 26 | }, 27 | 28 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 29 | "forwardPorts": [], 30 | 31 | // Use 'postCreateCommand' to run commands after the container is created. 32 | "postCreateCommand": "java -version", 33 | 34 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 35 | "remoteUser": "vscode" 36 | } 37 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((nil . ((cider-clojure-cli-aliases . ":demo") 2 | (cider-preferred-build-tool . clojure-cli) 3 | (cider-default-cljs-repl . custom) 4 | (cider-custom-cljs-repl-init-form . "(do (user/cljs-repl :demo))") 5 | (eval . (progn 6 | (make-variable-buffer-local 'cider-jack-in-nrepl-middlewares) 7 | (add-to-list 'cider-jack-in-nrepl-middlewares 8 | "shadow.cljs.devtools.server.nrepl/middleware")))))) 9 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: mike-thompson-day8 2 | -------------------------------------------------------------------------------- /.github/workflows/continuous-deployment-workflow.yml: -------------------------------------------------------------------------------- 1 | name: cd 2 | on: 3 | push: 4 | tags: 5 | - "v[0-9]+.[0-9]+.[0-9]+*" 6 | 7 | jobs: 8 | test: 9 | name: Test 10 | runs-on: ubuntu-22.04 11 | 12 | steps: 13 | - uses: actions/checkout@v1 14 | 15 | - name: Setup java 16 | uses: actions/setup-java@v3 17 | with: 18 | distribution: 'temurin' 19 | java-version: '21' 20 | 21 | - name: Install clojure tools 22 | uses: DeLaGuardo/setup-clojure@12.5 23 | with: 24 | cli: 'latest' 25 | bb: 'latest' 26 | 27 | - name: Cache clojure dependencies 28 | uses: actions/cache@v3 29 | with: 30 | path: | 31 | ~/.m2/repository 32 | ~/.gitlibs 33 | ~/.deps.clj 34 | ~/.npm 35 | .shadow-cljs 36 | key: cljdeps-${{ hashFiles('deps.edn') }}-${{ hashFiles ('package.json') }}-${{ hashFiles ('package-lock.json') }} 37 | restore-keys: cljdeps- 38 | 39 | - name: Fix git dubious directory ownership error 40 | run: git config --global --add safe.directory /__w/re-com/re-com 41 | 42 | - run: bb release-demo 43 | 44 | - name: Slack notification 45 | uses: homoluctus/slatify@v2.0.1 46 | if: failure() || cancelled() 47 | with: 48 | type: ${{ job.status }} 49 | job_name: re-com Tests 50 | channel: '#oss-robots' 51 | url: ${{ secrets.SLACK_WEBHOOK }} 52 | commit: true 53 | token: ${{ secrets.GITHUB_TOKEN }} 54 | 55 | release: 56 | name: Release 57 | needs: test 58 | runs-on: ubuntu-22.04 59 | 60 | env: 61 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 62 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 63 | CLOJARS_USERNAME: ${{ secrets.CLOJARS_USERNAME }} 64 | CLOJARS_TOKEN: ${{ secrets.CLOJARS_TOKEN }} 65 | 66 | steps: 67 | - uses: actions/checkout@v1 68 | 69 | - name: Setup java 70 | uses: actions/setup-java@v3 71 | with: 72 | distribution: 'temurin' 73 | java-version: '21' 74 | 75 | - name: Install clojure tools 76 | uses: DeLaGuardo/setup-clojure@12.5 77 | with: 78 | cli: 'latest' 79 | bb: 'latest' 80 | 81 | - name: Cache clojure dependencies 82 | uses: actions/cache@v3 83 | with: 84 | path: | 85 | ~/.m2/repository 86 | ~/.gitlibs 87 | ~/.deps.clj 88 | ~/.npm 89 | .shadow-cljs 90 | key: cljdeps-${{ hashFiles('deps.edn') }}-${{ hashFiles ('package.json') }}-${{ hashFiles ('package-lock.json') }} 91 | restore-keys: cljdeps- 92 | 93 | - run: bb release-clojars 94 | env: 95 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 96 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 97 | CLOJARS_USERNAME: ${{ secrets.CLOJARS_USERNAME }} 98 | CLOJARS_TOKEN: ${{ secrets.CLOJARS_TOKEN }} 99 | CLOJARS_PASSWORD: ${{ secrets.CLOJARS_TOKEN }} 100 | GITHUB_USERNAME: ${{ github.actor }} 101 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 102 | 103 | # This creates a 'GitHub Release' from the tag and includes link to CHANGELOG.md at the current 104 | # git ref. We do not use draft or prerelease features as we always want 105 | # the latest release to show in the right hand column of the project page regardless 106 | # of if it is a stable release. 107 | - name: Create GitHub Release 108 | uses: actions/create-release@v1 109 | env: 110 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 111 | with: 112 | tag_name: ${{ github.ref }} 113 | release_name: ${{ github.ref }} 114 | body: | 115 | [Changelog](https://github.com/day8/re-com/blob/master/CHANGELOG.md) 116 | draft: false 117 | prerelease: false 118 | 119 | - name: Slack notification 120 | uses: homoluctus/slatify@v2.0.1 121 | if: always() 122 | with: 123 | type: ${{ job.status }} 124 | job_name: re-com Deployment 125 | channel: '#oss-robots' 126 | url: ${{ secrets.SLACK_WEBHOOK }} 127 | commit: true 128 | token: ${{ secrets.GITHUB_TOKEN }} 129 | -------------------------------------------------------------------------------- /.github/workflows/continuous-integration-workflow.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: [push] 3 | 4 | jobs: 5 | 6 | test: 7 | 8 | name: Test 9 | 10 | runs-on: ubuntu-24.04 11 | 12 | steps: 13 | - uses: actions/checkout@v1 14 | 15 | - name: Setup java 16 | uses: actions/setup-java@v3 17 | with: 18 | distribution: 'temurin' 19 | java-version: '21' 20 | 21 | - name: Install clojure tools 22 | uses: DeLaGuardo/setup-clojure@12.5 23 | with: 24 | cli: 'latest' 25 | bb: 'latest' 26 | 27 | - name: Cache clojure dependencies 28 | uses: actions/cache@v3 29 | with: 30 | path: | 31 | ~/.m2/repository 32 | ~/.gitlibs 33 | ~/.deps.clj 34 | ~/.npm 35 | .shadow-cljs 36 | key: cljdeps-${{ hashFiles('deps.edn') }}-${{ hashFiles ('package.json') }}-${{ hashFiles ('package-lock.json') }} 37 | restore-keys: cljdeps- 38 | 39 | - name: Fix git dubious directory ownership error 40 | run: git config --global --add safe.directory /__w/re-com/re-com 41 | 42 | - run: bb ci 43 | 44 | - run: bb build-report-ci 45 | 46 | - uses: actions/upload-artifact@v4 47 | with: 48 | name: build-report 49 | path: target/build-report.html 50 | 51 | - name: Slack notification 52 | uses: homoluctus/slatify@v2.0.1 53 | if: failure() || cancelled() 54 | with: 55 | type: ${{ job.status }} 56 | job_name: re-com Tests 57 | channel: '#oss-robots' 58 | url: ${{ secrets.SLACK_WEBHOOK }} 59 | commit: true 60 | token: ${{ secrets.GITHUB_TOKEN }} 61 | -------------------------------------------------------------------------------- /.github/workflows/docs-workflow.yml: -------------------------------------------------------------------------------- 1 | name: re-com.day8.com.au 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | deploy: 9 | name: Deploy 10 | runs-on: ubuntu-22.04 11 | 12 | env: 13 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 14 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 15 | CLOJARS_USERNAME: ${{ secrets.CLOJARS_USERNAME }} 16 | CLOJARS_TOKEN: ${{ secrets.CLOJARS_TOKEN }} 17 | 18 | steps: 19 | - uses: actions/checkout@v1 20 | 21 | - name: Setup java 22 | uses: actions/setup-java@v3 23 | with: 24 | distribution: 'temurin' 25 | java-version: '21' 26 | 27 | - name: Install clojure tools 28 | uses: DeLaGuardo/setup-clojure@12.5 29 | with: 30 | cli: 'latest' 31 | bb: 'latest' 32 | 33 | - name: Cache clojure dependencies 34 | uses: actions/cache@v3 35 | with: 36 | path: | 37 | ~/.m2/repository 38 | ~/.gitlibs 39 | ~/.deps.clj 40 | ~/.npm 41 | .shadow-cljs 42 | key: cljdeps-${{ hashFiles('deps.edn') }}-${{ hashFiles ('package.json') }}-${{ hashFiles ('package-lock.json') }} 43 | restore-keys: cljdeps- 44 | 45 | - run: bb deploy-aws 46 | env: 47 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 48 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 49 | CLOJARS_USERNAME: ${{ secrets.CLOJARS_USERNAME }} 50 | CLOJARS_TOKEN: ${{ secrets.CLOJARS_TOKEN }} 51 | GITHUB_USERNAME: ${{ github.actor }} 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | AWS_EC2_METADATA_DISABLED: true 54 | 55 | - name: Invalidate CloudFront Distribution 56 | uses: chetan/invalidate-cloudfront-action@master 57 | env: 58 | DISTRIBUTION: ${{ secrets.AWS_CLOUDFRONT_DISTRIBUTION_ID }} 59 | PATHS: '/*' 60 | AWS_REGION: 'us-east-1' 61 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 62 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 63 | 64 | - name: Slack notification 65 | uses: homoluctus/slatify@v2.0.1 66 | if: always() 67 | with: 68 | type: ${{ job.status }} 69 | job_name: re-com.day8.com.au Deployment 70 | username: "Github Actions" 71 | channel: '#oss-robots' 72 | url: ${{ secrets.SLACK_WEBHOOK }} 73 | commit: true 74 | token: ${{ secrets.GITHUB_TOKEN }} 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | *.jar 4 | *.log 5 | .nrepl-* 6 | .lein-* 7 | pom.xml 8 | pom.xml.asc 9 | core/ 10 | out/ 11 | target/ 12 | compiled*/ 13 | misc/ 14 | /node_modules/ 15 | /.shadow-cljs/ 16 | /.clj-kondo/ 17 | /.lsp/ 18 | .* -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to re-com 2 | 3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: 4 | 5 | The following is a set of guidelines for contributing to re-com which is hosted on [Github](https://github.com/day8/re-com). 6 | These are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request. 7 | 8 | ## Creating issues for bugs 9 | 10 | Check if the issue has already been reported. If possible provide: 11 | 12 | * Version of re-com being used 13 | * Minimal reproduction steps 14 | 15 | ## Creating issues for features 16 | 17 | Use your best judgement on what is needed here. 18 | 19 | ## Pull requests 20 | 21 | **Create pull requests to the master branch**. 22 | 23 | ## Pull requests for bugs 24 | 25 | If possible provide: 26 | 27 | * Code that fixes the bug 28 | * Failing tests which pass with the new changes 29 | * Improvements to documentation to make it less likely that others will run into issues (if relevant). 30 | 31 | ## Pull requests for features 32 | 33 | If possible provide: 34 | 35 | * Code that implements the new feature 36 | * Tests to cover the new feature including all of the code paths 37 | * Docstrings for functions 38 | * Documentation examples 39 | -------------------------------------------------------------------------------- /bb.edn: -------------------------------------------------------------------------------- 1 | {:min-bb-version "0.4.0" 2 | :tasks 3 | {:requires ([babashka.fs :as fs] 4 | [clojure.string :as str]) 5 | :init (do 6 | (defn release-tag [] 7 | (->> (shell {:out :string} 8 | "git tag -l --sort=v:refname") 9 | :out 10 | str/split-lines 11 | (filter (fn [s] 12 | (re-find (re-pattern "^v\\d+\\.\\d+\\.\\d+$") s))) 13 | last 14 | str/trim)) 15 | (defn release-hash [] 16 | (->> (release-tag) 17 | (str "git rev-parse --short ") 18 | (shell {:out :string}) 19 | :out 20 | str/trim)) 21 | (defn latest-hash [] 22 | (->> "git log -1 --format=%h" 23 | (shell {:out :string}) 24 | :out 25 | str/trim)) 26 | (def opts (let [tag (release-tag) 27 | latest-hash (latest-hash) 28 | release-hash (release-hash)] 29 | {:extra-env {"DAY8_RELEASE_TAG" tag 30 | "RE_COM_VERSION" (cond-> tag 31 | (not= release-hash latest-hash) 32 | (str "--" latest-hash))}}))) 33 | clean (let [clean-targets ["node_modules" 34 | "run/resources/public/compiled_dev " 35 | "run/resources/public/compiled_prod" 36 | "run/resources/public/compiled_test"]] 37 | (doseq [target clean-targets] 38 | (println "bb clean: deleting" target) 39 | (fs/delete-tree target))) 40 | install (shell "npm install") 41 | test {:depends [clean install] 42 | :task (do (shell opts "npx shadow-cljs compile karma-test") 43 | (shell opts "npx karma start --single-run --reporters junit,dots"))} 44 | jar {:depends [clean] 45 | :task (shell opts "clojure -T:build jar")} 46 | release-clojars {:depends [jar] 47 | :task (shell opts "clojure -T:build clojars")} 48 | release-demo {:depends [clean install] 49 | :task (shell opts "npx shadow-cljs release demo")} 50 | ci (do (run 'test) (run 'release-demo)) 51 | build-report-ci {:depends [install] 52 | :task (shell opts "npx shadow-cljs clj-run shadow.cljs.build-report demo" 53 | "target/build-report.html")} 54 | watch {:depends [install] 55 | :task (shell opts "npx shadow-cljs watch demo browser-test karma-test")} 56 | watch-demo {:depends [install] 57 | :task (shell opts "npx shadow-cljs watch demo")} 58 | browser-test {:depends [install] 59 | :task (shell opts "npx shadow-cljs watch browser-test")} 60 | deploy-aws {:depends [ci] 61 | :task (shell opts "aws s3 sync run/resources/public s3://re-demo/ --acl" 62 | "public-read" 63 | "--cache-control" 64 | "max-age=2592000,public")}}} 65 | -------------------------------------------------------------------------------- /build.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | (:require 3 | [clojure.string :as str] 4 | [clojure.tools.build.api :as b])) 5 | 6 | (def lib 're-com/re-com) 7 | (def version (str/replace (System/getenv "DAY8_RELEASE_TAG") "v" "")) 8 | (def main-ns 're-com.core) 9 | (def class-dir "target/classes") 10 | (def basis (delay (b/create-basis {}))) 11 | (def jar-file (format "target/%s-%s.jar" lib version)) 12 | 13 | (defn- build-opts [opts] 14 | (merge opts 15 | {:lib lib 16 | :version version 17 | :jar-file jar-file 18 | :basis basis 19 | :class-dir class-dir 20 | :src-dirs ["src"] 21 | :pom-data [[:licenses 22 | [:license 23 | [:name "MIT"] 24 | [:url "https://opensource.org/licenses/MIT"]]]] 25 | :scm {:connection "scm:git:git://github.com/day8/re-com.git" 26 | :developerConnection "scm:git:ssh://git@github.com/day8/re-com.git" 27 | :url "https://github.com/day8/re-com" 28 | :tag version} 29 | :ns-compile [main-ns]})) 30 | 31 | (defn jar [opts] 32 | (b/delete {:path "target"}) 33 | (let [opts (build-opts opts)] 34 | (println "Writing pom.xml...") 35 | (b/write-pom opts) 36 | (b/copy-dir {:src-dirs ["src"] 37 | :include "re_com/**" 38 | :target-dir class-dir}) 39 | (b/copy-file {:src "src/deps.cljs" 40 | :target (str class-dir "/deps.cljs")}) 41 | (b/copy-dir {:src-dirs ["run/resources"] 42 | :include "public/assets/**" 43 | :target-dir class-dir}) 44 | (b/copy-file {:src "README.md" 45 | :target (str class-dir "/META-INF/README.md")}) 46 | (b/copy-file {:src "license.txt" 47 | :target (str class-dir "/META-INF/license.txt")}) 48 | (println "Building JAR...") 49 | (b/jar opts)) 50 | opts) 51 | 52 | (defn clojars [opts] 53 | (jar opts) 54 | ((requiring-resolve 'deps-deploy.deps-deploy/deploy) 55 | (merge {:installer :remote 56 | :artifact jar-file 57 | :pom-file (b/pom-path {:lib lib :class-dir class-dir})} 58 | opts)) 59 | opts) 60 | 61 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "test"] 2 | :aliases {:demo {:deps {org.clojure/clojure {:mvn/version "1.11.1"} 3 | org.clojure/clojurescript {:mvn/version "1.11.132" 4 | :exclusions [com.google.javascript/closure-compiler-unshaded 5 | org.clojure/google-closure-library 6 | org.clojure/google-closure-library-third-party]} 7 | thheller/shadow-cljs {:mvn/version "2.28.2"} 8 | reagent/reagent {:mvn/version "1.1.0"} 9 | org.clojure/core.async {:mvn/version "1.3.618"} 10 | com.andrewmcveigh/cljs-time {:mvn/version "0.5.2"} 11 | alandipert/storage-atom {:mvn/version "2.0.1"} 12 | com.cognitect/transit-cljs {:mvn/version "0.8.264"} 13 | clj-commons/secretary {:mvn/version "1.2.4"} 14 | day8/shadow-git-inject {:mvn/version "0.0.5"} 15 | hashp/hashp {:mvn/version "0.2.2"} 16 | zprint/zprint {:mvn/version "1.2.9"} 17 | day8.re-frame/re-frame-10x {:mvn/version "1.9.10"} 18 | org.clojars.abhinav/snitch {:mvn/version "0.1.14"}}} 19 | 20 | :build {:deps {io.github.clojure/tools.build {:git/tag "v0.10.9" 21 | :git/sha "e405aac"} 22 | slipset/deps-deploy {:mvn/version "0.2.2"}} 23 | :ns-default build}}} 24 | -------------------------------------------------------------------------------- /docs/CreateAComponentPage.md: -------------------------------------------------------------------------------- 1 | # Creating a Demo App Page 2 | 3 | If you create a new component, you'll want to add a new panel to the re-com demo app, demonstrating its functionality and specifying its arguments. Here's how to do that. 4 | 5 | ## 1. Create the Namespace 6 | 7 | The demo app has one namespace per component under `src/re-demo`. Place a new file in `src/re_demo` which mirrors the component name, and declare a namespace like: 8 | 9 | ```clojure 10 | (ns re-demo.my-component 11 | (:require-macros [re-com.core :refer [handler-fn]]) 12 | (:require [re-com.core :refer [at h-box v-box box gap line label title]] 13 | [re-demo.utils :refer [panel-title title2 args-table]] 14 | [reagent.core :as reagent])) 15 | ``` 16 | 17 | You may `require` additional namespaces as needed, typically the component being demonstrated and utility functions. 18 | 19 | ## 2. Define Demo State and Helpers 20 | 21 | Most pages define one or more functions (often named `*—demo` or `panel*`) that hold state atoms and return a hiccup for the interactive example. Keep page-specific state inside these functions using `reagent/atom`. 22 | 23 | ## 3. Compose the Demo Layout 24 | 25 | Arrange explanatory text, controls and the demo component using `h-box`, `v-box`, `gap`, `line` and other layout helpers. Display the component's argument and part tables via `args-table` and `parts-table` from `re-demo.utils` and the component namespace. 26 | 27 | A typical page has a structure like: 28 | 29 | ```clojure 30 | (defn button-demo [] 31 | (let [clicked? (reagent/atom false)] 32 | (fn [] 33 | [v-box 34 | :src (at) 35 | :gap "10px" 36 | :children [[panel-title "[button ...]" "src/re_com/buttons.cljs" "src/re_demo/button.cljs"] 37 | ;; notes column on the left 38 | [h-box 39 | :children [...explanatory widgets...]] 40 | ;; demo column on the right 41 | [h-box 42 | :children [...demo widgets...]]]]))) 43 | ``` 44 | 45 | ## 4. Provide a `panel` Wrapper 46 | 47 | The router in `core.cljs` expects each page namespace to expose a no‑arg `panel` function. It should merely call your demo function so that shadow hot reloading can replace the code correctly: 48 | 49 | ```clojure 50 | (defn panel [] 51 | [button-demo]) 52 | ``` 53 | 54 | ## 5. Register the Page 55 | 56 | Open `src/re_demo/core.cljs` and add an entry to `tabs-definition`. This entry links a `:label` and optional `:id` to your new `panel` function. Once added, the page will appear in the demo navigation. 57 | 58 | ```clojure 59 | {:id :my-component :level :minor :label "My Component" :panel my-component/panel} 60 | ``` 61 | 62 | ## 6. Test the Page 63 | 64 | Run the demo with: 65 | 66 | ```bash 67 | npx shadow-cljs watch demo 68 | ``` 69 | 70 | Navigate to your new page in the browser to ensure it appears and behaves as expected. 71 | -------------------------------------------------------------------------------- /docs/ai-docs.md: -------------------------------------------------------------------------------- 1 | # [Misunderstanding something about :key prop passing](https://github.com/reagent-project/reagent/issues/34) 2 | 3 | In Reagent, how you call a component like `(defn my-comp [arg] [:div "Hello " arg])` significantly affects where `:key` props are needed, especially when rendering lists of items. There are two main ways: 4 | * Calling with parentheses `(my-comp "world")`: This inlines the component's output (e.g., the `[:div "Hello " arg]` becomes `[:div "Hello world"]`) directly into the surrounding Hiccup. If this inlined Hiccup (like `[:div ...]`) has a `:key`, React uses that key. 5 | * Calling with square brackets `[my-comp "world"]`: This creates a distinct Reagent component instance for `my-comp`. If this `[my-comp ...]` form is itself an item in a list that React is managing, then *this vector form* needs the `:key` (e.g., `[my-comp {:key "some-unique-id"} "world"]`). A key inside what `my-comp` returns (e.g., on the `[:div]`) won't serve this purpose for the list. 6 | 7 | The crucial point for avoiding issues is this: when rendering a dynamic list of components, React requires a unique `:key` on each *immediate child element* in that list. This allows React to efficiently update, add, or remove items. 8 | * If you use the bracket form `[my-comp ...]` for items in a list, ensure that vector itself receives the `:key`: `[my-comp {:key "unique-id"} arg-1 ...]`. 9 | * If you use the parenthesis form `(my-comp ...)` for items in a list, and `my-comp` returns a single Hiccup element like `[:li {:key "unique-id"} ...]`, then that element's key is what React sees and uses at that list level. 10 | Confusing these two scenarios or omitting keys where React expects them on list items can lead to React warnings, incorrect UI updates, or loss of component state. 11 | 12 | # [Updating app-data Ratom succeeds, but page rerenders with original data](https://github.com/reagent-project/reagent/issues/35) 13 | 14 | The main problem faced was that the user interface (UI) wasn't updating even though the underlying application data, stored in a Reagent atom, was being correctly changed. For instance, when text was typed into an input field, the atom reflected this new text, but the screen stubbornly continued to display the old value. This can be very confusing because you can see the data is right, but the display is wrong. 15 | 16 | The core reason this happens in Reagent is related to how Reagent knows *when* to re-render a component. Reagent components automatically re-render when the Reagent atoms they *depend on* change. For Reagent to establish this dependency, it needs to see your component *accessing* (or "dereferencing" with `@`) the atom *inside* the component's rendering logic. In the problematic code, the atom was dereferenced *outside* the main component, like this: `[MyComponent (:some-data @app-atom)]`. Here, `@app-atom` is evaluated, and `MyComponent` just receives the plain data value. Reagent doesn't know `MyComponent`'s rendering depends on `app-atom` itself. 17 | 18 | To avoid this, you need to ensure that the atom dereference happens *within* a Reagent component's render function. The fix was to introduce a new top-level component, say `RootComponent`, and call the atom from inside it: `(defn RootComponent [] [MyComponent (:some-data @app-atom)])`. Then, you'd render `[RootComponent]`. Now, Reagent sees `RootComponent` dereferencing `app-atom`, so when `app-atom` changes, `RootComponent` (and consequently `MyComponent` with the new data) will re-render. Always make sure your components that need to react to atom changes are set up so Reagent can track their usage of those atoms. 19 | -------------------------------------------------------------------------------- /docs/release-procedure.md: -------------------------------------------------------------------------------- 1 | # Re-com Release Procedure 2 | 3 | ## Release Steps 4 | 5 | ### Build library and test the demo 6 | 7 | Note that all these commands are entered at the repo root folder. 8 | 9 | - [ ] Finish any feature branch you're working on. You should now be on the master branch. 10 | - [ ] Update README.md file if required and commit it. 11 | - [ ] Close all auto-compiles (command line and/or IntelliJ). 12 | - [ ] Run each of these tasks (will require separate terminals for each): 13 | 14 | bb watch 15 | bb release-demo 16 | bb ci 17 | 18 | - [ ] For `dev` and `prod`, run through each demo page and make sure no errors or debug output appears in the console. 19 | - [ ] For `test`, make sure all tests pass. Modify code/tests until all tests pass. 20 | - [ ] Close all auto-compiles again. 21 | 22 | ### Tag release 23 | 24 | - [ ] Tag the release on GitHub: 25 | 26 | git tag v2.10.0 HEAD 27 | git push --tags 28 | 29 | NOTE: Tagging the release will trigger GitHub Actions to deploy the library to Clojars and the demo site to AWS S3. 30 | 31 | ### Final tasks 32 | 33 | - [ ] Reply to all issues and pull requests relating to this release. 34 | -------------------------------------------------------------------------------- /externs/detect-element-resize-externs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Externs file for compiling with :optimization :advanced in the Google Closure Compiler 3 | * Without this, the compiler will munge the variable names and they will end up being undefined 4 | */ 5 | 6 | window.addResizeListener = function() {}; 7 | window.removeResizeListener = function() {}; 8 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | var root = './target/karma' // same as :output-dir 3 | var junitOutputDir = "target/karma/junit" 4 | var browsers = ['ChromeHeadless'] 5 | 6 | config.set({ 7 | frameworks: ['cljs-test'], 8 | browsers: browsers, 9 | basePath: root, 10 | files: [ 11 | 'test.js' 12 | ], 13 | plugins: [ 14 | 'karma-cljs-test', 15 | 'karma-chrome-launcher', 16 | 'karma-junit-reporter' 17 | ], 18 | colors: true, 19 | logLevel: config.LOG_INFO, 20 | client: { 21 | args: ['shadow.test.karma.init'], 22 | singleRun: true 23 | }, 24 | 25 | // the default configuration 26 | junitReporter: { 27 | outputDir: junitOutputDir + '/karma', // results will be saved as outputDir/browserName.xml 28 | outputFile: undefined, // if included, results will be saved as outputDir/browserName/outputFile 29 | suite: '' // suite will become the package name attribute in xml testsuite element 30 | } 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2021 Michael Thompson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "re-com", 3 | "devDependencies": { 4 | "@faker-js/faker": "^9.4.0", 5 | "karma": "6.3.4", 6 | "karma-chrome-launcher": "3.1.0", 7 | "karma-cljs-test": "0.1.0", 8 | "karma-junit-reporter": "2.0.1", 9 | "react": "17.0.2", 10 | "react-dom": "17.0.2", 11 | "shadow-cljs": "2.28.2" 12 | }, 13 | "dependencies": { 14 | "@js-joda/core": "3.2.0", 15 | "@js-joda/locale_en-us": "3.1.1", 16 | "@js-joda/timezone": "2.5.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /run/resources/public/assets/css/chosen-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/day8/re-com/472dee703e92ff20f1493cc8a17692b77724a4c2/run/resources/public/assets/css/chosen-sprite.png -------------------------------------------------------------------------------- /run/resources/public/assets/css/chosen-sprite@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/day8/re-com/472dee703e92ff20f1493cc8a17692b77724a4c2/run/resources/public/assets/css/chosen-sprite@2x.png -------------------------------------------------------------------------------- /run/resources/public/assets/css/re-com-demo.css: -------------------------------------------------------------------------------- 1 | .re-com-demo-util-code 2 | {font-size: 12px; 3 | background: unset; 4 | border: thin solid #ccc; 5 | border-radius: 4px; 6 | padding: 0.5em; 7 | background-color: #eee; 8 | margin-right: unset;} 9 | 10 | .re-com-demo-util-code code 11 | {font-size: 12px; 12 | border: unset; 13 | padding: unset; 14 | margin-right: unset;} 15 | -------------------------------------------------------------------------------- /run/resources/public/assets/fonts/Material-Design-Iconic-Font.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/day8/re-com/472dee703e92ff20f1493cc8a17692b77724a4c2/run/resources/public/assets/fonts/Material-Design-Iconic-Font.eot -------------------------------------------------------------------------------- /run/resources/public/assets/fonts/Material-Design-Iconic-Font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/day8/re-com/472dee703e92ff20f1493cc8a17692b77724a4c2/run/resources/public/assets/fonts/Material-Design-Iconic-Font.ttf -------------------------------------------------------------------------------- /run/resources/public/assets/fonts/Material-Design-Iconic-Font.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/day8/re-com/472dee703e92ff20f1493cc8a17692b77724a4c2/run/resources/public/assets/fonts/Material-Design-Iconic-Font.woff -------------------------------------------------------------------------------- /run/resources/public/assets/fonts/Material-Design-Iconic-Font.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/day8/re-com/472dee703e92ff20f1493cc8a17692b77724a4c2/run/resources/public/assets/fonts/Material-Design-Iconic-Font.woff2 -------------------------------------------------------------------------------- /run/resources/public/assets/scripts/detect-element-resize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Detect Element Resize 3 | * 4 | * https://github.com/sdecima/javascript-detect-element-resize 5 | * Sebastian Decima 6 | * 7 | * version: 0.5.3 8 | **/ 9 | 10 | (function () { 11 | var attachEvent = document.attachEvent, 12 | stylesCreated = false; 13 | 14 | if (!attachEvent) { 15 | var requestFrame = (function(){ 16 | var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || 17 | function(fn){ return window.setTimeout(fn, 20); }; 18 | return function(fn){ return raf(fn); }; 19 | })(); 20 | 21 | var cancelFrame = (function(){ 22 | var cancel = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame || 23 | window.clearTimeout; 24 | return function(id){ return cancel(id); }; 25 | })(); 26 | 27 | function resetTriggers(element){ 28 | var triggers = element.__resizeTriggers__, 29 | expand = triggers.firstElementChild, 30 | contract = triggers.lastElementChild, 31 | expandChild = expand.firstElementChild; 32 | contract.scrollLeft = contract.scrollWidth; 33 | contract.scrollTop = contract.scrollHeight; 34 | expandChild.style.width = expand.offsetWidth + 1 + 'px'; 35 | expandChild.style.height = expand.offsetHeight + 1 + 'px'; 36 | expand.scrollLeft = expand.scrollWidth; 37 | expand.scrollTop = expand.scrollHeight; 38 | }; 39 | 40 | function checkTriggers(element){ 41 | return element.offsetWidth != element.__resizeLast__.width || 42 | element.offsetHeight != element.__resizeLast__.height; 43 | } 44 | 45 | function scrollListener(e){ 46 | var element = this; 47 | resetTriggers(this); 48 | if (this.__resizeRAF__) cancelFrame(this.__resizeRAF__); 49 | this.__resizeRAF__ = requestFrame(function(){ 50 | if (checkTriggers(element)) { 51 | element.__resizeLast__.width = element.offsetWidth; 52 | element.__resizeLast__.height = element.offsetHeight; 53 | element.__resizeListeners__.forEach(function(fn){ 54 | fn.call(element, e); 55 | }); 56 | } 57 | }); 58 | }; 59 | 60 | /* Detect CSS Animations support to detect element display/re-attach */ 61 | var animation = false, 62 | animationstring = 'animation', 63 | keyframeprefix = '', 64 | animationstartevent = 'animationstart', 65 | domPrefixes = 'Webkit Moz O ms'.split(' '), 66 | startEvents = 'webkitAnimationStart animationstart oAnimationStart MSAnimationStart'.split(' '), 67 | pfx = ''; 68 | { 69 | var elm = document.createElement('fakeelement'); 70 | if( elm.style.animationName !== undefined ) { animation = true; } 71 | 72 | if( animation === false ) { 73 | for( var i = 0; i < domPrefixes.length; i++ ) { 74 | if( elm.style[ domPrefixes[i] + 'AnimationName' ] !== undefined ) { 75 | pfx = domPrefixes[ i ]; 76 | animationstring = pfx + 'Animation'; 77 | keyframeprefix = '-' + pfx.toLowerCase() + '-'; 78 | animationstartevent = startEvents[ i ]; 79 | animation = true; 80 | break; 81 | } 82 | } 83 | } 84 | } 85 | 86 | var animationName = 'resizeanim'; 87 | var animationKeyframes = '@' + keyframeprefix + 'keyframes ' + animationName + ' { from { opacity: 0; } to { opacity: 0; } } '; 88 | var animationStyle = keyframeprefix + 'animation: 1ms ' + animationName + '; '; 89 | } 90 | 91 | function createStyles() { 92 | if (!stylesCreated) { 93 | //opacity:0 works around a chrome bug https://code.google.com/p/chromium/issues/detail?id=286360 94 | var css = (animationKeyframes ? animationKeyframes : '') + 95 | '.resize-triggers { ' + (animationStyle ? animationStyle : '') + 'visibility: hidden; opacity: 0; } ' + 96 | '.resize-triggers, .resize-triggers > div, .contract-trigger:before { content: \" \"; display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; } .resize-triggers > div { background: #eee; overflow: auto; } .contract-trigger:before { width: 200%; height: 200%; }', 97 | head = document.head || document.getElementsByTagName('head')[0], 98 | style = document.createElement('style'); 99 | 100 | style.type = 'text/css'; 101 | if (style.styleSheet) { 102 | style.styleSheet.cssText = css; 103 | } else { 104 | style.appendChild(document.createTextNode(css)); 105 | } 106 | 107 | head.appendChild(style); 108 | stylesCreated = true; 109 | } 110 | } 111 | 112 | window.addResizeListener = function(element, fn){ 113 | if (attachEvent) element.attachEvent('onresize', fn); 114 | else { 115 | if (!element.__resizeTriggers__) { 116 | if (getComputedStyle(element).position == 'static') element.style.position = 'relative'; 117 | createStyles(); 118 | element.__resizeLast__ = {}; 119 | element.__resizeListeners__ = []; 120 | (element.__resizeTriggers__ = document.createElement('div')).className = 'resize-triggers'; 121 | element.__resizeTriggers__.innerHTML = '
' + 122 | '
'; 123 | element.appendChild(element.__resizeTriggers__); 124 | resetTriggers(element); 125 | element.addEventListener('scroll', scrollListener, true); 126 | 127 | /* Listen for a css animation to detect element display/re-attach */ 128 | animationstartevent && element.__resizeTriggers__.addEventListener(animationstartevent, function(e) { 129 | if(e.animationName == animationName) 130 | resetTriggers(element); 131 | }); 132 | } 133 | element.__resizeListeners__.push(fn); 134 | } 135 | }; 136 | 137 | window.removeResizeListener = function(element, fn){ 138 | if (attachEvent) element.detachEvent('onresize', fn); 139 | else { 140 | element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1); 141 | if (!element.__resizeListeners__.length) { 142 | element.removeEventListener('scroll', scrollListener); 143 | element.__resizeTriggers__ = !element.removeChild(element.__resizeTriggers__); 144 | } 145 | } 146 | } 147 | })(); -------------------------------------------------------------------------------- /run/resources/public/demo/architecture-LOD.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/day8/re-com/472dee703e92ff20f1493cc8a17692b77724a4c2/run/resources/public/demo/architecture-LOD.jpg -------------------------------------------------------------------------------- /run/resources/public/demo/h-box-demo-words.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/day8/re-com/472dee703e92ff20f1493cc8a17692b77724a4c2/run/resources/public/demo/h-box-demo-words.png -------------------------------------------------------------------------------- /run/resources/public/demo/heart-parts.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/day8/re-com/472dee703e92ff20f1493cc8a17692b77724a4c2/run/resources/public/demo/heart-parts.jpg -------------------------------------------------------------------------------- /run/resources/public/demo/light-bulb-shapes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/day8/re-com/472dee703e92ff20f1493cc8a17692b77724a4c2/run/resources/public/demo/light-bulb-shapes.jpg -------------------------------------------------------------------------------- /run/resources/public/index_dev.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | re-com Demo (dev) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /run/resources/public/index_prod.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | re-com Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /scripts/add-at-macro/README.md: -------------------------------------------------------------------------------- 1 | You should run this [babashka](https://github.com/babashka/babashka) script on a legacy codebase which uses `re-com`. 2 | 3 | Version 2.13.0 of `re-com` introduced a new `:src` 4 | debugging feature described here: https://re-com.day8.com.au/#/debug 5 | 6 | This script will recursively traverse all the ClojureScript files in an existing codebase, adding `:src (at)` to every 7 | use of a `re-com` component while maintaining the indentation. Where necessary, it will also modify namespace `requires` 8 | to add the `at` macro. 9 | 10 | So, existing code like this: 11 | ```clojure 12 | [v-box 13 | :size "auto" 14 | :gap "10px" 15 | :children [...]] 16 | ``` 17 | 18 | will be changed to 19 | ```clojure 20 | [v-box 21 | :src (at) ;; <-- this was added 22 | :size "auto" 23 | :gap "10px" 24 | :children [...]] 25 | ``` 26 | 27 | This script is clever enough to detect when a component already has an existing `:src (at)` argument, and it will not 28 | add duplicates. It is also clever enough to not add a duplicate requires for `at`. As a result, it can be run multiple 29 | times on a codebase. 30 | 31 | ### To Run This Script 32 | 33 | 1. Install [babashka](https://github.com/babashka/babashka) `0.3.2` or later by following [these instructions](https://github.com/babashka/babashka#installation). 34 | 35 | 2. Clone re-com's GitHub repository, 36 | 37 | ``` 38 | git clone https://github.com/day8/re-com.git 39 | ``` 40 | 41 | 3. Navigate to the scripts location 42 | ``` 43 | cd re-com/scripts/add-at-macro 44 | ``` 45 | 46 | 4. Run 47 | 48 | If the project using re-com had sources in `../my-project/src`, then run via babashka (aka `bb`): 49 | ``` 50 | bb add_at_macro.clj "../my-project/src" 51 | ``` 52 | 53 | 5. Inspect, the files in the `src` directory. Notice the updates made. 54 | 55 | #### Tip 56 | 57 | For run, (not test command in the next section) The `bb` command also takes the following extra command line 58 | arguments after the directory. 59 | 1. `--verbose` or `-v`. When this is passed, the changes the script makes are printed to console. Example command 60 | ```sh 61 | bb add_at_macro.clj "../my-project/src" --verbose 62 | ``` 63 | 64 | 2. `--testing` or `-t`. When this is passed, the files that the script edits are not saved to disk but printed to console 65 | ```sh 66 | bb add_at_macro.clj "../my-project/src" --testing 67 | ``` 68 | Note, When `-testing` is passed, `-verbose` is always true. 69 | 70 | 3. `--help` or `-h`. Print the help menu. Example command 71 | ```sh 72 | bb add_at_macro.clj --help 73 | ``` 74 | 75 | 76 | 77 | ### Running The Tests 78 | 79 | 1. Install [babashka](https://github.com/babashka/babashka) `0.3.2` or later by following [these instructions](https://github.com/babashka/babashka#installation). 80 | 81 | 2. Navigate to the home directory of this script 82 | ``` 83 | cd re-com/scripts/add-at-macro 84 | ``` 85 | 3. To run the tests via babashka run, 86 | ```sh 87 | bb test\test-runner.clj 88 | ``` 89 | 90 | -------------------------------------------------------------------------------- /scripts/add-at-macro/test/test_runner.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (require '[clojure.test :as t] 4 | '[babashka.classpath :as cp]) 5 | 6 | (cp/add-classpath "test") ;; Watch the test file 7 | (cp/add-classpath ".") ;; Watch the main script 8 | 9 | (require 'add-at-macro-test) 10 | 11 | (def test-results 12 | (t/run-tests 'add-at-macro-test)) 13 | 14 | (def failures-and-errors 15 | (let [{:keys [:fail :error]} test-results] 16 | (+ fail error))) 17 | 18 | (System/exit failures-and-errors) -------------------------------------------------------------------------------- /scripts/kwargs-to-map/test/test_runner.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (require '[clojure.test :as t] 4 | '[babashka.classpath :as cp]) 5 | 6 | (cp/add-classpath "test") 7 | (cp/add-classpath ".") 8 | 9 | (require 'kwargs-to-map-test) 10 | 11 | (def test-results 12 | (t/run-tests 'kwargs-to-map-test)) 13 | 14 | (def failures-and-errors 15 | (let [{:keys [:fail :error]} test-results] 16 | (+ fail error))) 17 | 18 | (System/exit failures-and-errors) -------------------------------------------------------------------------------- /shadow-cljs.edn: -------------------------------------------------------------------------------- 1 | {:deps {:aliases [:demo]} 2 | :nrepl {:port 7777} 3 | 4 | :builds {:demo 5 | {:target :browser 6 | :modules {:demo {:init-fn re-demo.core/mount-demo}} 7 | :build-hooks [(shadow-git-inject.core/hook)] 8 | :compiler-options {:closure-defines {re-com.config/version #env "RE_COM_VERSION" 9 | ;; For production builds of the demo app, set goog.DEBUG 10 | ;; to be true so that the debugging demo page works as expected. 11 | goog.DEBUG true 12 | re-com.config/force-include-args-desc? true} 13 | ;; For production builds of the demo app, keep the component name 14 | ;; symbols for display in validation error logging. 15 | :pseudo-names true 16 | :externs ["externs/detect-element-resize-externs.js"]} 17 | :dev {:asset-path "/compiled_dev/demo" 18 | :output-dir "run/resources/public/compiled_dev/demo" 19 | :compiler-options 20 | {:closure-defines {;; When re-com produces validation errors it tries to provide links 21 | ;; to source code. These links require that you provide the root URL 22 | ;; to the ClojureScript compiler output with source maps. 23 | re-com.config/root-url-for-compiler-output 24 | "http://localhost:3449/compiled_dev/demo/cljs-runtime/"} 25 | :external-config {:devtools/config {:features-to-install [:formatters :hints]}}}} 26 | :release {:output-dir "run/resources/public/compiled_prod/demo" 27 | :compiler-options {:closure-defines {;; For production builds, such as the demo website, there is no source 28 | ;; code to link to in validation errors or component stacks, so we set 29 | ;; it to an empty string to cause links to not be displayed at all. 30 | re-com.config/root-url-for-compiler-output ""}}} 31 | :devtools {:http-port 3449 32 | :http-root "run/resources/public" 33 | :push-state/index "index_dev.html" 34 | :preloads [hashp.core]}} 35 | 36 | :browser-test {:target :browser-test 37 | :ns-regexp "-test$" 38 | :test-dir "run/resources/public/compiled_test/demo" 39 | :compiler-options {:closure-defines {re-com.config/version #env "RE_COM_VERSION"} 40 | :externs ["externs/detect-element-resize-externs.js"] 41 | :external-config {:devtools/config {:features-to-install [:formatters :hints]}}} 42 | :devtools {:http-port 8021 43 | :http-root "run/resources/public/compiled_test/demo" 44 | :preloads [hashp.core]}} 45 | :karma-test {:target :karma 46 | :ns-regexp ".*-test$" 47 | :output-to "target/karma/test.js" 48 | :compiler-options {:pretty-print true 49 | :closure-defines {re-com.config/version #env "RE_COM_VERSION"} 50 | :externs ["externs/detect-element-resize-externs.js"]}}}} 51 | -------------------------------------------------------------------------------- /src/deps.cljs: -------------------------------------------------------------------------------- 1 | {:npm-dev-deps {"react" "17.0.2" 2 | "react-dom" "17.0.2" 3 | "shadow-cljs" "2.28.2" 4 | "karma" "6.3.4" 5 | "karma-chrome-launcher" "3.1.0" 6 | "karma-cljs-test" "0.1.0" 7 | "karma-junit-reporter" "2.0.1"}} 8 | -------------------------------------------------------------------------------- /src/re_com/args.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.args 2 | (:require 3 | [re-com.validate :refer [css-style? html-attr? parts? vector-of-maps? css-class?]])) 4 | 5 | (def class 6 | {:name :class, 7 | :required false, 8 | :type "string | vector", 9 | :validate-fn css-class?, 10 | :description [:span "See " [:a {:href "#/customization"} "Customization"]]}) 11 | 12 | (def style 13 | {:name :style, 14 | :required false, 15 | :type "CSS style map", 16 | :validate-fn css-style?, 17 | :description [:span "See " [:a {:href "#/customization"} "Customization"]]}) 18 | 19 | (def attr 20 | {:name :attr, 21 | :required false, 22 | :type "HTML attr map", 23 | :validate-fn html-attr?, 24 | :description [:span "See " [:a {:href "#/customization"} "Customization"]]}) 25 | 26 | (defn parts [part-names] 27 | {:name :parts, 28 | :required false, 29 | :type "map", 30 | :validate-fn (parts? part-names), 31 | :description "See Parts section below."}) 32 | 33 | (def pre {:name :pre-theme, :description "alpha"}) 34 | 35 | (def theme {:name :theme, :description "alpha"}) 36 | 37 | (def ref {:name :ref :description "re-com internal"}) 38 | (def data-rc-src {:name :data-rc-src :description "re-com internal"}) 39 | (def data-rc {:name :data-rc :description "re-com internal"}) 40 | 41 | (def src 42 | {:name :src 43 | :required false 44 | :type "map" 45 | :validate-fn map? 46 | :description [:span "See " [:a {:href "#/debug"} "Debugging"]]}) 47 | 48 | (def debug-as 49 | {:name :debug-as, 50 | :required false, 51 | :type "map", 52 | :validate-fn map?, 53 | :description 54 | [:span 55 | "Used in dev builds to assist with debugging, when one component is used to implement another component, " 56 | "and we want the implementation component to masquerade as the original component in debug output, " 57 | "such as component stacks. A map optionally containing keys" 58 | [:code ":component"] "and" [:code ":args"] "."]}) 59 | 60 | -------------------------------------------------------------------------------- /src/re_com/bar_tabs/theme.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.bar-tabs.theme 2 | (:require-macros 3 | [re-com.core :refer [handler-fn]]) 4 | (:require 5 | [re-com.dropdown :as-alias dd] 6 | [re-com.theme.util :as tu] 7 | [re-com.box :refer [flex-child-style]] 8 | [re-com.horizontal-tabs :as-alias ht] 9 | [re-com.bar-tabs :as-alias bt] 10 | [re-com.theme.default :refer [bootstrap base]])) 11 | 12 | (defmethod bootstrap ::bt/wrapper [{:keys [vertical?] :as props}] 13 | (tu/class props "noselect" 14 | (if vertical? "btn-group-vertical" "btn-group") 15 | "rc-tabs")) 16 | 17 | (defmethod base ::bt/wrapper [props] 18 | (-> props 19 | (assoc :type "button") 20 | (tu/style (flex-child-style "none")))) 21 | 22 | (defmethod bootstrap ::bt/button [{{{:keys [enable selectable]} :state} :re-com 23 | :as props}] 24 | (tu/class props 25 | "btn" 26 | "btn-default" 27 | "rc-tabs-btn" 28 | (when (= :disabled enable) "disabled") 29 | (when (= :selected selectable) ["active"]))) 30 | 31 | (defmethod base ::bt/button [{{{:keys [enable selectable]} :state} :re-com 32 | :keys [id on-change] 33 | :as props}] 34 | (cond-> props 35 | :do (tu/style {:cursor "pointer"}) 36 | (and on-change 37 | (= :enabled enable) 38 | (= :unselected selectable)) (tu/attr {:on-click (handler-fn (on-change id))}))) 39 | -------------------------------------------------------------------------------- /src/re_com/config.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.config) 2 | 3 | ;; debug? is true for development builds (e.g. bb watch) and false for production release builds (e.g. bb release-demo). 4 | ;; It is used to disable component argument validation in production, and to determine the value of include-args-desc? 5 | ;; below. 6 | (def debug? 7 | ^boolean js/goog.DEBUG) 8 | 9 | ;; We don't want to include the data structures for argument and parts descriptions in production builds that use re-com 10 | ;; EXCEPT for the demo site (https://re-com.day8.com.au/) which is a special case. On the demo site we use those data 11 | ;; structures to generate the documentation (i.e. argument and parts tables). The following Closure define is therefore 12 | ;; set via shadow-cljs compiler options for demo builds only, to ensure the data structures are available regardless of 13 | ;; debug?. 14 | (goog-define force-include-args-desc? false) 15 | 16 | (goog-define root-url-for-compiler-output "") 17 | 18 | (goog-define debug-parts? true) 19 | 20 | (goog-define log-format "js") 21 | 22 | ;; When include-args-desc? is true, the data structures for arguments and parts will be included in the output JS file, 23 | ;; otherwise they will not be included. 24 | (def ^boolean include-args-desc? 25 | (or debug? force-include-args-desc?)) 26 | 27 | (goog-define version "") 28 | -------------------------------------------------------------------------------- /src/re_com/core.clj: -------------------------------------------------------------------------------- 1 | (ns re-com.core) 2 | 3 | ;; There is a trap when writing DOM event handlers. This looks innocent enough: 4 | ;; 5 | ;; :on-mouse-out #(reset! my-over-atom false) 6 | ;; 7 | ;; But notice that it inadvertently returns false. returning false means something!! 8 | ;; v0.11 of ReactJS will invoke both stopPropagation() and preventDefault() 9 | ;; on the event. Almost certainly not what we want. 10 | ;; 11 | ;; Note: v0.12 of ReactJS will do the same as v0.11, except it also issues a 12 | ;; deprecation warning about false returns. 13 | ;; 14 | ;; Note: ReactJS only tests explicitly for false, not falsy values. So 'nil' is a 15 | ;; safe return value. 16 | ;; 17 | ;; So 'handler-fn' is a macro which will stop you from inadvertently returning 18 | ;; false in a handler. 19 | ;; 20 | ;; 21 | ;; Examples: 22 | ;; 23 | ;; :on-mouse-out (handler-fn (reset! my-over-atom false)) ;; notice no # in front reset! form 24 | ;; 25 | ;; 26 | ;; :on-mouse-out (handler-fn 27 | ;; (reset! over-atom false) ;; notice: no need for a 'do' 28 | ;; (now do something else) 29 | ;; (.preventDefault event)) ;; notice access to the 'event' 30 | 31 | (defmacro handler-fn 32 | ([& body] 33 | `(fn [~'event] ~@body nil))) ;; force return nil 34 | 35 | ;; Obtain source code coordinates and assemble them into a map literal containing `:file` and `:line` keys 36 | ;; See explanation: https://re-com.day8.com.au/#/debug 37 | (defmacro at 38 | [] 39 | `(if-not ~(vary-meta 'js/goog.DEBUG assoc :tag 'boolean) 40 | nil 41 | ~(select-keys (meta &form) [:file :line]))) 42 | 43 | ;; Obtain the current component's component-name and args to be provided as a :debug-as argument to another component. 44 | ;; Causes the other component to masquerade in debug output, such as component stacks, as the current component in terms 45 | ;; of its component-name and args. Used when the root of the current component is another re-com component. See also 46 | ;; :debug-as parameter supplied to all components. 47 | ;; 48 | ;; WARNING: Assumes the context has an args symbol. 49 | (defmacro reflect-current-component 50 | [] 51 | {:component '(re-com.debug/short-component-name (reagent.impl.component/component-name (reagent.core/current-component))) 52 | :args 'args}) -------------------------------------------------------------------------------- /src/re_com/dmm_tracker.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.dmm-tracker) 2 | ;; Taken from: https://github.com/facebook/fixed-data-table/blob/master/src/vendor_upstream/dom/DOMMouseMoveTracker.js 3 | ;; From: https://github.com/facebook/fixed-data-table/blob/master/src/vendor_upstream/stubs/EventListener.js 4 | 5 | (defn document-event-listener 6 | "Listen to DOM events during the bubble phase 7 | arg1 Event type, e.g. \"click\" or \"mouseover\" 8 | arg2 callback function 9 | return function to call to remove the event listener" 10 | [eventType callback] 11 | (let [target (.-documentElement js/document)] 12 | (if (.-addEventListener target) 13 | (do (.addEventListener target eventType callback false) 14 | ;(println "addEventListener" eventType) 15 | #(do (.removeEventListener target eventType callback false))) 16 | ;(println "removeEventListener" eventType) 17 | 18 | (throw (js/Error. "Couldn't find addEventListener method in document-event-listener"))))) 19 | 20 | (defprotocol IMouseMoveTracker 21 | (captureMouseMoves [this event]) 22 | (-releaseMouseMoves [this event]) 23 | (-onMouseMove [this event])) 24 | 25 | (deftype MouseMoveTracker [on-change 26 | on-drag-end 27 | ^:mutable eventMoveToken 28 | ^:mutable eventUpToken 29 | ^:mutable isDragging? 30 | ^:mutable x 31 | ^:mutable y] 32 | IMouseMoveTracker 33 | (captureMouseMoves 34 | ;; This is to set up the listeners for listening to mouse move and mouse up signaling the movement has ended. Please note that these listeners are added at the document.body level. It takes in an event in order to grab inital state 35 | [this event] 36 | (when (and (not eventMoveToken) (not eventUpToken)) 37 | (set! eventMoveToken (document-event-listener "mousemove" #(-onMouseMove this %))) 38 | (set! eventUpToken (document-event-listener "mouseup" #(-releaseMouseMoves this %)))) 39 | (when-not isDragging? 40 | (set! isDragging? true) 41 | (set! x (.-clientX event)) 42 | (set! y (.-clientY event))) 43 | #_(.preventDefault event)) ;; [GR] This prevented getting access to the components in the popover 44 | 45 | (-releaseMouseMoves 46 | ; These releases all of the listeners on document.body 47 | [this event] 48 | (when eventMoveToken 49 | (eventMoveToken) 50 | (set! eventMoveToken nil)) 51 | (when eventUpToken 52 | (eventUpToken) 53 | (set! eventUpToken nil)) 54 | (when isDragging? 55 | (set! isDragging? false) 56 | (set! x nil) 57 | (set! y nil) 58 | (on-drag-end (.-ctrlKey event) (.-shiftKey event) event))) 59 | 60 | (-onMouseMove 61 | ;; Calls onMove passed into constructor and updates internal state 62 | [this event] 63 | (let [curr-x (.-clientX event) 64 | curr-y (.-clientY event) 65 | delta-x (- curr-x x) 66 | delta-y (- curr-y y)] 67 | (on-change delta-x delta-y curr-x curr-y (.-ctrlKey event) (.-shiftKey event) event) 68 | (set! x curr-x) 69 | (set! y curr-y) 70 | (.preventDefault event)))) 71 | 72 | (defn make-dmm-tracker 73 | [on-change on-drag-end] 74 | (->MouseMoveTracker on-change on-drag-end nil nil false nil nil)) 75 | -------------------------------------------------------------------------------- /src/re_com/dropdown/theme.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.dropdown.theme 2 | (:require 3 | [clojure.string :as str] 4 | [re-com.dropdown :as-alias dd] 5 | [re-com.util :refer [px]] 6 | [re-com.theme.util :as tu :refer [merge-props]] 7 | [re-com.theme.default :refer [base main]])) 8 | 9 | (defmethod base ::dd/body-wrapper 10 | [{:keys [position top left anchor-top] :as props}] 11 | (tu/style props {:position position 12 | :top (px top) 13 | :left (px left) 14 | :opacity (when-not anchor-top 0) 15 | :overflow-y "auto" 16 | :overflow-x "visible" 17 | :z-index 30})) 18 | 19 | (defmethod base ::dd/anchor-wrapper 20 | [{{:keys [state transition!] 21 | {:keys [sm-2]} :variables} :re-com 22 | :as props}] 23 | (-> props 24 | (merge-props 25 | {:attr {:tab-index (or (:tab-index state) 0) 26 | :on-click #(transition! :toggle) 27 | #_#_:on-blur #(do (transition! :blur) 28 | (transition! :exit))} 29 | :style {:outline (when (and (= :focused (:focusable state)) 30 | (not= :open (:openable state))) 31 | (str sm-2 " auto #ddd")) 32 | :outline-offset (str "-" sm-2) 33 | :position "relative" 34 | #_#_:display "block" 35 | :overflow "hidden" 36 | :user-select "none" 37 | #_#_:width "100%" 38 | :z-index (case (:openable state) 39 | :open 20 nil)}}))) 40 | 41 | (defmethod main ::dd/anchor-wrapper 42 | [{:as props 43 | {:keys [state] 44 | $ :variables} :re-com}] 45 | (let [open? (= :open (:openable state)) 46 | closed? (= :closed (:openable state))] 47 | (-> props 48 | (merge-props 49 | {:align :center 50 | :style {:background-color (:background $) 51 | :background-clip "padding-box" 52 | :border (str "1px solid " 53 | (cond 54 | closed? (:border $) 55 | open? "#66afe9")) 56 | :border-radius "4px" 57 | :box-shadow (cond-> "0 1px 1px rgba(0, 0, 0, .075) inset" 58 | open? (str ", 0 0 8px rgba(82, 168, 236, .6)")) 59 | :color (:foreground $) 60 | :height "34px" 61 | :padding "0 8px 0 8px" 62 | :text-decoration "none" 63 | :white-space "nowrap" 64 | :transition "border 0.2s box-shadow 0.2s"}})))) 65 | 66 | (defmethod base ::dd/backdrop 67 | [props] 68 | (merge-props props 69 | {:class "noselect" 70 | :style {:position "fixed" 71 | :left "0px" 72 | :top "0px" 73 | :width "100%" 74 | :height "100%" 75 | :pointer-events "none"}})) 76 | 77 | (defmethod main ::dd/backdrop 78 | [{{:keys [state]} :re-com :as props}] 79 | (tu/style props 80 | {:background-color "black" 81 | :opacity (if (-> state :transitionable (= :in)) 0.1 0) 82 | :transition "opacity 0.25s"})) 83 | 84 | (defmethod base ::dd/wrapper 85 | [props] 86 | (tu/style props 87 | {:display "inline-block" 88 | :position "relative"})) 89 | 90 | (defmethod main ::dd/body-wrapper 91 | [props] 92 | (let [{:keys [sm-2 sm-3 sm-6 shadow border background]} (-> props :re-com :variables)] 93 | (tu/style props {:background-color background 94 | :border-radius "4px" 95 | :border (str "thin solid " border) 96 | :padding sm-3 97 | :box-shadow (str/join " " [sm-2 sm-2 sm-6 shadow])}))) 98 | 99 | (defmethod main ::dd/body 100 | [props] 101 | (let [{:keys [foreground]} (-> props :re-com :variables)] 102 | (tu/style props {:color foreground}))) 103 | 104 | (defmethod main ::dd/anchor 105 | [{:keys [state] 106 | {$ :variables} :re-com 107 | :as props}] 108 | (tu/style props 109 | (cond-> {:color (:foreground $) 110 | :overflow "hidden" 111 | :text-overflow "ellipsis" 112 | :white-space "nowrap"} 113 | (-> state :enable (= :disabled)) 114 | (merge {:background-color (:background-disabled $)})))) 115 | -------------------------------------------------------------------------------- /src/re_com/error_modal/theme.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.error-modal.theme 2 | (:require 3 | [re-com.util :refer [px]] 4 | [re-com.theme.util :refer [merge-props]] 5 | [re-com.theme.default :refer [main]] 6 | [re-com.error-modal :as-alias em])) 7 | 8 | (defmethod main ::em/modal 9 | [props] 10 | (merge-props props 11 | {:wrap-nicely? false 12 | :style {:z-index 50}})) 13 | 14 | (defmethod main ::em/inner-wrapper 15 | [{:as props 16 | {$ :variables} :re-com}] 17 | (merge-props props 18 | {:style {:background-color (:white $) 19 | :box-shadow "2.82843px 2.82843px 4px rgba(1,1,1,0.2)" 20 | :font-size (:font-size/medium $) 21 | :min-width (px 474) 22 | :min-height (px 300) 23 | :max-width (px 525)}})) 24 | 25 | (defmethod main ::em/top-bar 26 | [{:as props 27 | {{:keys [severity]} :error-modal 28 | {:keys [md-2 sm-6] :as $} :variables} :re-com}] 29 | (merge-props props 30 | {:justify :between 31 | :align :center 32 | :style {:background-color (case severity 33 | :error (:error $) 34 | :warning (:warning $) 35 | "#1e1e1e") 36 | :color "#FFFFFF" 37 | :padding-left md-2 38 | :padding-right sm-6} 39 | :height (px 50)})) 40 | 41 | (defmethod main ::em/title-wrapper 42 | [{:as props {$ :variables} :re-com}] 43 | (merge-props props 44 | {:style {:font-size 25 45 | :color (:white $) 46 | :padding 0 47 | :margin "0px"}})) 48 | 49 | (defmethod main ::em/triangle 50 | [{:as props 51 | {{:keys [severity]} :error-modal 52 | $ :variables} :re-com}] 53 | (merge-props props 54 | {:style {:fill (case severity 55 | :error (:error $) 56 | :warning (:warning $) 57 | "#1e1e1e")}})) 58 | 59 | (defmethod main ::em/code 60 | [{:as props {$ :variables} :re-com}] 61 | (merge-props props 62 | {:style {:font-family "monospace" 63 | :white-space "pre-wrap" 64 | :font-size :font-size/xx-small 65 | :color (:neutral $)}})) 66 | 67 | (defmethod main ::em/body 68 | [{:as props {{:keys [md-2 sm-4]} :variables} :re-com}] 69 | (merge-props props 70 | {:style {:padding (str sm-4 " " md-2)}})) 71 | -------------------------------------------------------------------------------- /src/re_com/horizontal_tabs.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.horizontal-tabs 2 | (:require-macros 3 | [re-com.validate :as v]) 4 | (:require 5 | [re-com.args :as args] 6 | [re-com.config :as conf] 7 | [re-com.util :as u] 8 | [re-com.part :as p] 9 | [re-com.debug :as debug] 10 | [re-com.theme :as theme] 11 | [re-com.validate :refer [vector-of-maps?]] 12 | [re-com.horizontal-tabs.theme])) 13 | 14 | (def part-structure 15 | [::wrapper {:tag :ul} 16 | [::tab {:tag :li 17 | :multiple? true} 18 | [::anchor {:top-level-arg? true}]]]) 19 | 20 | (def parts-desc 21 | (when conf/include-args-desc? (p/describe part-structure))) 22 | 23 | (def part-names 24 | (when conf/include-args-desc? (set (map :name parts-desc)))) 25 | 26 | (def args-desc 27 | (when conf/include-args-desc? 28 | [{:name :model :required true :type "unique-id | r/atom" :description "the unique identifier of the currently selected tab"} 29 | {:name :disabled? :required false :type "boolean | r/atom" :description "disables all tabs."} 30 | {:name :tabs :required true :type "vector of tabs | r/atom" :validate-fn vector-of-maps? :description "one element in the vector for each tab. Typically, each element is a map with :id and :label keys"} 31 | {:name :on-change :required true :type "unique-id -> nil" :validate-fn fn? :description "called when user alters the selection. Passed the unique identifier of the selection"} 32 | {:name :id-fn :required false :default :id :type "tab -> anything" :validate-fn ifn? :description [:span "given an element of " [:code ":tabs"] ", returns its unique identifier (aka id)"]} 33 | {:name :label-fn :required false :default :label :type "tab -> string | hiccup" :validate-fn ifn? :description [:span "given an element of " [:code ":tabs"] ", returns its displayable label"]} 34 | args/class 35 | (assoc args/style :description [:span "Applies to the " [:code ":anchor"] " part."]) 36 | args/attr 37 | (args/parts part-names) 38 | args/src 39 | args/debug-as])) 40 | 41 | (defn horizontal-tabs [& {:keys [theme pre-theme]}] 42 | (let [theme (theme/comp theme pre-theme)] 43 | (fn [& {:keys [model tabs on-change id-fn label-fn disabled? tab-type] 44 | :or {id-fn :id 45 | label-fn :label 46 | tab-type :horizontal} 47 | :as props}] 48 | (or 49 | (v/validate-args-macro args-desc props) 50 | (let [model (u/deref-or-value model) 51 | tabs (u/deref-or-value tabs) 52 | disabled? (u/deref-or-value disabled?) 53 | _ (assert (not-empty (filter #(= model (id-fn %)) tabs)) "model not found in tabs vector") 54 | part (partial p/part part-structure props)] 55 | (part ::wrapper 56 | {:theme theme 57 | :post-props (-> (select-keys props [:class :attr]) 58 | (update :attr (merge (debug/->attr props)))) 59 | :props {:on-change on-change 60 | :tab-type tab-type 61 | :re-com {:state {:enable (not disabled?)}} 62 | :tag :ul 63 | :children 64 | (for [t tabs 65 | :let [{:keys [disabled?] 66 | :or {disabled? disabled?}} 67 | t 68 | id (id-fn t) 69 | label (label-fn t) 70 | selected? (= id model) 71 | tab-state {:enable (if disabled? :disabled :enabled) 72 | :selectable (if selected? :selected :unselected)} 73 | tab-props {:id id 74 | :tab-type tab-type 75 | :label label 76 | :re-com {:state tab-state} 77 | :on-change on-change}]] 78 | (part ::tab 79 | {:key t 80 | :theme theme 81 | :props 82 | (merge tab-props 83 | {:tag :li 84 | :children 85 | [(part ::anchor 86 | {:theme theme 87 | :post-props (select-keys props [:style]) 88 | :props (merge tab-props 89 | {:tag :a 90 | :children [label]})})]})}))}})))))) 91 | -------------------------------------------------------------------------------- /src/re_com/horizontal_tabs/theme.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.horizontal-tabs.theme 2 | (:require-macros 3 | [re-com.core :refer [handler-fn]]) 4 | (:require 5 | [re-com.dropdown :as-alias dd] 6 | [re-com.theme.util :as tu] 7 | [re-com.box :refer [flex-child-style]] 8 | [re-com.horizontal-tabs :as-alias ht] 9 | [re-com.horizontal-bar-tabs :as-alias hbt] 10 | [re-com.theme.default :refer [bootstrap base]])) 11 | 12 | (defmethod bootstrap ::ht/wrapper [props] 13 | (tu/class props "nav" "nav-tabs" "noselect" "rc-tabs")) 14 | 15 | (defmethod base ::ht/wrapper [props] 16 | (tu/style props (flex-child-style "none"))) 17 | 18 | (defmethod bootstrap ::ht/tab [{:keys [tab-type] 19 | {{:keys [enable selectable]} :state} :re-com 20 | :as props}] 21 | (case tab-type 22 | :horizontal 23 | (tu/class props 24 | (when (= :disabled enable) "disabled") 25 | (when (= :selected selectable) ["active" "rc-tab"])))) 26 | 27 | (defmethod base ::ht/anchor [{{{:keys [enable selectable]} :state} :re-com 28 | :keys [id on-change] 29 | :as props}] 30 | (cond-> props 31 | :do (tu/style {:cursor "pointer"}) 32 | (and (= :enabled enable) 33 | (= :unselected selectable)) (tu/attr {:on-click (handler-fn (on-change id))}))) 34 | -------------------------------------------------------------------------------- /src/re_com/nested_grid/parts.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.nested-grid.parts 2 | (:require 3 | [reagent.core :as r] 4 | [re-com.nested-grid.util :as ngu])) 5 | 6 | (defn box-style [position] 7 | (case position 8 | :top {:top -2 :left 0 :height 5 :width "100%"} 9 | :bottom {:bottom -3 :left 0 :height 5 :width "100%"} 10 | :left {:top 0 :left -2 :width 5 :height "100%"} 11 | :right {:top 0 :right -3 :width 5 :height "100%"})) 12 | 13 | (defn header-label [{:keys [path]}] 14 | (let [spec (peek path)] 15 | [:div 16 | (or (:label spec) 17 | (some-> spec :id str) 18 | (some-> spec str))])) 19 | 20 | (def row-header-label header-label) 21 | 22 | (def column-header-label header-label) 23 | 24 | (defn grid-line-button [_] 25 | (let [hover? (r/atom nil)] 26 | (fn [{:keys [on-mouse-down position style]}] 27 | [:div {:style (merge {:position :absolute 28 | :cursor (case position 29 | (:left :right) :col-resize 30 | (:top :bottom) :row-resize) 31 | :background "rgba(0,0,0,0.1)" 32 | :box-shadow "0 0 4px rgba(0,0,0,0.1)" 33 | :opacity (if @hover? 1 0)} 34 | (box-style position) 35 | style) 36 | :on-mouse-down on-mouse-down 37 | :on-mouse-over #(reset! hover? true) 38 | :on-mouse-out #(reset! hover? false)}]))) 39 | 40 | (defn drag-overlay [{:keys [x-start y-start on-mouse-move on-mouse-up size-dimension]}] 41 | (fn [_] 42 | [:div {:on-mouse-up on-mouse-up 43 | :on-mouse-move #(let [x (.-clientX %) 44 | y (.-clientY %)] 45 | (.preventDefault %) 46 | (on-mouse-move 47 | {:x x :y y 48 | :x-start x-start :y-start y-start 49 | :dx (- x x-start) :dy (- y y-start)})) 50 | :style {:position "fixed" 51 | :top 0 52 | :left 0 53 | :z-index 2147483647 54 | :height "100%" 55 | :width "100%" 56 | :cursor (case size-dimension :width :col-resize :height :row-resize :grab) 57 | #_#_:background-color "rgba(255,0,0,0.4)"}}])) 58 | 59 | (defn resizer [{:keys [path keypath size offset overlay on-resize index header-dimension size-dimension style]}] 60 | [:div {:class "rc-nested-grid-resizer" 61 | :style (merge 62 | {:position :relative} 63 | (case size-dimension 64 | :width {:grid-row-start 1 65 | :grid-row-end -1 66 | :width 0 67 | :margin-left (+ size offset)} 68 | :height {:grid-column-start 1 69 | :grid-column-end -1 70 | :height 0 71 | :margin-top (+ size offset)}) 72 | (case [header-dimension size-dimension] 73 | [:column :width] {:grid-column-start (ngu/keypath->grid-line-name keypath)} 74 | [:column :height] {:grid-row-start (inc index)} 75 | [:row :height] {:grid-row-start (ngu/keypath->grid-line-name keypath)} 76 | [:row :width] {:grid-column-start (inc index)}) 77 | style)} 78 | [grid-line-button 79 | {:position (case size-dimension :width :right :height :bottom) 80 | :on-mouse-down (fn [e] 81 | (reset! overlay [drag-overlay 82 | {:x-start (.-clientX e) 83 | :y-start (.-clientY e) 84 | :header-dimension header-dimension 85 | :size-dimension :size-dimension 86 | :on-mouse-up #(reset! overlay nil) 87 | :on-mouse-move (fn [{:keys [dx dy]}] 88 | (on-resize {:header-dimension header-dimension 89 | :size-dimension size-dimension 90 | :keypath keypath 91 | :size (-> (case size-dimension :width dx :height dy) 92 | (+ size) 93 | (max 10))}))}]))}]]) 94 | -------------------------------------------------------------------------------- /src/re_com/nested_grid/theme.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.nested-grid.theme 2 | (:require 3 | [re-com.theme.default :as default :refer [base main]] 4 | [re-com.nested-grid :as-alias ng])) 5 | 6 | ;; NOTE: See re-com.css for styling of cells & headers 7 | 8 | (def border-light "thin solid #ccc") 9 | (def border-dark "thin solid #aaa") 10 | 11 | (defn style [props & ms] (apply update props :style merge ms)) 12 | 13 | (defmethod base ::ng/wrapper [{:keys [sticky-child?] :as props}] 14 | (style props {:overflow (when-not sticky-child? :auto) 15 | :flex "0 0 auto" 16 | :display :grid})) 17 | 18 | (defmethod base ::ng/cell-grid 19 | [props] 20 | (style props {:display :grid 21 | :overflow :hidden 22 | :grid-row-start 2 23 | :grid-column-start 2})) 24 | 25 | (defmethod base ::ng/column-header-grid 26 | [{:keys [sticky-top] :or {sticky-top 0} :as props}] 27 | (style props {:display :grid 28 | :overflow :hidden 29 | :position :sticky 30 | :top sticky-top})) 31 | 32 | (defmethod base ::ng/row-header-grid 33 | [{:keys [sticky-left] :or {sticky-left 0} :as props}] 34 | (style props {:display :grid 35 | :position :sticky 36 | :left sticky-left})) 37 | 38 | (defmethod base ::ng/corner-header-grid 39 | [{:keys [sticky-left sticky-top] 40 | :or {sticky-left 0 41 | sticky-top 0} 42 | :as props}] 43 | (style props {:position :sticky 44 | :display :grid 45 | :grid-row-start 1 46 | :grid-column-start 1 47 | :left sticky-left 48 | :top sticky-top})) 49 | 50 | (defmethod main ::ng/corner-header 51 | [{:keys [edge] :as props}] 52 | (style props {} 53 | (when (edge :top) {:border-top border-light}) 54 | (when (edge :right) {:border-right border-light}) 55 | (when (edge :bottom) {:border-bottom border-light}) 56 | (when (edge :left) {:border-left border-light}))) 57 | -------------------------------------------------------------------------------- /src/re_com/pill_tabs.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.pill-tabs 2 | (:require-macros 3 | [re-com.core :refer [at handler-fn]] 4 | [re-com.validate :as v]) 5 | (:require 6 | [re-com.validate :as v] 7 | [reagent.core :as r] 8 | [re-com.config :as conf] 9 | [re-com.util :as u] 10 | [re-com.part :as p] 11 | [re-com.popover :as po] 12 | [re-com.debug :as debug] 13 | [re-com.theme :as theme] 14 | [re-com.horizontal-tabs :as horizontal-tabs] 15 | [re-com.pill-tabs.theme])) 16 | 17 | (def part-structure 18 | [::wrapper {:tag :ul} 19 | [::tab {:tag :li 20 | :multiple? true} 21 | [::anchor {:top-level-arg? true}]]]) 22 | 23 | (def parts-desc 24 | (when conf/include-args-desc? (p/describe part-structure))) 25 | 26 | (def part-names 27 | (when conf/include-args-desc? (set (map :name parts-desc)))) 28 | 29 | (def args-desc 30 | (when conf/include-args-desc? 31 | (-> 32 | (remove (comp #{:parts} :name) horizontal-tabs/args-desc) 33 | vec 34 | (conj 35 | {:name :vertical? :type "boolean" :default false} 36 | {:name :parts :required false :type "map" :validate-fn (v/parts? part-names) :description "See Parts section below."})))) 37 | 38 | (defn pill-tabs [& {:keys [theme pre-theme]}] 39 | (let [theme (theme/comp theme pre-theme)] 40 | (fn [& {:keys [model tabs on-change id-fn label-fn disabled? tab-type vertical?] 41 | :or {id-fn :id 42 | label-fn :label 43 | tab-type :horizontal} 44 | :as props}] 45 | (or 46 | (v/validate-args-macro args-desc props) 47 | (let [model (u/deref-or-value model) 48 | tabs (u/deref-or-value tabs) 49 | disabled? (u/deref-or-value disabled?) 50 | _ (assert (not-empty (filter #(= model (id-fn %)) tabs)) "model not found in tabs vector") 51 | part (partial p/part part-structure props)] 52 | (part ::wrapper 53 | {:theme theme 54 | :post-props (-> (select-keys props [:class :attr]) 55 | (update :attr (merge (debug/->attr props)))) 56 | :props {:on-change on-change 57 | :vertical? vertical? 58 | :tab-type tab-type 59 | :re-com {:state {:enable (not disabled?)}} 60 | :tag :ul 61 | :children 62 | (for [t tabs 63 | :let [{:keys [disabled?] 64 | :or {disabled? disabled?}} 65 | t 66 | id (id-fn t) 67 | label (label-fn t) 68 | selected? (= id model) 69 | tab-state {:enable (if disabled? :disabled :enabled) 70 | :selectable (if selected? :selected :unselected)} 71 | tab-props {:id id 72 | :tab-type tab-type 73 | :label label 74 | :re-com {:state tab-state} 75 | :on-change on-change}]] 76 | (part ::tab 77 | {:key t 78 | :theme theme 79 | :props 80 | (merge tab-props 81 | {:tag :li 82 | :children 83 | [(part ::anchor 84 | {:theme theme 85 | :post-props (select-keys props [:style]) 86 | :props (merge tab-props 87 | {:tag :a 88 | :children [label]})})]})}))}})))))) 89 | 90 | (defn vertical-pill-tabs [& {:as props}] 91 | [pill-tabs (assoc props :vertical? true)]) 92 | 93 | (def horizontal-pill-tabs pill-tabs) 94 | -------------------------------------------------------------------------------- /src/re_com/pill_tabs/theme.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.pill-tabs.theme 2 | (:require-macros 3 | [re-com.core :refer [handler-fn]]) 4 | (:require 5 | [re-com.dropdown :as-alias dd] 6 | [re-com.theme.util :as tu] 7 | [re-com.box :refer [flex-child-style]] 8 | [re-com.horizontal-tabs :as-alias ht] 9 | [re-com.pill-tabs :as-alias pt] 10 | [re-com.theme.default :refer [bootstrap base]])) 11 | 12 | (defmethod bootstrap ::pt/wrapper [{:keys [vertical?] :as props}] 13 | (tu/class props 14 | "rc-tabs" 15 | "noselect" 16 | "nav" 17 | "nav-pills" 18 | (when vertical? "nav-stacked"))) 19 | 20 | (defmethod base ::pt/wrapper [props] 21 | (-> props 22 | (assoc :role "tabslist") 23 | (tu/style (flex-child-style "none")))) 24 | 25 | 26 | (defmethod bootstrap ::pt/tab [{:keys [tab-type] 27 | {{:keys [enable selectable]} :state} :re-com 28 | :as props}] 29 | (case tab-type 30 | :horizontal 31 | (tu/class props 32 | (when (= :disabled enable) "disabled") 33 | (when (= :selected selectable) ["active"])))) 34 | 35 | (defmethod base ::pt/anchor [{{{:keys [enable selectable]} :state} :re-com 36 | :keys [id on-change] 37 | :as props}] 38 | (cond-> props 39 | :do (tu/style {:cursor "pointer"}) 40 | (and (= :enabled enable) 41 | (= :unselected selectable)) (tu/attr {:on-click (handler-fn (on-change id))}))) 42 | 43 | -------------------------------------------------------------------------------- /src/re_com/progress_bar.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.progress-bar 2 | (:require-macros 3 | [re-com.core :refer [handler-fn at reflect-current-component]] 4 | [re-com.validate :refer [validate-args-macro]]) 5 | (:require 6 | [re-com.config :refer [include-args-desc?]] 7 | [re-com.debug :refer [->attr]] 8 | [re-com.theme :as theme] 9 | [re-com.util :refer [deref-or-value px]] 10 | [re-com.popover :refer [popover-tooltip]] 11 | [re-com.box :refer [h-box v-box box gap line flex-child-style align-style]] 12 | [re-com.validate :refer [input-status-type? input-status-types-list regex? string-or-hiccup? css-style? html-attr? parts? 13 | number-or-string? string-or-atom? nillable-string-or-atom? throbber-size? throbber-sizes-list css-class?]])) 14 | 15 | ;; ------------------------------------------------------------------------------------ 16 | ;; Component: progress-bar 17 | ;; ------------------------------------------------------------------------------------ 18 | 19 | (def progress-bar-parts-desc 20 | (when include-args-desc? 21 | [{:name :wrapper :level 0 :class "rc-progress-bar-wrapper" :impl "[progress-bar]" :notes "Outer wrapper of the progress bar."} 22 | {:type :legacy :level 1 :class "rc-progress-bar" :impl "[:div]" :notes "The container for the progress bar."} 23 | {:type :legacy :level 2 :class "rc-progress-bar-portion" :impl "[:div]" :notes "The portion of the progress bar complete so far." 24 | :name-label [:span "Use " [:code ":bar-class"] " instead."]}])) 25 | 26 | (def progress-bar-parts 27 | (when include-args-desc? 28 | (-> (map :name progress-bar-parts-desc) set))) 29 | 30 | (def progress-bar-args-desc 31 | (when include-args-desc? 32 | [{:name :model :required true :type "double | string | r/atom" :validate-fn number-or-string? :description "current value of the slider. A number between 0 and 100"} 33 | {:name :width :required false :default "100%" :type "string" :validate-fn string? :description "a CSS width"} 34 | {:name :striped? :required false :default false :type "boolean" :description "when true, the progress section is a set of animated stripes"} 35 | {:name :bar-class :required false :type "string" :validate-fn string? :description "CSS class name(s) for the actual progress bar itself, space separated"} 36 | {:name :class :required false :type "string" :validate-fn css-class? :description "CSS class names, space separated (applies to the progress-bar, not the wrapping div)"} 37 | {:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override (applies to the progress-bar, not the wrapping div)"} 38 | {:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed (applies to the progress-bar, not the wrapping div)"]} 39 | {:name :parts :required false :type "map" :validate-fn (parts? progress-bar-parts) :description "See Parts section below."} 40 | {:name :src :required false :type "map" :validate-fn map? :description [:span "Used in dev builds to assist with debugging. Source code coordinates map containing keys" [:code ":file"] "and" [:code ":line"] ". See 'Debugging'."]} 41 | {:name :debug-as :required false :type "map" :validate-fn map? :description [:span "Used in dev builds to assist with debugging, when one component is used implement another component, and we want the implementation component to masquerade as the original component in debug output, such as component stacks. A map optionally containing keys" [:code ":component"] "and" [:code ":args"] "."]}])) 42 | 43 | (defn progress-bar 44 | "Render a bootstrap styled progress bar" 45 | [& {:keys [model width striped? class bar-class style attr parts src debug-as] 46 | :or {width "100%"} 47 | :as args}] 48 | (or 49 | (validate-args-macro progress-bar-args-desc args) 50 | (let [model (deref-or-value model)] 51 | [box 52 | :src src 53 | :debug-as (or debug-as (reflect-current-component)) 54 | :class (str "rc-progress-bar-wrapper " (get-in parts [:wrapper :class])) 55 | :style (get-in parts [:wrapper :style]) 56 | :attr (get-in parts [:wrapper :attr]) 57 | :align :start 58 | :child [:div 59 | (merge 60 | {:class (theme/merge-class "progress" "rc-progress-bar" class) 61 | :style (merge (flex-child-style "none") 62 | {:width width} 63 | style)} 64 | attr) 65 | [:div 66 | {:class (let [striped? true bar-class "hi"] 67 | (theme/merge-class "progress-bar" 68 | (when striped? 69 | ["progress-bar-striped" 70 | "active" 71 | "rc-progress-bar-portion"]) 72 | bar-class)) 73 | :role "progressbar" 74 | :style {:width (str model "%") 75 | :transition "none"}} ;; Default BS transitions cause the progress bar to lag behind 76 | (str model "%")]]]))) 77 | -------------------------------------------------------------------------------- /src/re_com/tabs.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.tabs 2 | (:require 3 | [re-com.core :as rc] 4 | [re-com.horizontal-tabs :as horizontal-tabs] 5 | [re-com.bar-tabs :as bar-tabs] 6 | [re-com.pill-tabs :as pill-tabs])) 7 | 8 | (def horizontal-tabs horizontal-tabs/horizontal-tabs) 9 | (def bar-tabs bar-tabs/bar-tabs) 10 | (def horizontal-bar-tabs bar-tabs/horizontal-bar-tabs) 11 | (def vertical-bar-tabs bar-tabs/vertical-bar-tabs) 12 | (def pill-tabs pill-tabs/pill-tabs) 13 | (def horizontal-pill-tabs pill-tabs/horizontal-pill-tabs) 14 | (def vertical-pill-tabs pill-tabs/vertical-pill-tabs) 15 | -------------------------------------------------------------------------------- /src/re_com/theme.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.theme 2 | (:refer-clojure :exclude [comp]) 3 | (:require 4 | [re-com.theme.util :as tu] 5 | [re-com.part :as part] 6 | [re-com.theme.default :as theme.default])) 7 | 8 | (def ^:dynamic *variables* theme.default/variables) 9 | (def ^:dynamic *pre-user* theme.default/main) 10 | (def ^:dynamic *bootstrap* theme.default/bootstrap) 11 | (def ^:dynamic *base* theme.default/base) 12 | (def ^:dynamic *main* theme.default/main) 13 | (def ^:dynamic *user* nil) 14 | 15 | (def args-desc 16 | [{:name :theme 17 | :validate-fn ifn? 18 | :description [:span "See the theme section of re-com docs (TBD). " 19 | "This argument is not reactive."]} 20 | {:name :pre-theme 21 | :validate-fn ifn? 22 | :description [:span "See the theme section of re-com docs (TBD). " 23 | "This argument is not reactive."]}]) 24 | 25 | (def merge-class tu/merge-class) 26 | 27 | (def merge-props tu/merge-props) 28 | 29 | (defn re-com-meta [{:keys [part] :as props}] 30 | (tu/class props (part/css-class* part))) 31 | 32 | (defn comp [component-local-pre-theme component-local-theme] 33 | (clojure.core/apply 34 | clojure.core/comp 35 | (filter some? [component-local-theme 36 | re-com-meta 37 | *user* 38 | *main* 39 | *bootstrap* 40 | *base* 41 | component-local-pre-theme 42 | *pre-user* 43 | *variables*]))) 44 | -------------------------------------------------------------------------------- /src/re_com/theme/blue_modern.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.theme.blue-modern 2 | (:require 3 | [re-com.theme :as theme] 4 | [re-com.text :as text] 5 | [re-com.dropdown :as dropdown] 6 | [re-com.tree-select :as tree-select] 7 | [re-com.error-modal :as-alias error-modal])) 8 | 9 | (defn theme [{:as props 10 | :keys [part] 11 | {$ :variables} :re-com}] 12 | (->> (case part 13 | ::dropdown/anchor-wrapper 14 | {:style {:font-size :small 15 | :height "25px"}} 16 | ::dropdown/indicator 17 | {:style {:color (:light-foreground $)}} 18 | {}) 19 | (theme/merge-props props))) 20 | -------------------------------------------------------------------------------- /src/re_com/theme/default.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.theme.default 2 | (:require 3 | [re-com.util :as ru :refer [px]] 4 | [re-com.dropdown :as-alias dropdown] 5 | [re-com.error-modal :as-alias error-modal] 6 | [re-com.tree-select :as-alias tree-select])) 7 | 8 | (def golden-section-50 9 | {:sm-1 "1px" 10 | :sm-2 "2px" 11 | :sm-3 "3px" 12 | :sm-4 "5px" 13 | :sm-5 "7px" 14 | :sm-6 "12px" 15 | :md-1 "19px" 16 | :md-2 "31px" 17 | :md-3 "50px" 18 | :md-4 "81px" 19 | :md-5 "131px" 20 | :md-6 "212px" 21 | :lg-1 "343px" 22 | :lg-2 "554px" 23 | :lg-3 "897px" 24 | :lg-4 "1452px" 25 | :lg-5 "2349px" 26 | :lg-6 "3800px" 27 | :half "25px" 28 | :double "100px"}) 29 | 30 | (def colors 31 | {:primary "#0d6efd" 32 | :secondary "#6c757d" 33 | :success "#198754" 34 | :info "#0dcaf0" 35 | :error "#bf1010" 36 | :warning "#ffc107" 37 | :danger "#dc3545" 38 | :light "#f8f9fa" 39 | :white "#ffffff" 40 | :dark "#212529" 41 | :black "#000000" 42 | :neutral "#555555" 43 | :foreground "#767a7c" 44 | :light-background "#eee" 45 | :light-foreground "#ccc" 46 | :background "white" 47 | :background-disabled "#EEE" 48 | :border "#cccccc" 49 | :border-dark "#aaa" 50 | :shadow "rgba(0, 0, 0, 0.2)"}) 51 | 52 | (def font-sizes 53 | {:font-size/xx-small (px 11) 54 | :font-size/x-small (px 12) 55 | :font-size/small (px 13) 56 | :font-size/medium (px 14) 57 | :font-size/large (px 15) 58 | :font-size/x-large (px 16) 59 | :font-size/xx-large (px 17)}) 60 | 61 | (def static-variables (merge colors golden-section-50 font-sizes)) 62 | 63 | (defn variables [props] 64 | (assoc-in props [:re-com :variables] static-variables)) 65 | 66 | (defmulti base :part) 67 | 68 | (defmethod base :default [props] props) 69 | 70 | (defmulti main :part) 71 | 72 | (defmethod main :default [props] props) 73 | 74 | (defmethod base :default [props] props) 75 | 76 | (defmulti bootstrap :part) 77 | 78 | (defmethod bootstrap :default [props] props) 79 | -------------------------------------------------------------------------------- /src/re_com/theme/util.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.theme.util) 2 | 3 | (defn merge-class [& classes] 4 | (filterv some? (flatten classes))) 5 | 6 | (defn style [props & styles] 7 | (apply update props :style merge styles)) 8 | 9 | (defn attr [props & attrs] 10 | (apply update props :attr merge attrs)) 11 | 12 | (defn class [props & classes] 13 | (apply update props :class merge-class classes)) 14 | 15 | (defn ->v [x] (cond (vector? x) x 16 | (sequential? x) (vec x) 17 | (nil? x) nil 18 | :else [x])) 19 | 20 | (defn merge-props-rf [acc {:keys [class attr style] :as m}] 21 | (merge acc (cond-> (if-not (string? m) m {:style [m]}) 22 | (contains? m :class) 23 | (assoc :class (into (->v (:class acc)) (->v class))) 24 | (contains? m :attr) 25 | (assoc :attr (merge (:attr acc) attr)) 26 | (contains? m :style) 27 | (assoc :style (merge (:style acc) style))))) 28 | 29 | (defn merge-props [& ms] (reduce merge-props-rf {} ms)) 30 | 31 | -------------------------------------------------------------------------------- /src/re_com/throbber.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.throbber 2 | (:require-macros 3 | [re-com.core :refer [handler-fn at reflect-current-component]] 4 | [re-com.validate :refer [validate-args-macro]]) 5 | (:require 6 | [re-com.config :refer [include-args-desc?]] 7 | [re-com.debug :refer [->attr]] 8 | [re-com.theme :as theme] 9 | [re-com.util :refer [deref-or-value px]] 10 | [re-com.popover :refer [popover-tooltip]] 11 | [re-com.box :refer [h-box v-box box gap line flex-child-style align-style]] 12 | [re-com.validate :refer [input-status-type? input-status-types-list regex? string-or-hiccup? css-style? html-attr? parts? 13 | number-or-string? string-or-atom? nillable-string-or-atom? throbber-size? throbber-sizes-list css-class?]])) 14 | 15 | ;; ------------------------------------------------------------------------------------ 16 | ;; Component: throbber 17 | ;; ------------------------------------------------------------------------------------ 18 | 19 | (def throbber-parts-desc 20 | (when include-args-desc? 21 | [{:name :wrapper :level 0 :class "rc-throbber-wrapper" :impl "[throbber]" :notes "Outer wrapper of the throbber."} 22 | {:type :legacy :level 1 :class "rc-throbber" :impl "[:ul]" :notes "The throbber."} 23 | {:name :segment :level 2 :class "rc-throbber-segment" :impl "[:li]" :notes "Repeated eight times. Each represents one of the eight circles in the throbber."}])) 24 | 25 | (def throbber-parts 26 | (when include-args-desc? 27 | (-> (map :name throbber-parts-desc) set))) 28 | 29 | (def throbber-args-desc 30 | (when include-args-desc? 31 | [{:name :size :required false :default :regular :type "keyword" :validate-fn throbber-size? :description [:span "one of " throbber-sizes-list]} 32 | {:name :color :required false :default "#999" :type "string" :validate-fn string? :description "CSS color"} 33 | {:name :class :required false :type "string" :validate-fn css-class? :description "CSS class names, space separated (applies to the throbber, not the wrapping div)"} 34 | {:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override (applies to the throbber, not the wrapping div)"} 35 | {:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed (applies to the throbber, not the wrapping div)"]} 36 | {:name :parts :required false :type "map" :validate-fn (parts? throbber-parts) :description "See Parts section below."} 37 | {:name :src :required false :type "map" :validate-fn map? :description [:span "Used in dev builds to assist with debugging. Source code coordinates map containing keys" [:code ":file"] "and" [:code ":line"] ". See 'Debugging'."]} 38 | {:name :debug-as :required false :type "map" :validate-fn map? :description [:span "Used in dev builds to assist with debugging, when one component is used implement another component, and we want the implementation component to masquerade as the original component in debug output, such as component stacks. A map optionally containing keys" [:code ":component"] "and" [:code ":args"] "."]}])) 39 | 40 | (defn throbber 41 | "Render an animated throbber using CSS" 42 | [& {:keys [size color class style attr parts src debug-as] :as args}] 43 | (or 44 | (validate-args-macro throbber-args-desc args) 45 | (let [seg (fn [] 46 | [:li 47 | (merge 48 | {:class (theme/merge-class "rc-throbber-segment" 49 | (get-in parts [:segment :class])) 50 | :style (merge 51 | (when color {:background-color color}) 52 | (get-in parts [:segment :style]))} 53 | (get-in parts [:segment :attr]))])] 54 | [box 55 | :src src 56 | :debug-as (or debug-as (reflect-current-component)) 57 | :class (str "rc-throbber-wrapper " (get-in parts [:wrapper :class])) 58 | :style (get-in parts [:wrapper :style]) 59 | :attr (get-in parts [:wrapper :attr]) 60 | :align :start 61 | :child [:ul 62 | (merge {:class (theme/merge-class "loader rc-throbber" 63 | (case size 64 | :smaller "smaller" 65 | :small "small" 66 | :large "large" 67 | nil) 68 | class) 69 | :style style} 70 | attr) 71 | [seg] [seg] [seg] [seg] 72 | [seg] [seg] [seg] [seg]]]))) ;; Each :li element in [seg] represents one of the eight circles in the throbber 73 | -------------------------------------------------------------------------------- /src/re_com/tour.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.tour 2 | (:require-macros 3 | [re-com.core :refer [handler-fn at]]) 4 | (:require 5 | [reagent.core :as reagent] 6 | [re-com.box :refer [flex-child-style]] 7 | [re-com.buttons :refer [button]])) 8 | 9 | ;;-------------------------------------------------------------------------------------------------- 10 | ;; Component: tour 11 | ;; 12 | ;; Strings together 13 | ;;-------------------------------------------------------------------------------------------------- 14 | 15 | (defn make-tour 16 | "Returns a map containing 17 | - A reagent atom for each tour step controlling popover show/hide (boolean) 18 | - A standard atom holding the current step (integer) 19 | - A copy of the steps parameter passed in, to determine the order for prev/next functions 20 | It sets the first step atom to true so that it will be initially shown 21 | Sample return value: 22 | {:steps [:step1 :step2 :step3] 23 | :current-step (atom 0) 24 | :step1 (reagent/atom true) 25 | :step2 (reagent/atom false) 26 | :step3 (reagent/atom false)}" 27 | [tour-spec] 28 | (let [tour-map {:current-step (atom 0) :steps tour-spec}] ;; Only need normal atom 29 | 30 | (reduce #(assoc %1 %2 (reagent/atom false)) tour-map tour-spec))) ;; Old way: (merge {} (map #(hash-map % (reagent/atom false)) tour-spec)) 31 | 32 | (defn- initialise-tour 33 | "Resets all poover atoms to false" 34 | [tour] 35 | (doall (for [step (:steps tour)] (reset! (step tour) false)))) 36 | 37 | (defn start-tour 38 | "Sets the first popover atom in the tour to true" 39 | [tour] 40 | (initialise-tour tour) 41 | (reset! (:current-step tour) 0) 42 | (reset! ((first (:steps tour)) tour) true)) 43 | 44 | (defn finish-tour 45 | "Closes all tour popovers" 46 | [tour] 47 | (initialise-tour tour)) 48 | 49 | (defn- next-tour-step 50 | [tour] 51 | (let [steps (:steps tour) 52 | old-step @(:current-step tour) 53 | new-step (inc old-step)] 54 | (when (< new-step (count (:steps tour))) 55 | (reset! (:current-step tour) new-step) 56 | (reset! ((nth steps old-step) tour) false) 57 | (reset! ((nth steps new-step) tour) true)))) 58 | 59 | (defn- prev-tour-step 60 | [tour] 61 | (let [steps (:steps tour) 62 | old-step @(:current-step tour) 63 | new-step (dec old-step)] 64 | (when (>= new-step 0) 65 | (reset! (:current-step tour) new-step) 66 | (reset! ((nth steps old-step) tour) false) 67 | (reset! ((nth steps new-step) tour) true)))) 68 | 69 | (defn make-tour-nav 70 | "Generate the hr and previous/next buttons markup. 71 | If first button in tour, don't generate a Previous button. 72 | If last button in tour, change Next button to a Finish button" 73 | [tour] 74 | (let [on-first-button (= @(:current-step tour) 0) 75 | on-last-button (= @(:current-step tour) (dec (count (:steps tour))))] 76 | [:div 77 | [:hr {:style (merge (flex-child-style "none") 78 | {:margin "10px 0px 10px"})}] 79 | (when-not on-first-button 80 | [button 81 | :src (at) 82 | :label "Previous" 83 | :on-click (handler-fn (prev-tour-step tour)) 84 | :style {:margin-right "15px"} 85 | :class "btn-default rc-tour-btn-previous"]) 86 | [button 87 | :src (at) 88 | :label (if on-last-button "Finish" "Next") 89 | :on-click (handler-fn (if on-last-button 90 | (finish-tour tour) 91 | (next-tour-step tour))) 92 | :class (str "btn-default " (if on-last-button "rc-tour-btn-finish" "rc-tour-btn-next"))]])) 93 | -------------------------------------------------------------------------------- /src/re_com/tree_select/theme.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.tree-select.theme 2 | (:require 3 | [re-com.theme.util :as tu :refer [merge-props]] 4 | [re-com.tree-select :as-alias ts] 5 | [re-com.theme.default :refer [base main]])) 6 | 7 | (defmethod base ::ts/label 8 | [props] 9 | (update props :style merge 10 | {:white-space :nowrap 11 | :overflow :hidden 12 | :text-overflow :ellipsis})) 13 | 14 | (defmethod main ::ts/dropdown-anchor 15 | [{:keys [state] 16 | {$ :variables} :re-com 17 | :as props}] 18 | (tu/style props {:padding "0 0 0 0" 19 | :overflow "hidden" 20 | :color (:foreground $) 21 | :cursor (if (-> state :enable (= :disabled)) 22 | "default" "pointer")})) 23 | 24 | (defmethod main ::ts/dropdown-indicator 25 | [{{$ :variables} :re-com 26 | :as props}] 27 | (merge-props props {:align :center 28 | :style {:gap "5px" 29 | :color (:light-foreground $)}})) 30 | 31 | (defmethod main 32 | ::ts/dropdown-indicator-triangle 33 | [{{$ :variables} :re-com 34 | :as props}] 35 | (merge-props props 36 | {:align :center 37 | :style {:gap "5px" 38 | :color (:foreground $)}})) 39 | 40 | (defmethod main ::ts/dropdown-counter 41 | [props] 42 | (tu/style props 43 | {:style {#_#_:margin-left "5px" 44 | #_#_:margin-right "5px" 45 | :opacity "50%"}})) 46 | 47 | (defmethod base ::ts/only-button 48 | [{{{:keys [background]} :variables} :re-com :as props}] 49 | (tu/style props {:position :absolute 50 | :right 5 51 | :margin-bottom 2 52 | :background background})) 53 | -------------------------------------------------------------------------------- /src/re_com/validate.clj: -------------------------------------------------------------------------------- 1 | (ns re-com.validate) 2 | 3 | (defmacro validate-args-macro 4 | "if goog.DEBUG is true then validate the args, otherwise replace the validation code with nil" 5 | [args-desc args] 6 | `(if-not ~(vary-meta 'js/goog.DEBUG assoc :tag 'boolean) 7 | nil 8 | (re-com.validate/validate-args (re-com.validate/extract-arg-data ~args-desc) ~args))) 9 | -------------------------------------------------------------------------------- /src/re_demo/alert_list.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.alert-list 2 | (:require-macros 3 | [re-com.core :refer []]) 4 | (:require 5 | [re-com.core :refer [at h-box v-box box line gap label title button alert-box alert-list p]] 6 | [re-com.alert :refer [alert-list-parts-desc alert-box-args-desc alert-list-args-desc]] 7 | [re-com.util :refer [insert-nth remove-id-item px]] 8 | [re-demo.utils :refer [panel-title title2 title3 parts-table args-table github-hyperlink status-text]] 9 | [reagent.core :as reagent])) 10 | 11 | (defn add-alert 12 | [alerts id alert-type {:keys [heading body]}] 13 | (let [alert {:id id :alert-type alert-type :heading heading :body body :padding "8px" :closeable? true}] 14 | (reset! alerts (insert-nth @alerts 0 alert)))) 15 | 16 | (defn alert-list-demo 17 | [] 18 | (let [alerts (reagent/atom [])] 19 | (add-alert alerts 0 :danger {:heading "Woa! something bad happened" :body "Next time you should take more care pressing that button! Did you read the fine print? No, I didn't think so."}) 20 | (add-alert alerts 1 :info {:heading "No Wait!" :body "The rain in Spain often falls on the mountatins too."}) 21 | (add-alert alerts 2 :info {:heading "Here's some info" :body "The rain in Spain falls mainly on the plain."}) 22 | (add-alert alerts 3 :warning {:heading "\"Oh bother\", said Pooh. And then ..." :body "\"Some people care too much. I think it's called love.\""}) 23 | 24 | (fn [] 25 | [v-box 26 | :src (at) 27 | :size "auto" 28 | :gap "10px" 29 | :children [[panel-title "[alert-list ... ]" 30 | "src/re_com/alert.cljs" 31 | "src/re_demo/alert_list.cljs"] 32 | [h-box 33 | :src (at) 34 | :gap "100px" 35 | :children [[v-box 36 | :src (at) 37 | :gap "10px" 38 | :width "450px" 39 | :children [[title2 "Notes"] 40 | [status-text "Stable"] 41 | [p "Renders a dynamic list of alert-boxes vertically, with a scroll bar if necessary."] 42 | [args-table alert-list-args-desc]]] 43 | [v-box 44 | :src (at) 45 | :width "600px" 46 | :gap "10px" 47 | :children [[title2 "Demo"] 48 | [h-box 49 | :src (at) 50 | :gap "10px" 51 | :align :center 52 | :children [[label 53 | :src (at) 54 | :label "To insert alerts at the top of the list, click "] 55 | [button 56 | :src (at) 57 | :label "Add alert" 58 | :style {:width "100px"} 59 | :on-click #(add-alert alerts (gensym) :info {:heading "New alert" :body "This alert was added by the \"Add alert\" button."})]]] 60 | [p "Also, try clicking the \"x\" on alerts."] 61 | [p [:code ":max-height"] " is set to 300px. A scroll bar will appear as necessary."] 62 | [p "For demonstration purposes, a 'dotted' " [:code ":border-style"] " is set."] 63 | [alert-list 64 | :src (at) 65 | :alerts alerts 66 | :on-close #(reset! alerts (remove-id-item % @alerts)) 67 | :max-height "300px" 68 | :border-style "1px dashed lightgrey"]]]]] 69 | [parts-table "alert-list" alert-list-parts-desc]]]))) 70 | 71 | ;; need a level of indirection to get figwheel updates 72 | (defn panel 73 | [] 74 | [alert-list-demo]) 75 | -------------------------------------------------------------------------------- /src/re_demo/border.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.border 2 | (:require-macros 3 | [re-com.core :refer []]) 4 | (:require 5 | [re-com.core :refer [at h-box v-box box gap border p]] 6 | [re-com.box :refer [border-args-desc]] 7 | [re-demo.utils :refer [panel-title title2 args-table github-hyperlink status-text]])) 8 | 9 | (defn panel 10 | [] 11 | [v-box 12 | :src (at) 13 | :size "auto" 14 | :gap "10px" 15 | :children [[panel-title "[border ... ]" 16 | "src/re_com/box.cljs" 17 | "src/re_demo/border.cljs"] 18 | 19 | [h-box 20 | :src (at) 21 | :gap "100px" 22 | :children [[v-box 23 | :src (at) 24 | :gap "10px" 25 | :width "450px" 26 | :children [[title2 "Notes"] 27 | [status-text "Stable"] 28 | [p "Wraps a child component in a border."] 29 | [p "The border can be used at any level in a box hierarchy. For example, it could be a child of an h-box and its child could be a v-box."] 30 | [args-table border-args-desc]]] 31 | [v-box 32 | :src (at) 33 | :gap "10px" 34 | ; :size "0 0 auto" 35 | ;:align :start 36 | :children [[title2 "Demo"] 37 | [p "Here is some sample code..."] 38 | [:pre 39 | {:style {:width "40em"}} 40 | "[border 41 | :border \"1px dashed red\" 42 | :child [box :height \"100px\" :child \"Hello\"]]"] 43 | 44 | [p "Here is the result..."] 45 | [border 46 | :src (at) 47 | :border "1px dashed red" 48 | :child [box 49 | :src (at) 50 | :height "100px" 51 | :child "Hello"]]]]]] 52 | [gap 53 | :src (at) 54 | :size "30px"]]]) 55 | -------------------------------------------------------------------------------- /src/re_demo/box.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.box 2 | (:require-macros 3 | [re-com.core :refer []]) 4 | (:require 5 | [re-com.core :refer [at h-box v-box box gap hyperlink-href p]] 6 | [re-com.box :refer [box-args-desc]] 7 | [re-demo.utils :refer [panel-title title2 args-table github-hyperlink status-text]])) 8 | 9 | (defn panel 10 | [] 11 | [v-box 12 | :src (at) 13 | :size "auto" 14 | :gap "10px" 15 | :children [[panel-title "[box ... ]" 16 | "src/re_com/box.cljs" 17 | "src/re_demo/box.cljs"] 18 | 19 | [h-box 20 | :src (at) 21 | :gap "100px" 22 | :children [[v-box 23 | :src (at) 24 | :gap "10px" 25 | :width "450px" 26 | :children [[title2 "Notes"] 27 | [status-text "Stable"] 28 | [p "If you need to introduce leaf nodes into a layout and they are not 29 | already correctly styled for use as flexbox items, then wrap them in (make them the child of) a " [:span.bold "[box ..]"] "."] 30 | [p "Leaf nodes which need this treatment are typically block level elements from outside of re-com."] 31 | [p "Back in the beginning, when we first put bootstrap styled buttons into re-com, they were streched. Alarmingly. 32 | It turned out their block display didn't play well with flex containers like h-box."] 33 | [p "We fixed that, but then there were other problems like they 34 | wouldn't justify correctly in a container. All these issues arose because they 35 | were never designed (styled) to be the children of flexbox containers. That's why we created [box]"] 36 | 37 | [args-table box-args-desc]]] 38 | [v-box 39 | :src (at) 40 | :gap "10px" 41 | :children [[title2 "Demo"] 42 | [:span.all-small-caps "This space intentionally free of dark pixels."]]]]] 43 | [gap 44 | :src (at) 45 | :size "30px"]]]) 46 | -------------------------------------------------------------------------------- /src/re_demo/config.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.config 2 | (:require-macros 3 | [re-com.core :refer []]) 4 | (:require 5 | [re-com.core :refer [at h-box v-box gap line title p hyperlink-href]] 6 | [re-demo.utils :refer [panel-title title2 title3]])) 7 | 8 | (defn compiler-config 9 | [] 10 | [v-box 11 | :src (at) 12 | :children [[title2 "Compiler"] 13 | [line :src (at)] 14 | [gap :src (at) :size "20px"] 15 | [title3 "Production Builds"] 16 | [gap :src (at) :size "20px"] 17 | [p "The overhead of parameter validation is elided in production builds. This is based on " 18 | [:code "js/goog.DEBUG"] " being set to " [:code "false"] "."] 19 | [p [:code "js/goog.DEBUG"] " is automatically set by " [:code "shadow-cljs"] " to " [:code "false"] " for " 20 | [:code "release"] " builds."] 21 | [p "Other build systems may require you to manually add a " [:code ":closure-defines"] " compiler option:"] 22 | [:pre 23 | {:style {:width "450px"}} 24 | ":closure-defines {goog.DEBUG false}"] 25 | [gap :src (at) :size "20px"] 26 | [title3 "Source Code Links"] 27 | [gap :src (at) :size "20px"] 28 | [p "When re-com produces validation errors or component stacks (such as via the " [:code "[stack-spy ...]"] 29 | " component) it tries to provide links to source code. For these links to be displayed it requires that you provide the root URL to the ClojureScript compiler output with source maps:"] 30 | [:pre 31 | {:style {:width "450px"}} 32 | ":closure-defines {re-com.config/root-url-for-compiler-output \"http://localhost:3449/compiled_dev/demo/cljs-runtime/\"}"]]]) 33 | 34 | (defn tools 35 | [] 36 | [v-box 37 | :src (at) 38 | :children [[title2 "Tooling"] 39 | [line :src (at)] 40 | [gap :src (at) :size "20px"] 41 | [p "It is essential you have " 42 | [hyperlink-href :src (at) 43 | :href "https://github.com/binaryage/cljs-devtools/blob/master/docs/installation.md" 44 | :label "CLJS DevTools" 45 | :target "_blank"] 46 | " integrated with your project to enable correct formatting and navigation in re-com logging such as 'Parameters' in component stacks."]]]) 47 | 48 | (defn config-page 49 | [] 50 | [v-box 51 | :src (at) 52 | :gap "10px" 53 | :children [[panel-title 54 | "Config" 55 | "project.clj" 56 | "src/re_demo/config.cljs"] 57 | 58 | [v-box 59 | :src (at) 60 | :gap "100px" 61 | :children [[compiler-config] 62 | [tools]]]]]) 63 | 64 | ;; core holds a reference to panel, so we need one level of indirection to get hot realoading to work 65 | (defn panel 66 | [] 67 | [config-page]) 68 | -------------------------------------------------------------------------------- /src/re_demo/customization.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.customization 2 | (:require 3 | [re-com.core :as rc] 4 | [re-demo.utils :as rdu])) 5 | 6 | (defn basics [] 7 | [rc/v-box 8 | {:src (rc/at) 9 | :children 10 | [[rc/gap :size "19px"] 11 | [rc/h-box 12 | :gap "31px" 13 | :children 14 | [[rc/v-box 15 | :children 16 | [[rc/p "Most re-com components can be passed these named arguments:"] 17 | [rc/p 18 | [:ul 19 | [:li [:code [:strong ":style"]] " must be a map, representing CSS inline style rules."] 20 | [:li [:code [:strong ":class"]] " must be a string, or vector of strings, representing CSS class-names."] 21 | [:li [:code [:strong ":attr"]] " must be a map. It stands for " [:i "Html attributes"] ". " 22 | "These represent extra attributes, alongside class and style. " 23 | "For instance, " [:code ":on-click"] " or " [:code ":data-my-attribute"]] 24 | [:li [:code [:strong ":children"]] " must be a sequence of hiccups. " 25 | "Some components expect a single " [:code ":child"] ", instead."]]] 26 | [rc/p "When a re-com component returns a tree of hiccups, these arguments tend to apply to the outermost hiccup."]]] 27 | [rc/h-box 28 | :style {:height :fit-content :gap "12px"} 29 | :children 30 | (rdu/with-src 31 | [rc/h-box {:class "italic" 32 | :style {:border "thin dashed grey" 33 | :padding "2px"} 34 | :children ["Hello" "World"] 35 | :attr {:title "Hello"}}])]]]]}]) 36 | (defn composition [] 37 | [rc/v-box 38 | {:src (rc/at) 39 | :children 40 | [[rc/title {:src (rc/at) :level :level2 :label "Composition"}] 41 | [rc/h-box 42 | :gap "31px" 43 | :children 44 | [[rc/v-box 45 | :children 46 | [[rc/p 47 | "The above is enough in most cases, but sometimes we want to express UI with more: "] 48 | [rc/p 49 | [:ul 50 | [:li [:i "flexibility"] ": such as, applying design conditionally, based on some dynamic state."] 51 | [:li [:i "specificity"] ": such as, building custom view logic into one instance of a component. "] 52 | [:li [:i "parsimony"] ": such as, applying a design system over an entire app, "]]] 53 | [rc/p 54 | "Given a re-com component function, which returns a tree of hiccups, " 55 | "we need deeper mechanics we can use, both individually and in combination. " 56 | "The following pages describe ways to: "] 57 | [rc/p 58 | [:ul 59 | [:li "Replace an entire subtree (a " [:code ":parts"] " hiccup)."] 60 | [:li "Override a certain hiccup's arguments (a " [:code ":parts"] " map)."] 61 | [:li "Re-write a hiccup's component function (a " [:code ":parts"] " function)."] 62 | [:li "Wrap a hiccup's arguments with a function (a " [:code ":theme"] ")."] 63 | [:li "Register a function that wraps the arguments of all hiccups everywhere " 64 | "(a global " [:code ":theme"] ")."]]] 65 | [rc/p {:style {:background-color "#eee" :padding 7}} 66 | [:strong "Note"] ": Re-com has used " 67 | [:a {:href "https://bootstrapdocs.com/v3.3.5/docs/getting-started/"} "bootstrap 3.3.5"] 68 | " as a design system. A complete implementation of our theme system will make bootstrap optional. " 69 | "It should then be possible to plug in a different system, such as " 70 | [:a {:href "https://tailwindcss.com/"} "tailwind"] 71 | " or " 72 | [:a {:href "https://github.com/thheller/shadow-css"} "shadow-css."]]]] 73 | [rc/box 74 | :align :center 75 | :child [:img {:src "demo/architecture-LOD.jpg" 76 | :style {:width "400px" 77 | :height "auto"}}]]]]]}]) 78 | 79 | (defn panel* [] 80 | [rc/v-box 81 | {:children 82 | [[rdu/panel-title "Customization" nil "src/re_demo/customization.cljs"] 83 | [basics] 84 | [rc/line] 85 | [composition]]}]) 86 | 87 | (defn panel [] 88 | [panel*]) 89 | -------------------------------------------------------------------------------- /src/re_demo/dropdown.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.dropdown 2 | (:require-macros 3 | [re-com.core :refer []]) 4 | (:require 5 | [re-com.core :as rc :refer [at h-box v-box single-dropdown label hyperlink-href p p-span]] 6 | [re-com.dropdown :refer [dropdown-parts-desc dropdown-args-desc dropdown]] 7 | [re-demo.utils :refer [panel-title title2 title3 parts-table args-table status-text prop-slider prop-checkbox]] 8 | [re-com.util :refer [px]] 9 | [reagent.core :as r])) 10 | 11 | (def model (r/atom false)) 12 | 13 | (defn panel* 14 | [] 15 | (let [width (r/atom 200) 16 | height (r/atom 200) 17 | min-width (r/atom 200) 18 | max-width (r/atom 200) 19 | max-height (r/atom 200) 20 | min-height (r/atom 200) 21 | anchor-height (r/atom 200) 22 | body-width (r/atom 200) 23 | body-height (r/atom 200) 24 | anchor-width (r/atom 200) 25 | show-backdrop? (r/atom nil)] 26 | (fn [] 27 | [v-box :src (at) :size "auto" :gap "10px" 28 | :children 29 | [[panel-title "[dropdown ... ]" 30 | "src/re_com/dropdown.cljs" 31 | "src/re_demo/dropdown.cljs"] 32 | [h-box :src (at) :gap "100px" 33 | :children 34 | [[v-box :src (at) :gap "10px" :width "450px" 35 | :children 36 | [[title2 "Notes"] 37 | [status-text "Alpha" {:color "red"}] 38 | [p-span "A generic dropdown component. You pass in your own components for " 39 | [:code ":anchor"] " and " [:code ":body"] "."] 40 | [p-span [:code ":dropdown"] " provides: "] 41 | [:ul 42 | [:li "state management (" [:span {:style {:color "red"}} "alpha!"] ") for opening and closing"] 43 | [:li "dynamically positioned container elements"]] 44 | [args-table dropdown-args-desc]]] 45 | [v-box :src (at) :width "700px" :gap "10px" 46 | :children 47 | [[title2 "Demo"] 48 | [dropdown 49 | (merge 50 | {:parts {:anchor (fn [{:keys [state label style]}] 51 | [:span {:style style} 52 | (str "This " label " is " (:openable state) (when (= :open (:openable state)) " ;)"))])} 53 | :label "dropdown" 54 | :body [:div "Hello World!"] 55 | :model model 56 | :width (some-> @width px) 57 | :show-backdrop? @show-backdrop?} 58 | (when @height {:height (px @height)}) 59 | (when @anchor-height {:anchor-height (px @anchor-height)}) 60 | (when @body-height {:body-height (px @body-height)}) 61 | {:min-width (some-> @min-width px) 62 | :max-width (some-> @max-width px) 63 | :max-height (some-> @max-height px) 64 | :min-height (some-> @min-height px) 65 | :body-width (some-> @body-width px) 66 | :anchor-width (some-> @anchor-width px) 67 | :on-change #(reset! model %)})] 68 | [v-box :src (at) 69 | :gap "10px" 70 | :style {:min-width "550px" 71 | :padding "15px" 72 | :border-top "1px solid #DDD" 73 | :background-color "#f7f7f7"} 74 | :children [[title3 "Interactive Parameters" {:margin-top "0"}] 75 | [v-box :src (at) 76 | :gap "20px" 77 | :children 78 | [[prop-checkbox {:prop show-backdrop? :id :show-backdrop?}] 79 | [prop-slider {:prop width :id :width :default 212 :default-on? false}] 80 | [prop-slider {:prop height :id :height :default 212 :default-on? false}] 81 | [prop-slider {:prop min-width :id :min-width :default 212 :default-on? false}] 82 | [prop-slider {:prop max-width :id :max-width :default 212 :default-on? false}] 83 | [prop-slider {:prop min-height :id :min-height :default 212 :default-on? false}] 84 | [prop-slider {:prop max-height :id :max-height :default 212 :default-on? false}] 85 | [prop-slider {:prop body-width :id :body-width :default 212 :default-on? false}] 86 | [prop-slider {:prop body-height :id :body-height :default 212 :default-on? false}] 87 | [prop-slider {:prop anchor-height :id :anchor-height :default 35 :default-on? false :min 10 :max 50}] 88 | [prop-slider {:prop anchor-width :id :anchor-width :default 212 :default-on? false}]]]]]]]]] 89 | [parts-table "dropdown" dropdown-parts-desc]]]))) 90 | 91 | ;; core holds a reference to panel, so need one level of indirection to get figwheel updates 92 | (defn panel 93 | [] 94 | [panel*]) 95 | -------------------------------------------------------------------------------- /src/re_demo/hyperlink.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.hyperlink 2 | (:require-macros 3 | [re-com.core :refer []]) 4 | (:require 5 | [re-com.core :refer [at h-box v-box box gap line label title checkbox hyperlink p]] 6 | [re-com.buttons :refer [hyperlink-parts-desc hyperlink-args-desc]] 7 | [re-demo.utils :refer [panel-title title2 title3 parts-table args-table github-hyperlink status-text]] 8 | [re-com.util :refer [px]] 9 | [reagent.core :as reagent])) 10 | 11 | (defn hyperlink-demo 12 | [] 13 | (let [disabled? (reagent/atom false) 14 | click-count (reagent/atom 0)] 15 | (fn 16 | [] 17 | [v-box 18 | :src (at) 19 | :size "auto" 20 | :gap "10px" 21 | :children [[panel-title "[hyperlink ... ]" 22 | "src/re_com/buttons.cljs" 23 | "src/re_demo/hyperlink.cljs"] 24 | 25 | [h-box 26 | :src (at) 27 | :gap "100px" 28 | :children [[v-box 29 | :src (at) 30 | :gap "10px" 31 | :width "450px" 32 | :children [[title2 "Notes"] 33 | [status-text "Stable"] 34 | [p "A blue, clickable hyperlink to which you can attach a click handler."] 35 | [p "If you want to launch external URLs, use the [hyperlink-href] component."] 36 | [args-table hyperlink-args-desc]]] 37 | [v-box 38 | :src (at) 39 | :gap "10px" 40 | :children [[title2 "Demo"] 41 | [hyperlink 42 | :src (at) 43 | :label "Click me" 44 | :tooltip "Click here to increase the click count" 45 | :tooltip-position :left-center 46 | :on-click #(swap! click-count inc) 47 | :disabled? disabled?] 48 | [label 49 | :src (at) 50 | :label (str "click count = " @click-count)] 51 | [v-box 52 | :src (at) 53 | :gap "10px" 54 | :style {:min-width "150px" 55 | :padding "15px" 56 | :border-top "1px solid #DDD" 57 | :background-color "#f7f7f7"} 58 | :children [[title 59 | :src (at) 60 | :level :level3 61 | :label "Interactive Parameters" 62 | :style {:margin-top "0"}] 63 | [checkbox 64 | :src (at) 65 | :label [:code ":disabled?"] 66 | :model disabled? 67 | :on-change (fn [val] 68 | (reset! disabled? val))]]]]]]] 69 | [parts-table "hyperlink" hyperlink-parts-desc]]]))) 70 | 71 | ;; core holds a reference to panel, so need one level of indirection to get figwheel updates 72 | (defn panel 73 | [] 74 | [hyperlink-demo]) 75 | -------------------------------------------------------------------------------- /src/re_demo/hyperlink_href.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.hyperlink-href 2 | (:require-macros 3 | [re-com.core :refer []]) 4 | (:require 5 | [re-com.core :refer [at h-box v-box box gap line label title radio-button hyperlink-href p checkbox]] 6 | [re-com.buttons :refer [hyperlink-href-parts-desc hyperlink-href-args-desc]] 7 | [re-demo.utils :refer [panel-title title2 title3 parts-table args-table github-hyperlink status-text]] 8 | [re-com.util :refer [px]] 9 | [reagent.core :as reagent])) 10 | 11 | (defn hyperlink-href-demo 12 | [] 13 | (let [disabled? (reagent/atom false) 14 | target (reagent/atom "_blank") 15 | href? (reagent/atom true)] 16 | (fn 17 | [] 18 | [v-box 19 | :src (at) 20 | :size "auto" 21 | :gap "10px" 22 | :children [[panel-title "[hyperlink-href ... ]" 23 | "src/re_com/buttons.cljs" 24 | "src/re_demo/hyperlink_href.cljs"] 25 | 26 | [h-box 27 | :src (at) 28 | :gap "100px" 29 | :children [[v-box 30 | :src (at) 31 | :gap "10px" 32 | :width "450px" 33 | :children [[title2 "Notes"] 34 | [status-text "Stable"] 35 | [p "A blue, clickable hyperlink which launches external URLs."] 36 | [p "If you want a hyperlink with a click handler, use the [hyperlink] component."] 37 | [args-table hyperlink-href-args-desc]]] 38 | [v-box 39 | :src (at) 40 | :gap "10px" 41 | :children [[title2 "Demo"] 42 | [hyperlink-href :src (at) 43 | :label "Launch Google" 44 | :tooltip "You're about to launch Google" 45 | :href (when href? "http://google.com") 46 | :target (when href? target) 47 | :disabled? disabled?] 48 | [v-box 49 | :src (at) 50 | :gap "10px" 51 | :style {:min-width "150px" 52 | :padding "15px" 53 | :border-top "1px solid #DDD" 54 | :background-color "#f7f7f7"} 55 | :children [[title 56 | :src (at) 57 | :level :level3 :label "Interactive Parameters" :style {:margin-top "0"}] 58 | (when @href? 59 | [v-box 60 | :src (at) 61 | :gap "15px" 62 | :children [[box 63 | :src (at) 64 | :align :start :child [:code ":target"]] 65 | [radio-button 66 | :src (at) 67 | :label "_self - load link into same tab" 68 | :value "_self" 69 | :model @target 70 | :on-change #(reset! target %) 71 | :style {:margin-left "20px"}] 72 | [radio-button 73 | :src (at) 74 | :label "_blank - load link into new tab" 75 | :value "_blank" 76 | :model @target 77 | :on-change #(reset! target %) 78 | :style {:margin-left "20px"}] 79 | [checkbox 80 | :src (at) 81 | :label [:code ":disabled?"] 82 | :model disabled? 83 | :on-change (fn [val] 84 | (reset! disabled? val))]]])]]]]]] 85 | [parts-table "hyperlink-href" hyperlink-href-parts-desc]]]))) 86 | 87 | ;; core holds a reference to panel, so need one level of indirection to get figwheel updates 88 | (defn panel 89 | [] 90 | [hyperlink-href-demo]) 91 | -------------------------------------------------------------------------------- /src/re_demo/label.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.label 2 | (:require [re-com.core :refer [at h-box v-box box gap line label p]] 3 | [re-com.text :refer [label-parts-desc label-args-desc]] 4 | [re-demo.utils :refer [panel-title title2 title3 parts-table args-table github-hyperlink status-text]] 5 | [re-com.util :refer [px]])) 6 | 7 | (defn label-demo 8 | [] 9 | [v-box :src (at) 10 | :size "auto" 11 | :gap "10px" 12 | :children [[panel-title "[label ... ]" 13 | "src/re_com/text.cljs" 14 | "src/re_demo/label.cljs"] 15 | [h-box :src (at) 16 | :gap "100px" 17 | :children [[v-box :src (at) 18 | :gap "10px" 19 | :width "450px" 20 | :children [[title2 "Notes"] 21 | [status-text "Stable"] 22 | [:p "A short single line of text."] 23 | [args-table label-args-desc]]] 24 | [v-box :src (at) 25 | :gap "10px" 26 | :children [[title2 "Demo"] 27 | [v-box :src (at) 28 | :children [[label :src (at) :label "This is a label."]]]]]]] 29 | [parts-table "label" label-parts-desc]]]) 30 | 31 | ;; core holds a reference to panel, so need one level of indirection to get figwheel updates 32 | (defn panel 33 | [] 34 | [label-demo]) 35 | -------------------------------------------------------------------------------- /src/re_demo/line.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.line 2 | (:require [re-com.core :refer [at h-box v-box box gap line p]] 3 | [re-com.box :refer [line-args-desc]] 4 | [re-demo.utils :refer [panel-title title2 args-table github-hyperlink status-text]])) 5 | 6 | (defn panel 7 | [] 8 | [v-box :src (at) 9 | :size "auto" 10 | :gap "10px" 11 | :children [[panel-title "[line ... ]" 12 | "src/re_com/box.cljs" 13 | "src/re_demo/line.cljs"] 14 | 15 | [h-box :src (at) 16 | :gap "100px" 17 | :children [[v-box :src (at) 18 | :gap "10px" 19 | :width "450px" 20 | :children [[title2 "Notes"] 21 | [status-text "Stable"] 22 | [p "Draws a line. Typically placed between the children of a v-box or h-box."] 23 | [p "The line is added in the expected direction (i.e. vertically for h-box or horizontally for v-box)."] 24 | [args-table line-args-desc]]] 25 | [v-box :src (at) 26 | :gap "10px" 27 | :children [[title2 "Demo"] 28 | [p "For an example, look at the top of this page. There's a title " [:span.bold "[line ...]"] 29 | ", and notice the line under it."] 30 | [p "Here is some sample code..."] 31 | [:pre 32 | {:style {:width "40em"}} 33 | "[h-box 34 | :gap \"10px\" 35 | :children [[grey-box] 36 | [line 37 | :size \"3px\" 38 | :color \"red\"] 39 | [grey-box]]]"] 40 | [p "Here is the result..."] 41 | [h-box :src (at) 42 | :gap "10px" 43 | :children [[box :src (at) 44 | :style {:background-color "lightgrey" 45 | :padding "20px"} 46 | :child "Box 1"] 47 | [line :src (at) 48 | :size "3px" 49 | :color "red"] 50 | [box :src (at) 51 | :style {:background-color "lightgrey" 52 | :padding "20px"} 53 | :child "Box 2"]]]]]]] 54 | [gap :src (at) :size "30px"]]]) 55 | -------------------------------------------------------------------------------- /src/re_demo/md_icon_button.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.md-icon-button 2 | (:require [re-com.core :refer [at h-box v-box box gap line md-icon-button label horizontal-bar-tabs vertical-bar-tabs p p-span]] 3 | [re-com.buttons :refer [md-icon-button-parts-desc md-icon-button-args-desc]] 4 | [re-demo.md-circle-icon-button :refer [icons example-icons]] 5 | [re-demo.utils :refer [panel-title title2 title3 parts-table args-table material-design-hyperlink github-hyperlink status-text]] 6 | [re-com.util :refer [px]] 7 | [reagent.core :as reagent])) 8 | 9 | (defn md-icon-button-demo 10 | [] 11 | (let [selected-icon (reagent/atom (:id (first icons)))] 12 | (fn [] 13 | [v-box :src (at) 14 | :size "auto" 15 | :gap "10px" 16 | :children [[panel-title "[md-icon-button ... ]" 17 | "src/re_com/buttons.cljs" 18 | "src/re_demo/md_icon_button.cljs"] 19 | 20 | [h-box :src (at) 21 | :gap "100px" 22 | :children [[v-box :src (at) 23 | :gap "10px" 24 | :width "450px" 25 | :children [[title2 "Notes"] 26 | [status-text "Stable"] 27 | [p-span "Material design icons, and their names, can be " [material-design-hyperlink "found here"] "."] 28 | [args-table md-icon-button-args-desc]]] 29 | [v-box :src (at) 30 | :gap "10px" 31 | :children [[title2 "Demo"] 32 | [v-box :src (at) 33 | :gap "15px" 34 | :children [[example-icons selected-icon] 35 | [gap :src (at) :size "10px"] 36 | [p "Here's what the chosen icon looks like in an Icon Button."] 37 | [h-box :src (at) 38 | :gap "20px" 39 | :align :center 40 | :children [[box :src (at) :width "90px" :child [:code ":size"]] 41 | [md-icon-button :src (at) 42 | :md-icon-name @selected-icon 43 | :tooltip ":size set to :smaller" 44 | :size :smaller 45 | :on-click #()] 46 | [md-icon-button :src (at) 47 | :md-icon-name @selected-icon 48 | :tooltip "No :size set. This is the default button" 49 | :on-click #()] 50 | [md-icon-button :src (at) 51 | :md-icon-name @selected-icon 52 | :tooltip ":size set to :larger" 53 | :size :larger 54 | :on-click #()]]] 55 | [h-box :src (at) 56 | :gap "20px" 57 | :align :center 58 | :justify :start 59 | :children [[box :src (at) :width "90px" :child [:code ":emphasise?"]] 60 | [md-icon-button :src (at) 61 | :md-icon-name @selected-icon 62 | :emphasise? true 63 | :tooltip "This button has :emphasise? set to true" 64 | :on-click #()]]] 65 | [h-box :src (at) 66 | :gap "20px" 67 | :align :center 68 | :children [[box :src (at) :width "90px" :child [:code ":disabled?"]] 69 | [md-icon-button :src (at) 70 | :md-icon-name @selected-icon 71 | :tooltip "This button has :disabled? set to true" 72 | :disabled? true 73 | :on-click #()]]]]]]]]] 74 | [parts-table "md-icon-button" md-icon-button-parts-desc]]]))) 75 | 76 | ;; core holds a reference to panel, so need one level of indirection to get figwheel updates 77 | (defn panel 78 | [] 79 | [md-icon-button-demo]) 80 | -------------------------------------------------------------------------------- /src/re_demo/nested_v_grid.cljs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/day8/re-com/472dee703e92ff20f1493cc8a17692b77724a4c2/src/re_demo/nested_v_grid.cljs -------------------------------------------------------------------------------- /src/re_demo/p.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.p 2 | (:require [re-com.core :refer [at h-box v-box box gap line label p p-span hyperlink-href]] 3 | [re-demo.utils :refer [panel-title title2 args-table github-hyperlink status-text]])) 4 | 5 | (def arg-style {:style {:display "inline-block" 6 | :font-weight "bold" 7 | :min-width "140px"}}) 8 | 9 | (defn p-demo 10 | [] 11 | [v-box :src (at) 12 | :size "auto" 13 | :gap "10px" 14 | :children [[panel-title "[p ... ]" 15 | "src/re_com/text.cljs" 16 | "src/re_demo/p.cljs"] 17 | [h-box :src (at) 18 | :gap "100px" 19 | :children [[v-box :src (at) 20 | :gap "10px" 21 | :width "450px" 22 | :children [[title2 "Notes"] 23 | [status-text "Stable"] 24 | [p-span "Acts like an html [:p]." [:br] [:br] 25 | 26 | "Creates a paragraph of body text, expected to have a font-szie of 14px or 15px, which should have limited width (450px)." [:br] [:br] 27 | 28 | "Why limited text width? See " [hyperlink-href :src (at) 29 | :label "this article" 30 | :href "http://baymard.com/blog/line-length-readability" 31 | :target "blank"] "." [:br] [:br] 32 | 33 | "The actual font-size is inherited." [:br] [:br] 34 | 35 | "At 14px, 450px will yield between 69 and 73 chars." [:br] 36 | "At 15px, 450px will yield about 66 to 70 chars." [:br] 37 | "So we're at the upper end of the prefered 50 to 75 char range." [:br] [:br] 38 | 39 | "If the first child is a map, it is interpreted as a map of attributes." [:br] [:br] 40 | 41 | "Note: This section is contained within a single [p]."] 42 | 43 | [title2 "Parameters"] 44 | [p [:span.bold "[p optional-attr-map & components]"] [:br] [:br] 45 | 46 | [:span arg-style "optional-attr-map"] "e.g. " [:code "{:style {:color \"red\"}}"] [:br] [:br] 47 | 48 | [:span arg-style "components"] "one or more hiccup components."]]] 49 | 50 | [v-box :src (at) 51 | :gap "10px" 52 | :children [[title2 "Demo"] 53 | [v-box :src (at) 54 | :children [[p "This is the simplest form of a p with no attribute map and only a single string. It wraps at 450px."] 55 | [p {:style {:color "red"}} "This is a p with an optional attribute map. In this case, we're setting the color to red."] 56 | [p {:style {:width "300px" :min-width "300px"}} "If you really feel the need to change with default width, 57 | you can do it with the attribute map. 300px in this case."]]]]]]]]]) 58 | 59 | ;; core holds a reference to panel, so need one level of indirection to get figwheel updates 60 | (defn panel 61 | [] 62 | [p-demo]) 63 | -------------------------------------------------------------------------------- /src/re_demo/parts.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.parts 2 | (:require 3 | [re-com.core :as rc] 4 | [re-demo.utils :as rdu])) 5 | 6 | (defn panel [] 7 | [rc/v-box 8 | {:src (rc/at) 9 | :children 10 | [[rdu/panel-title "Parts" nil "src/re_demo/parts.cljs"] 11 | [rc/gap :size "19px"] 12 | [rc/h-box 13 | :gap "31px" 14 | :children 15 | [[rc/v-box 16 | :children 17 | [[rc/p "While an ordinary reagent component simply returns a tree of hiccups, " 18 | "a re-com component identifies each meaningful hiccup as a " [:i "part"] ". " 19 | "We describe the tree of " [:i "parts"] " at the bottom of " 20 | "each component's documentation page (in the left sidebar of this website). "] 21 | [rc/p 22 | "Passing a " [:code ":parts"] " map to a re-com component gives you control over the details " 23 | "of each hiccup in the tree: its component function, its props and its children. " 24 | "The keys are " [:i "part"] "-ids. The vals are " [:i "part"] "-specs. " 25 | "They specify how to customize each part." 26 | "This customization works differently, depending on the type of val you declare."] 27 | [rc/p {:style {:background-color "#eee" :padding 7}} 28 | [:strong "Note"] ": Some re-com components only support the " [:i "map"] " type. " 29 | "Our effort to bring full support to all components is " 30 | [:a {:href "https://github.com/day8/re-com/issues/352"} "ongoing"] "."]]] 31 | [rc/box 32 | :align :center 33 | :child [:img {:style {:height "250px"} :src "demo/heart-parts.jpg"}]]]] 34 | [rc/title :level :level3 :label [:span "hiccup " [:code ":parts"]]] 35 | [rc/h-box 36 | :gap "31px" 37 | :children 38 | [[rc/v-box 39 | :children 40 | [[rc/p "A " [:i "part"] "-spec can be a string or a hiccup. In this case, " 41 | "the value is placed directly into the hiccup tree."]]] 42 | [rc/h-box 43 | :style {:height :fit-content :gap "12px"} 44 | :children 45 | (rdu/with-src 46 | [rc/dropdown 47 | {:parts {:anchor "Open" 48 | :body [:div "Sesame"]}}])]]] 49 | [rc/gap :size "31px"] 50 | [rc/title :level :level3 :label [:span "function " [:code ":parts"]]] 51 | [rc/h-box 52 | :gap "31px" 53 | :children 54 | [[rc/v-box 55 | :children 56 | [[rc/p "A " [:i "part"] "-spec can be a component function. " 57 | "It is placed into a hiccup, " 58 | "replacing re-com's default component function for that " [:i "part"] ". " 59 | "Re-com passes the same props to your new part function, " 60 | "including all the props listed in the " [:strong "Basics"] " " 61 | "section. There are a few more props, giving context to the part:"] 62 | [rc/p 63 | [:ul 64 | [:li [:strong [:code ":part"]] 65 | " is a keyword, with the namespace of the component and the name of the part. " 66 | " For instance, " [:code ":re-com.dropdown/anchor"] "."] 67 | [:li [:strong [:code ":re-com"]] " is a map describing the component overall, " 68 | "such as its " [:code ":state"] ", and " [:code "theme"] " information (see the " 69 | [:code "theme"] " section below)."]]] 70 | [rc/p {:style {:background-color "#eee" :padding 7}} 71 | [:strong "Note"] ": " [:code ":state"] " is an experimental feature, " 72 | "only supported by some components."]]] 73 | [rc/h-box 74 | :style {:height :fit-content :gap "12px"} 75 | :children 76 | (rdu/with-src 77 | [rc/dropdown 78 | {:parts {:anchor (fn [props] 79 | [:span "I am " (get-in props [:re-com :state :openable])]) 80 | :body (fn [props] 81 | [:ul 82 | [:li "I am a " [:code (str (get props :part))]] 83 | [:li "Class: " [:code (str (get props :class))]]])}}])]]] 84 | [rc/gap :size "31px"] 85 | [rc/title :level :level3 :label [:span "map " [:code ":parts"]]] 86 | [rc/h-box 87 | :gap "31px" 88 | :children 89 | [[rc/v-box 90 | :children 91 | [[rc/p 92 | "A " [:i "part"] "-spec can be a map, letting you control some visual characteristics" 93 | " without needing to re-implement the whole component. "] 94 | [rc/p 95 | "Every part has a default component function, used when " 96 | "you don't pass a function of your own. " 97 | "Here, the default component function for " [:code ":re-com.dropdown/anchor"] " " 98 | "is responsible for the text " [:code "\"Select an item\""] ". " 99 | "The props which re-com passes into this function have been " 100 | "overridden by the pink " [:code ":style"] 101 | " and italic " [:code ":class"] " we passed in."]]] 102 | [rc/h-box 103 | :style {:height :fit-content :gap "12px"} 104 | :children 105 | (rdu/with-src 106 | [rc/dropdown 107 | {:parts {:body "Sesame" 108 | :anchor {:style {:color "pink"} 109 | :class "italic"}}}])]]]]}]) 110 | -------------------------------------------------------------------------------- /src/re_demo/popover_dialog_demo.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.popover-dialog-demo 2 | (:require [re-com.core :refer [at h-box v-box box gap line label checkbox radio-button button single-dropdown popover-content-wrapper popover-anchor-wrapper]] 3 | [re-com.util :refer [deref-or-value]] 4 | [reagent.core :as reagent])) 5 | 6 | (defn popover-body 7 | [dialog-data on-change & {:keys [showing-injected? position-injected]}] ;; v0.10.0 breaking change fix (was [showing? position dialog-data on-change]) 8 | (let [dialog-data (reagent/atom (deref-or-value dialog-data)) 9 | submit-dialog (fn [new-dialog-data] 10 | (reset! showing-injected? false) 11 | (on-change new-dialog-data)) 12 | cancel-dialog #(reset! showing-injected? false) 13 | show-tooltip? (reagent/atom (= (:tooltip-state @dialog-data) "2"))] 14 | (fn [] 15 | [popover-content-wrapper :src (at) 16 | :showing-injected? showing-injected? 17 | :position-injected position-injected 18 | :on-cancel cancel-dialog 19 | :width "400px" 20 | :backdrop-opacity 0.3 21 | :title "This is the title" 22 | :body [v-box :src (at) 23 | :children [[label :src (at) 24 | :label "The body of a popover can act like a dialog box containing standard input controls."] 25 | [gap :src (at) :size "15px"] 26 | [h-box :src (at) 27 | :children [[v-box :src (at) 28 | :size "auto" 29 | :children [[radio-button :src (at) 30 | :label "Don't show extra popover" 31 | :value "1" 32 | :model (:tooltip-state @dialog-data) 33 | :on-change (fn [val] 34 | (swap! dialog-data assoc :tooltip-state val) 35 | (reset! show-tooltip? false))] 36 | [radio-button :src (at) 37 | :label "Show extra popover" 38 | :value "2" 39 | :model (:tooltip-state @dialog-data) 40 | :on-change (fn [val] 41 | (swap! dialog-data assoc :tooltip-state val) 42 | (reset! show-tooltip? true))]]]]] 43 | [gap :src (at) :size "20px"] 44 | [line :src (at)] 45 | [gap :src (at) :size "10px"] 46 | [h-box :src (at) 47 | :gap "10px" 48 | :children [[button :src (at) 49 | :label [:span [:i {:class "zmdi zmdi-check"}] " Apply"] 50 | :on-click #(submit-dialog @dialog-data) 51 | :class "btn-primary"] 52 | [popover-anchor-wrapper :src (at) 53 | :showing? show-tooltip? 54 | :position :right-below 55 | :anchor [button :src (at) 56 | :label [:span [:i {:class "zmdi zmdi-close"}] " Cancel"] 57 | :on-click cancel-dialog] 58 | :popover [popover-content-wrapper :src (at) ;; NOTE: didn't specify on-cancel here (handled properly) 59 | :width "250px" 60 | :title "This is the cancel button" 61 | :close-button? false 62 | :body "You can even have a popover over a popover!"]]]]]]]))) 63 | 64 | (defn popover-dialog-demo 65 | [position] 66 | (let [showing? (reagent/atom false) 67 | dialog-data (reagent/atom {:tooltip-state "2"}) 68 | on-change (fn [new-dialog-data] 69 | (reset! dialog-data new-dialog-data))] 70 | (fn [] 71 | [popover-anchor-wrapper :src (at) 72 | :showing? showing? 73 | :position @position 74 | :anchor [button :src (at) 75 | :label "Dialog box" 76 | :on-click #(reset! showing? true) 77 | :class "btn btn-danger"] 78 | :popover [popover-body dialog-data on-change]]))) ;; v0.10.0 breaking change fix (was [popover-body showing? @position dialog-data on-change]) 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/re_demo/progress_bar.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.progress-bar 2 | (:require [re-com.core :refer [at h-box v-box box gap line label title progress-bar slider checkbox p]] 3 | [re-com.progress-bar :refer [progress-bar-parts-desc progress-bar-args-desc]] 4 | [re-demo.utils :refer [panel-title title2 title3 parts-table args-table github-hyperlink status-text]] 5 | [re-com.util :refer [px]] 6 | [reagent.core :as reagent])) 7 | 8 | (defn progress-bar-demo 9 | [] 10 | (let [progress (reagent/atom 75) 11 | striped? (reagent/atom false)] 12 | (fn 13 | [] 14 | [v-box :src (at) 15 | :size "auto" 16 | :gap "10px" 17 | :children [[panel-title "[progress-bar ... ]" 18 | "src/re_com/progress_bar.cljs" 19 | "src/re_demo/progress_bar.cljs"] 20 | [h-box :src (at) 21 | :gap "100px" 22 | :children [[v-box :src (at) 23 | :gap "10px" 24 | :width "450px" 25 | :children [[title2 "Notes"] 26 | [status-text "Stable"] 27 | [p "A Bootstrap styled progress bar."] 28 | [args-table progress-bar-args-desc]]] 29 | [v-box :src (at) 30 | :gap "10px" 31 | :children [[title2 "Demo"] 32 | [v-box :src (at) 33 | :gap "20px" 34 | :children [[progress-bar :src (at) 35 | :model progress 36 | :width "350px" 37 | :striped? @striped?] 38 | [v-box :src (at) 39 | :gap "10px" 40 | :style {:min-width "150px" 41 | :padding "15px" 42 | :border-top "1px solid #DDD" 43 | :background-color "#f7f7f7"} 44 | :children [[title :src (at) :level :level3 :label "Interactive Parameters" :style {:margin-top "0"}] 45 | [h-box :src (at) 46 | :gap "10px" 47 | :children [[box :src (at) :align :start :child [:code ":model"]] 48 | [slider :src (at) 49 | :model progress 50 | :min 0 51 | :max 100 52 | :width "200px" 53 | :on-change #(reset! progress %)] 54 | [label :src (at) :label @progress]]] 55 | [checkbox :src (at) 56 | :label [box :src (at) :align :start :child [:code ":striped?"]] 57 | :model striped? 58 | :on-change #(reset! striped? %)]]]]]]]]] 59 | [parts-table "progress-bar" progress-bar-parts-desc]]]))) 60 | 61 | ;; core holds a reference to panel, so need one level of indirection to get figwheel updates 62 | (defn panel 63 | [] 64 | [progress-bar-demo]) 65 | -------------------------------------------------------------------------------- /src/re_demo/radio_button.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.radio-button 2 | (:require [re-com.core :refer [at h-box v-box box gap checkbox title line radio-button p]] 3 | [re-com.radio-button :refer [radio-button-parts-desc radio-button-args-desc]] 4 | [re-demo.utils :refer [panel-title title2 title3 parts-table args-table github-hyperlink status-text]] 5 | [re-com.util :refer [px]] 6 | [reagent.core :as reagent])) 7 | 8 | (defn radios-demo 9 | [] 10 | (let [disabled? (reagent/atom false) 11 | color (reagent/atom "green")] 12 | (fn 13 | [] 14 | [v-box :src (at) 15 | :size "auto" 16 | :gap "10px" 17 | :children [[panel-title "[radio-button ... ]" 18 | "src/re_com/radio_button.cljs" 19 | "src/re_demo/radio_button.cljs"] 20 | [h-box :src (at) 21 | :gap "100px" 22 | :children [[v-box :src (at) 23 | :gap "10px" 24 | :width "450px" 25 | :children [[title2 "Notes"] 26 | [status-text "Stable"] 27 | [p "A boostrap-styled radio button, with optional label (always displayed to the right)."] 28 | [p "Clicking on the label is the same as clicking on the radio button."] 29 | [args-table radio-button-args-desc]]] 30 | [v-box :src (at) 31 | :gap "10px" 32 | :children [[title2 "Demo"] 33 | (doall (for [c ["red" "green" "blue"]] ;; Notice the ugly "doall" 34 | ^{:key c} ;; key should be unique among siblings 35 | [radio-button :src (at) 36 | :disabled? disabled? 37 | :label c 38 | :value c 39 | :model color 40 | :label-style (if (= c @color) {:color c 41 | :font-weight "bold"}) 42 | :on-change #(reset! color %)])) 43 | [v-box :src (at) 44 | :gap "10px" 45 | :style {:min-width "150px" 46 | :padding "15px" 47 | :border-top "1px solid #DDD" 48 | :background-color "#f7f7f7"} 49 | :children [[title :src (at) :level :level3 :label "Interactive Parameters" :style {:margin-top "0"}] 50 | [checkbox :src (at) 51 | :label [:code ":disabled?"] 52 | :model disabled? 53 | :on-change (fn [val] 54 | (reset! disabled? val))]]]]]]] 55 | 56 | [parts-table "radio-button" radio-button-parts-desc]]]))) 57 | 58 | ;; core holds a reference to panel, so need one level of indirection to get figwheel updates 59 | (defn panel 60 | [] 61 | [radios-demo]) 62 | -------------------------------------------------------------------------------- /src/re_demo/scroller.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.scroller 2 | (:require [re-com.core :refer [at h-box v-box gap scroller p]] 3 | [re-com.box :refer [scroller-args-desc]] 4 | [re-demo.utils :refer [panel-title title2 args-table github-hyperlink status-text]])) 5 | 6 | (defn panel 7 | [] 8 | [v-box :src (at) 9 | :size "auto" 10 | :gap "10px" 11 | :children [[panel-title "[scroller ... ]" 12 | "src/re_com/box.cljs" 13 | "src/re_demo/scroller.cljs"] 14 | 15 | [h-box :src (at) 16 | :gap "100px" 17 | :children [[v-box :src (at) 18 | :gap "10px" 19 | :width "450px" 20 | :children [[title2 "Notes"] 21 | [status-text "Stable"] 22 | [p "Wraps a child component in scroll bars."] 23 | [p "The scroller can be used at any level in a box hierarchy. For example, it could be a child of an h-box and its child could be a v-box."] 24 | [args-table scroller-args-desc]]] 25 | [v-box :src (at) 26 | :gap "10px" 27 | :children [[title2 "Demo"] 28 | [p "Here is some sample code..."] 29 | [:pre 30 | {:style {:width "40em"}} 31 | "[scroller 32 | :v-scroll :auto 33 | :height \"300px\" 34 | :child [some-component]]"] 35 | [p "Notes:"] 36 | [:ul {:style {:width "450px"}} 37 | [:li "In this example, if the height of [some-component] is greater than 300px, a vertical scroll bar will be added."]]]]]] 38 | [gap :src (at) :size "30px"]]]) 39 | -------------------------------------------------------------------------------- /src/re_demo/splits.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.splits 2 | (:require [re-com.core :refer [at h-box v-box box gap line scroller border h-split v-split title flex-child-style p]] 3 | [re-com.splits :refer [hv-split-parts-desc hv-split-args-desc]] 4 | [re-demo.utils :refer [panel-title title2 title3 parts-table args-table github-hyperlink status-text]] 5 | [re-com.util :refer [px]])) 6 | 7 | (def rounded-panel (merge (flex-child-style "1") 8 | {:background-color "#fff4f4" 9 | :border "1px solid lightgray" 10 | :border-radius "4px" 11 | :padding "0px 20px 0px 20px"})) 12 | 13 | (defn splitter-panel-title 14 | [text] 15 | [title :src (at) 16 | :label text 17 | :level :level3 18 | :style {:margin-top "20px"}]) 19 | 20 | (defn left-panel 21 | [] 22 | [box :src (at) 23 | :size "auto" 24 | :child [:div {:style rounded-panel} 25 | [splitter-panel-title [:code ":panel-1"]]]]) 26 | 27 | (defn right-panel 28 | [] 29 | [box :src (at) 30 | :size "auto" 31 | :child [:div {:style rounded-panel} 32 | [splitter-panel-title [:code ":panel-2"]]]]) 33 | 34 | (defn top-panel 35 | [] 36 | [box :src (at) 37 | :size "auto" 38 | :child [:div {:style rounded-panel} 39 | [splitter-panel-title [:code ":panel-1"]]]]) 40 | 41 | (defn bottom-panel 42 | [] 43 | [box :src (at) 44 | :size "auto" 45 | :child [:div {:style rounded-panel} 46 | [splitter-panel-title [:code ":panel-2"]]]]) 47 | 48 | (defn panel 49 | [] 50 | [v-box :src (at) 51 | :size "auto" 52 | :gap "10px" 53 | :children [[panel-title "Splitter Components" 54 | "src/re_com/splits.cljs" 55 | "src/re_demo/splits.cljs"] 56 | 57 | [h-box :src (at) 58 | :gap "100px" 59 | :children [[v-box :src (at) 60 | :gap "10px" 61 | :width "450px" 62 | :children [[title2 "[h-split ... ] & [v-split ... ]"] 63 | [status-text "Stable"] 64 | [p "Arranges two components horizontally (or vertically) and provides a splitter bar between them."] 65 | [p "By dragging the splitter bar, a user can change the width (or height) allocated to each."] 66 | [p "Can contain further nested layout components."] 67 | [args-table hv-split-args-desc]]] 68 | [v-box :src (at) 69 | :size "auto" 70 | :gap "10px" 71 | :height "800px" 72 | :children [[title2 "Demo"] 73 | [title :src (at) :level :level3 :label "[h-split]"] 74 | [h-split :src (at) 75 | :panel-1 [left-panel] 76 | :panel-2 [right-panel] 77 | :size "300px" 78 | :parts {:top {:style {:overflow :hidden}} 79 | :bottom {:style {:overflow :hidden}}}] 80 | [title :src (at) :level :level3 :label "[v-split]"] 81 | [v-split :src (at) 82 | :panel-1 [top-panel] 83 | :panel-2 [bottom-panel] 84 | :size "300px" 85 | :initial-split "25%" 86 | :parts {:top {:style {:overflow :hidden}} 87 | :bottom {:style {:overflow :hidden}}}]]]]] 88 | [parts-table "h-split" hv-split-parts-desc]]]) 89 | -------------------------------------------------------------------------------- /src/re_demo/throbber.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.throbber 2 | (:require [re-com.core :refer [at h-box v-box box gap line button label throbber p]] 3 | [re-com.throbber :refer [throbber-parts-desc throbber-args-desc]] 4 | [re-demo.utils :refer [panel-title title2 title3 parts-table args-table github-hyperlink status-text]] 5 | [re-com.util :refer [px]] 6 | [reagent.core :as reagent])) 7 | 8 | (def state (reagent/atom 9 | {:outcome-index 0 10 | :see-throbber false})) 11 | 12 | (defn throbber-demo 13 | [] 14 | [v-box :src (at) 15 | :size "auto" 16 | :gap "10px" 17 | :children [[panel-title "[throbber ... ]" 18 | "src/re_com/throbber.cljs" 19 | "src/re_demo/throbber.cljs"] 20 | 21 | [h-box :src (at) 22 | :gap "100px" 23 | :children [[v-box :src (at) 24 | :gap "10px" 25 | :width "450px" 26 | :children [[title2 "Notes"] 27 | [status-text "Stable"] 28 | [p "A CSS Throbber."] 29 | [args-table throbber-args-desc]]] 30 | [v-box :src (at) 31 | :gap "10px" 32 | :children [[title2 "Demo"] 33 | [h-box :src (at) 34 | :gap "50px" 35 | :children [[v-box :src (at) 36 | :align :center 37 | :children [[box :src (at) :align :start :child [:code ":smaller"]] 38 | [throbber :src (at) 39 | :size :smaller 40 | :color "green"]]] 41 | [v-box :src (at) 42 | :align :center 43 | :children [[box :src (at) :align :start :child [:code ":small"]] 44 | [throbber :src (at) 45 | :size :small 46 | :color "red"]]] 47 | [v-box :src (at) 48 | :align :center 49 | :children [[box :src (at) :align :start :child [:code ":regular"]] 50 | [throbber :src (at)]]] 51 | [v-box :src (at) 52 | :align :center 53 | :children [[box :src (at) :align :start :child [:code ":large"]] 54 | [throbber :src (at) 55 | :size :large 56 | :color "blue"]]]]]]]]] 57 | [parts-table "throbber" throbber-parts-desc]]]) 58 | 59 | ;; core holds a reference to panel, so need one level of indirection to get figwheel updates 60 | (defn panel 61 | [] 62 | [throbber-demo]) 63 | -------------------------------------------------------------------------------- /src/re_demo/title.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.title 2 | (:require [re-com.core :refer [at h-box v-box box gap line title label checkbox hyperlink-href p p-span]] 3 | [re-com.text :refer [title-parts-desc title-args-desc]] 4 | [re-demo.utils :refer [panel-title title2 title3 parts-table args-table github-hyperlink status-text]] 5 | [re-com.util :refer [px]] 6 | [reagent.core :as reagent])) 7 | 8 | (defn title-demo 9 | [] 10 | (let [underline? (reagent/atom false)] 11 | (fn 12 | [] 13 | (let [para-text [p "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quod si ita est, sequitur id ipsum, quod te velle video, omnes semper beatos esse sapientes. Tamen a proposito, inquam, aberramus. "]] 14 | [v-box :src (at) 15 | :size "auto" 16 | :gap "10px" 17 | :children [[panel-title "[title ... ]" 18 | "src/re_com/text.cljs" 19 | "src/re_demo/title.cljs"] 20 | [h-box :src (at) 21 | :gap "100px" 22 | :children [[v-box :src (at) 23 | :gap "10px" 24 | :width "450px" 25 | :children [[title2 "Notes"] 26 | [status-text "Stable"] 27 | [p "We use a four tier system of titles, the equivalent of h1 to h4."] 28 | [p-span 29 | "If you actually use [:h1] to [:h4] then " 30 | [hyperlink-href :src (at) 31 | :label "Bootstrap styles" 32 | :href "http://getbootstrap.com/css/#type" 33 | :target "_blank"] 34 | " will apply."] 35 | [p-span 36 | "re-com uses " 37 | [hyperlink-href :src (at) 38 | :label "Segoe UI" 39 | :href "https://www.microsoft.com/typography/fonts/family.aspx?FID=331" 40 | :target "_blank"] 41 | " as its default font (available on Windows) with a fallback to the public domain " 42 | [hyperlink-href :src (at) 43 | :label "Roboto" 44 | :href "http://www.google.com/fonts/specimen/Roboto" 45 | :target "_blank"] 46 | " font. See " 47 | [hyperlink-href :src (at) 48 | :label "re-com.css" 49 | :href "https://github.com/day8/re-com/tree/master/run/resources/public/assets/css/re-com.css" 50 | :target "_blank"] 51 | "."] 52 | [args-table title-args-desc]]] 53 | [v-box :src (at) 54 | :gap "10px" 55 | :children [[title2 "Demo"] 56 | [v-box :src (at) 57 | :children [[checkbox :src (at) 58 | :label [box :src (at) :align :start :child [:code ":underline?"]] 59 | :model underline? 60 | :on-change #(reset! underline? %)] 61 | [gap :src (at) :size "40px"] 62 | para-text 63 | [title :src (at) :level :level1 :underline? @underline? :label ":level1 - Light 42px"] 64 | para-text 65 | [title :src (at) :level :level2 :underline? @underline? :label ":level2 - Light 26px"] 66 | para-text 67 | [title :src (at) :level :level3 :underline? @underline? :label ":level3 - Semibold 15px"] 68 | para-text 69 | [title :src (at) :level :level4 :underline? @underline? :label ":level4 - Semibold 15px"] 70 | para-text]]]]]] 71 | [parts-table "title" title-parts-desc]]])))) 72 | 73 | ;; core holds a reference to panel, so need one level of indirection to get figwheel updates 74 | (defn panel 75 | [] 76 | [title-demo]) 77 | -------------------------------------------------------------------------------- /src/re_demo/utils.clj: -------------------------------------------------------------------------------- 1 | (ns re-demo.utils) 2 | 3 | (defmacro with-src [body] 4 | `[[rdu/zprint-code '~body] 5 | ~body]) 6 | -------------------------------------------------------------------------------- /src/re_demo/v_box.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.v-box 2 | (:require [re-com.core :refer [at h-box gap v-box hyperlink-href p]] 3 | [re-com.box :refer [v-box-args-desc]] 4 | [re-demo.utils :refer [panel-title title2 args-table github-hyperlink status-text]])) 5 | 6 | (defn panel 7 | [] 8 | [v-box :src (at) 9 | :size "auto" 10 | :gap "10px" 11 | :children [[panel-title "[v-box ... ]" 12 | "src/re_com/box.cljs" 13 | "src/re_demo/v_box.cljs"] 14 | 15 | [h-box :src (at) 16 | :gap "100px" 17 | :children [[v-box :src (at) 18 | :gap "10px" 19 | :width "450px" 20 | :children [[title2 "Notes"] 21 | [status-text "Stable"] 22 | [p "Same as [h-box] except in the vertical direction."] 23 | [p "See [h-box] for further details."] 24 | [args-table v-box-args-desc]]] 25 | [v-box :src (at) 26 | :gap "10px" 27 | :children [[title2 "Demo"] 28 | [p "Refer to the full interactive demo in the [h-box] section, then tilt your head 90 degrees."]]]]]]]) -------------------------------------------------------------------------------- /src/re_demo/v_table_renderers.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.v-table-renderers 2 | (:require [re-com.core :refer [at v-box v-table label]] 3 | [re-com.util :refer [px]] 4 | [reagent.core :as reagent])) 5 | 6 | (defn box-with-border 7 | [{:keys [name background height width]}] 8 | [v-box :src (at) 9 | :height (px height) 10 | :width (if width (px width) "1 0 auto") 11 | :style {:color "white" :background-color background :padding "3px" :border "solid white 1px"} 12 | :align :center 13 | :justify :center 14 | :children [[label :src (at) :label name :style {:font-size 11 :font-weight "bold"}]]]) 15 | 16 | (defn table-showing-renderers 17 | [] 18 | (let [light-blue "#d860a0" 19 | blue "#60A0D8" 20 | gold "#d89860" 21 | green "#60d898" 22 | 23 | fib-ratio 0.618 ;; fibonacci ratios to make the visuals look pretty 24 | unit-50 50 ;; base for fibonacci calulations 25 | unit-121 (js/Math.round (/ unit-50 fib-ratio fib-ratio)) 26 | unit-31 (js/Math.round (* unit-50 fib-ratio)) 27 | 28 | num-rows 5 29 | row-height unit-31 30 | total-row-height (* num-rows row-height) 31 | 32 | width-of-main-row-content (js/Math.round (/ total-row-height fib-ratio)) 33 | dummy-rows (reagent/atom (mapv #(hash-map :id %1) (range num-rows)))] 34 | (fn [] 35 | [v-table :src (at) 36 | :model dummy-rows 37 | 38 | ;; Data Rows (section 5) 39 | :row-renderer (fn [_row_index, _row] [box-with-border {:name ":row-renderer" :background light-blue :height row-height :width width-of-main-row-content}]) 40 | :row-content-width width-of-main-row-content 41 | :row-height row-height 42 | :max-row-viewport-height (- total-row-height row-height) ;; force a vertical scrollbar 43 | 44 | ;; row header/footer (sections 2,8) 45 | :row-header-renderer (fn [_row-index, _row] [box-with-border {:name ":row-header-renderer " :background green :height unit-31 :width unit-121}]) 46 | :row-footer-renderer (fn [_row-index, _row] [box-with-border {:name ":row-footer-renderer" :background green :height unit-31 :width unit-121}]) 47 | 48 | ;; column header/footer (sections 4,6) 49 | :column-header-renderer (fn [] [box-with-border {:name ":column-header-renderer" :background gold :height unit-50 :width width-of-main-row-content}]) 50 | :column-header-height unit-50 51 | :column-footer-renderer (fn [] [box-with-border {:name ":column-footer-renderer" :background "#d8d460" :height unit-50 :width width-of-main-row-content}]) 52 | :column-footer-height unit-50 53 | 54 | ;; 4 corners (sections 1,3,7,9) 55 | :top-left-renderer (fn [] [box-with-border {:name ":top-left-renderer" :background blue :height unit-50 :width unit-121}]) 56 | :bottom-left-renderer (fn [] [box-with-border {:name ":bottom-left-renderer" :background blue :height unit-50 :width unit-121}]) 57 | :top-right-renderer (fn [] [box-with-border {:name ":top-right-renderer" :background blue :height unit-50 :width unit-121}]) 58 | :bottom-right-renderer (fn [] [box-with-border {:name ":bottom-right-renderer" :background blue :height unit-50 :width unit-121}])]))) 59 | 60 | -------------------------------------------------------------------------------- /src/re_demo/v_table_sections.cljs: -------------------------------------------------------------------------------- 1 | (ns re-demo.v-table-sections 2 | (:require 3 | [re-com.core :refer [at v-box v-table label]] 4 | [re-com.util :refer [px]] 5 | [reagent.core :as reagent])) 6 | 7 | (defn box-with-text 8 | [{:keys [name section background height width]}] 9 | [v-box :src (at) 10 | :height (str height "px") 11 | :width (if width (px width) "1 0 auto") 12 | :style {:color "white" :background-color background :padding "5px"} 13 | :align :center 14 | :justify :center 15 | :children [[label :src (at) :label section :style {:font-size 20}] 16 | [label :src (at) :label name :style {:font-size 10}]]]) 17 | 18 | (defn sections-render 19 | [] 20 | (let [light-blue "#C7AFE7" 21 | medium-blue "#60A0D8" 22 | blue "#0070C4" 23 | 24 | ;; only one fat row in this table 25 | single-dummy-row (reagent/atom [{:id 1}]) 26 | row-height 200 27 | 28 | width-of-main-row-content (int (/ row-height 0.618 0.618)) ;; fibonacci ratios to make it look pretty 29 | size2 (int (* row-height 0.618 0.618))] ;; fibonacci ratios to make it look pretty 30 | (fn [] 31 | [v-table :src (at) 32 | :model single-dummy-row 33 | 34 | ;; ===== Column header/footer (sections 4,6) 35 | :column-header-renderer (fn [] [box-with-text {:name "column headers" :section "4" :background medium-blue :height size2 :width width-of-main-row-content}]) 36 | :column-header-height size2 37 | :column-footer-renderer (fn [] [box-with-text {:name "column footers" :section "6" :background medium-blue :height size2 :width width-of-main-row-content}]) 38 | :column-footer-height size2 39 | 40 | ;; ===== Row header/footer (sections 2,8) 41 | :row-header-renderer (fn [_row-index, _row] [box-with-text {:name "row header" :section "2" :background medium-blue :height row-height :width size2}]) 42 | :row-footer-renderer (fn [_row-index, _row] [box-with-text {:name "row footer" :section "8" :background medium-blue :height row-height :width size2}]) 43 | 44 | ;; ===== Rows (section 5) 45 | :row-renderer (fn [_row_index, _row] [box-with-text {:name "row section" :section "5" :background light-blue :height row-height :width width-of-main-row-content}]) 46 | :row-content-width width-of-main-row-content 47 | :row-height row-height 48 | :max-row-viewport-height (- row-height size2) ;; deliberately create a vertical scrollbar, by not giving enough vertical space to ender the one row 49 | 50 | ;; ===== Corners (sections 1,3,7,9) 51 | :top-left-renderer (fn [] [box-with-text {:name "top left" :section "1" :background blue :height size2 :width size2}]) 52 | :bottom-left-renderer (fn [] [box-with-text {:name "bottom left" :section "3" :background blue :height size2 :width size2}]) 53 | :top-right-renderer (fn [] [box-with-text {:name "top right" :section "7" :background blue :height size2 :width size2}]) 54 | :bottom-right-renderer (fn [] [box-with-text {:name "bottom right" :section "9" :background blue :height size2 :width size2}])]))) 55 | 56 | ;; MT's Notes: 57 | ;; 58 | ;; On section width: 59 | ;; - the width of left sections 1,2,3 is determined by the widest hiccup returned by the 3 renderers for these sections. 60 | ;; - the width of center sections 4,5,6 is determined by `:row-content-width` 61 | ;; - the width of left sections 7,8,9 is determined by by the widest hiccup returned by the 3 renderers for these sections. 62 | ;; 63 | ;; the viewport width for 4,5,6 is determined by the widest hiccup returned by renderers. Once I put in an `h-box` it expanded out. When i only had `div` the viewport collapsed to the size of the content. 64 | ;; puzzled about column headings XXX 65 | ;; 66 | ;; I have to provide `:column-header-height`. Could the height of top sections 1, 4, 7 should provide the height. 67 | ;; 68 | ;; Discuss with Gregg and Isaac: 69 | ;; - the idea of variable row heights. 70 | ;; - sorting with `simple-v-table` 71 | ;; - performance: we have to reduce the amount of inline styles 72 | ;; - How do I create "CSS classes" in a namespace 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/user.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:require [shadow.cljs.devtools.api :as shadow] 3 | [shadow.cljs.devtools.server :as server])) 4 | 5 | (defn cljs-repl 6 | "Connects to a given build-id. Defaults to `:app`." 7 | ([] 8 | (cljs-repl :app)) 9 | ([build-id] 10 | (server/start!) 11 | (shadow/watch build-id) 12 | (shadow/nrepl-select build-id))) 13 | -------------------------------------------------------------------------------- /test/re_com/box_test.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.box-test 2 | (:require [cljs.test :refer-macros [is are deftest]] 3 | [reagent.core :as reagent] 4 | [re-com.box :as box])) 5 | 6 | (deftest test-flex-child-style 7 | (are [expected actual] (= expected actual) 8 | "initial" (:flex (box/flex-child-style "initial")) 9 | "auto" (:flex (box/flex-child-style "auto")) 10 | "none" (:flex (box/flex-child-style "none")) 11 | "0 0 100px" (:flex (box/flex-child-style "100px")) 12 | "0 0 4.5em" (:flex (box/flex-child-style "4.5em")) 13 | "60 1 0px" (:flex (box/flex-child-style "60%")) 14 | "60 1 0px" (:flex (box/flex-child-style "60")) 15 | "5 4 0%" (:flex (box/flex-child-style "5 4 0%")))) 16 | 17 | (deftest test-flex-flow-style 18 | (is (= (box/flex-flow-style "row wrap") 19 | {:-webkit-flex-flow "row wrap" 20 | :flex-flow "row wrap"}))) 21 | 22 | (deftest test-justify-style 23 | (let [make-expected (fn [x] {:-webkit-justify-content x 24 | :justify-content x})] 25 | (are [expected actual] (= expected actual) 26 | (make-expected "flex-start") (box/justify-style :start) 27 | (make-expected "flex-end") (box/justify-style :end) 28 | (make-expected "center") (box/justify-style :center) 29 | (make-expected "space-between") (box/justify-style :between) 30 | (make-expected "space-around") (box/justify-style :around)))) 31 | 32 | (deftest test-align-style 33 | (let [make-align-items (fn [x] {:-webkit-align-items x 34 | :align-items x})] 35 | (are [expected actual] (= expected actual) 36 | (make-align-items "flex-start") (box/align-style :align-items :start) 37 | (make-align-items "flex-end") (box/align-style :align-items :end) 38 | (make-align-items "center") (box/align-style :align-items :center) 39 | (make-align-items "baseline") (box/align-style :align-items :baseline) 40 | (make-align-items "stretch") (box/align-style :align-items :stretch)))) 41 | 42 | (deftest test-scroll-style 43 | (are [expected actual] (= expected actual) 44 | {:overflow "auto"} (box/scroll-style :overflow :auto) 45 | {:overflow "hidden"} (box/scroll-style :overflow :off) 46 | {:overflow "scroll"} (box/scroll-style :overflow :on) 47 | {:overflow "visible"} (box/scroll-style :overflow :spill))) 48 | 49 | (defn without-debug 50 | "Returns hiccup form without debug in first attrs as writing tests for that would be complex (i.e. equality of ref-fn fns etc)." 51 | [[tag attrs & rest]] 52 | (into [tag (dissoc attrs :data-rc :ref)] rest)) 53 | 54 | (deftest test-gap 55 | (are [expected actual] (= expected actual) 56 | [:div 57 | {:class ["rc-gap" "my-gap"] 58 | :style {:flex "0 0 1px" 59 | :-webkit-flex "0 0 1px"} 60 | :id "my-id"}] 61 | (without-debug (box/gap :class "my-gap" :attr {:id "my-id"} :size "1px")))) 62 | 63 | (deftest test-line 64 | (are [expected actual] (= expected actual) 65 | [:div 66 | {:class ["rc-line" "my-line"] 67 | :style {:flex "0 0 1px" 68 | :-webkit-flex "0 0 1px" 69 | :background-color "lightgray"} 70 | :id "my-id"}] 71 | (without-debug (box/line :class "my-line" :attr {:id "my-id"})))) 72 | 73 | (deftest test-box 74 | (are [expected actual] (= expected actual) 75 | [:div 76 | {:class ["rc-box" "display-flex" "my-box"] 77 | :style {:flex "none" 78 | :-webkit-flex "none" 79 | :flex-flow "inherit" 80 | :-webkit-flex-flow "inherit"} 81 | :id "my-id"} 82 | "text"] 83 | (without-debug (box/box :class "my-box" :attr {:id "my-id"} :child "text")))) 84 | 85 | (deftest test-scroller 86 | (are [expected actual] (= expected actual) 87 | [:div 88 | {:class ["rc-scroller" "display-flex" "my-scroller"] 89 | :style {:flex "auto" 90 | :-webkit-flex "auto" 91 | :flex-flow "inherit" 92 | :-webkit-flex-flow "inherit" 93 | :overflow "auto"} 94 | :id "my-id"} 95 | "text"] 96 | (without-debug (box/scroller :class "my-scroller" :attr {:id "my-id"} :child "text")))) 97 | 98 | (deftest test-border 99 | (are [expected actual] (= expected actual) 100 | [:div 101 | {:class ["rc-border" "display-flex" "my-border"] 102 | :style {:flex "none" 103 | :-webkit-flex "none" 104 | :flex-flow "inherit" 105 | :-webkit-flex-flow "inherit" 106 | :border "1px solid lightgrey"} 107 | :id "my-id"} 108 | "text"] 109 | (without-debug (box/border :class "my-border" :attr {:id "my-id"} :child "text")))) 110 | -------------------------------------------------------------------------------- /test/re_com/dropdown_test.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.dropdown-test 2 | (:require [cljs.test :refer-macros [is are deftest]] 3 | [reagent.core :as reagent] 4 | [re-com.dropdown :as dropdown])) 5 | -------------------------------------------------------------------------------- /test/re_com/misc_test.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.misc-test 2 | (:require-macros [re-com.core :refer [handler-fn]]) 3 | (:require [cljs.test :refer-macros [is deftest]] 4 | [reagent.core :as reagent])) 5 | 6 | (deftest test-handler-fn 7 | (let [atm (reagent/atom false)] 8 | (is (false? @atm)) 9 | (is (false? (reset! atm false))) 10 | (is (nil? ((handler-fn (reset! atm true) {}) (js/Event. "click")) "expected handler-fn to return nil")) 11 | (is (true? (reset! atm true))) 12 | (is (nil? ((handler-fn (reset! atm false) {}) (js/Event. "click")) "expected handler-fn to return nil")) 13 | (is (false? (reset! atm false))))) 14 | -------------------------------------------------------------------------------- /test/re_com/part_test.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.part-test 2 | (:require 3 | [cljs.test :refer-macros [is are deftest]] 4 | [re-com.part :as part])) 5 | 6 | (def structure 7 | [::wrapper 8 | [::cell-grid 9 | [::cell {:top-level-arg? true} 10 | [::cell-label]]]]) 11 | 12 | (deftest depth 13 | (is (= 1 (part/depth structure ::wrapper))) 14 | (is (= 4 (part/depth structure ::cell-label)))) 15 | 16 | (deftest tree-walk 17 | (is (= (tree-seq part/branch? part/children structure) 18 | '([::wrapper 19 | [::cell-grid 20 | [::cell {:top-level-arg? true} 21 | [::cell-label]]]] 22 | [::cell-grid 23 | [::cell {:top-level-arg? true} 24 | [::cell-label]]] 25 | [::cell {:top-level-arg? true} 26 | [::cell-label]] 27 | [::cell-label])))) 28 | 29 | (deftest get-part 30 | (let [get-part (partial part/get structure)] 31 | (are [props k] (get-part props k) 32 | {:parts {:wrapper true}} :wrapper 33 | {:parts {:wrapper true}} :wrapper 34 | {:cell true} :cell) 35 | (is (= [true true true true] 36 | [(part/top-level-arg? structure :cell) 37 | (get-part {:cell true} :cell) 38 | (not (part/top-level-arg? structure :wrapper)) 39 | (not 40 | (get-part {:wrapper true} :wrapper))]) 41 | "The getter function looks in a component's top-level keys, 42 | but only when :top-level-arg? is explicitly declared in the part structure."))) 43 | 44 | (deftest describe 45 | (is (= (part/describe structure) 46 | [{:name :wrapper, :class "rc-part-test-wrapper", :level 1, :impl "[:div]"} 47 | {:name :cell-grid, :class "rc-part-test-cell-grid", :level 2, :impl "[:div]"} 48 | {:name :cell, :class "rc-part-test-cell", :level 3, :impl "[:div]" :top-level-arg? true} 49 | {:name :cell-label, :class "rc-part-test-cell-label", :level 4, :impl "[:div]"}]))) 50 | 51 | (deftest validate-props 52 | (are [props problems] 53 | (= (part/args-valid? structure props []) 54 | problems) 55 | {:cell true} [] 56 | {:parts {:cell true}} [] 57 | {:parts {:wrapper true}} [] 58 | {:cell true 59 | :parts {:cell true}} [{:problem :part-top-level-collision 60 | :arg-name :cell}] 61 | {:wrapper true} [{:problem :part-top-level-unsupported 62 | :arg-name :wrapper}] 63 | {:wrapper true 64 | :cell true 65 | :parts {:cell true}} [{:problem :part-top-level-collision 66 | :arg-name :cell} 67 | {:problem :part-top-level-unsupported 68 | :arg-name :wrapper}])) 69 | -------------------------------------------------------------------------------- /test/re_com/selection_list_test.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.selection-list-test 2 | (:require [cljs.test :refer-macros [is are deftest]] 3 | [reagent.core :as reagent] 4 | [re-com.selection-list :as s-list])) 5 | 6 | ;; --- Utilities --- 7 | 8 | (defn div-app [] 9 | (let [div (.createElement js/document "div")] 10 | (set! (.-id div) "app") 11 | div)) 12 | 13 | (extend-type js/NodeList 14 | ISeqable 15 | (-seq [array] (array-seq array 0))) 16 | 17 | ;; --- Tests --- 18 | ;; NOTE: Commented out becasue internal representation of props changes with new versions of React/Reagent. 19 | ;; Find a more robust way to get props before reinstating 20 | 21 | #_(deftest test-selection-list 22 | (let [new-comp (reagent/render-component 23 | [s-list/selection-list 24 | :choices [{:id "1" :name "item 1"} {:id "2" :name "item 2"} {:id "3" :name "item 3"}] 25 | :model #{"2"} 26 | :on-change #(println %)] 27 | (div-app)) 28 | ;props (last (-> new-comp .-_renderedComponent .-props .-argv .-tail)) ;; Old internal representation 29 | props (last (-> new-comp .-_reactInternalInstance .-_renderedComponent .-_instance .-props .-argv .-tail)) ;; New internal representation (very fragile!) 30 | ] 31 | (is (true? (:multi-select? props)) "Expected :multi-select? to default to true.") 32 | (is (false? (:as-exclusions? props)) "Expected :as-exclusions? to default to false.") 33 | (is (false? (:required? props)) "Expected :required? to default to false.") 34 | (is (false? (:disabled? props)) "Expected :disabled? to default to false.") 35 | (is (false? (:hide-border? props)) "Expected :hide-border? to default to false.") 36 | (is (fn? (:label-fn props)) "Expected :label-fn to default to a function.")) 37 | (let [new-comp (reagent/render-component 38 | [s-list/selection-list 39 | :choices [{:id "1" :name "item 1"} {:id "2" :name "item 2"} {:id "3" :name "item 3"}] 40 | :model #{"2"} 41 | :multi-select? false 42 | :on-change #()] 43 | (div-app)) 44 | ;props (last (-> new-comp .-_renderedComponent .-props .-argv .-tail)) 45 | props (last (-> new-comp .-_reactInternalInstance .-_renderedComponent .-_instance .-props .-argv .-tail))] 46 | (is (false? (:multi-select? props)) "Expected :multi-select? to default to true."))) 47 | -------------------------------------------------------------------------------- /test/re_com/time_test.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.time-test 2 | (:require [cljs.test :refer-macros [is are deftest]] 3 | [reagent.core :as reagent] 4 | [re-com.input-time :as time])) 5 | 6 | ;; --- Tests --- 7 | 8 | (deftest test-valid-time? 9 | (are [expected actual] (= expected actual) 10 | true (time/valid-time? 0) 11 | true (time/valid-time? 600) 12 | true (time/valid-time? 130) 13 | true (time/valid-time? 2159) 14 | true (time/valid-time? 2430) 15 | false (time/valid-time? nil))) 16 | 17 | (deftest test-time->text 18 | (are [expected actual] (= expected actual) 19 | "00:00" (time/time->text 0) 20 | "06:00" (time/time->text 600) 21 | "11:00" (time/time->text 1100) 22 | "09:00" (time/time->text 900) 23 | "01:30" (time/time->text 130) 24 | "21:59" (time/time->text 2159) 25 | "24:30" (time/time->text 2430))) 26 | 27 | (deftest test-text->time 28 | (are [expected actual] (= expected actual) 29 | 600 (time/text->time "600") 30 | 630 (time/text->time "630") 31 | 630 (time/text->time "6:30") 32 | 630 (time/text->time "06:30") 33 | 3000 (time/text->time "30") 34 | 2359 (time/text->time "2359") 35 | 2359 (time/text->time "23:59"))) 36 | 37 | (deftest test-valid-text? 38 | (are [expected actual] (= expected actual) 39 | true (time/valid-text? "0000") 40 | true (time/valid-text? "00:00") 41 | true (time/valid-text? "99") 42 | false (time/valid-text? "a99") 43 | true (time/valid-text? "2359"))) 44 | 45 | ;; IJ: TODO 46 | #_(deftest test-input-time 47 | (is (fn? (time/input-time :model 1530 :minimum 600 :maximum 2159 :on-change #())) "Expected a function.") 48 | (let [input-time-fn (time/input-time :model 1530 :on-change #())] 49 | (is (fn? input-time-fn) "Expected a function.") 50 | (let [result (input-time-fn :model (reagent/atom 1530) :on-change #() :minimum 600 :maximum 2159)] 51 | ;(is (= :span.input-append (first result)) "Expected first element to be :span.input-append.bootstrap-timepicker") 52 | (is (fn? (first result)) "Expected first element to be a function") 53 | (let [input-time-comp (first (nth result 8)) 54 | input-time-attrs (last input-time-comp)] 55 | (is (= :input (first input-time-comp)) "Expected time input start with :input") 56 | (are [expected actual] (= expected actual) 57 | nil (:disabled input-time-attrs) 58 | "time-entry rc-time-entry " (:class input-time-attrs) 59 | "15:30" (:value input-time-attrs) 60 | "text" (:type input-time-attrs) 61 | "time-entry rc-time-entry " (:class input-time-attrs) 62 | true (fn? (:on-blur input-time-attrs)) 63 | true (fn? (:on-change input-time-attrs)))))) 64 | ;; These tests don't work. But i have verified that the check is happening and it works 65 | #_(is (thrown? js/Error (time/input-time :model "abc") "should fail - model is invalid")) 66 | #_(is (thrown? js/Error (time/input-time :model 930 :minimum "abc" :maximum 2159) "should fail - minimum is invalid")) 67 | #_(is (thrown? js/Error (time/input-time :model 930 :minimum 600 :maximum "fred") "should fail - maximum is invalid"))) 68 | 69 | ;; (is (thrown? js/Error (time/input-time :model 530 :minimum 600 :maximum 2159) "should fail - model is before range start")) 70 | ;; (is (thrown? js/Error (time/input-time :model 2230 :minimum 600 :maximum 2159) "should fail - model is after range end")) 71 | 72 | ;; IJ: TODO 73 | #_(deftest test-pre-conditions 74 | (is (fn? (time/input-time :model 1530 :minimum 600 :maximum 2159 :on-change #())) "Expected a function.") 75 | (is (thrown? js/Error (time/input-time :model 1530 :minimum 600 :maximum 2159 :fred "test") "Expected an exception due to invalid parameter."))) 76 | 77 | ;; --- WIP --- 78 | 79 | #_(defn div-app [] 80 | (let [div (.createElement js/document "div")] 81 | (set! (.-id div) "app") 82 | div)) 83 | 84 | #_(trace-forms 85 | {:tracer default-tracer} 86 | (deftest test-test-input-time-gen 87 | (let [tm-input (time/input-time :model 1500) 88 | result (reagent/render-component [tm-input] (div-app))] 89 | (println (-> result .-_renderedComponent .-props))))) 90 | 91 | ;; The above statement results in - 92 | ;; #js {:cljsArgv 93 | ;; [# 4) { 96 | ;; p__60249 = cljs.core.array_seq(Array.prototype.slice.call(arguments, 4),0); 97 | ;; } 98 | ;; return private_time_input__delegate.call(this,model,previous_val,min,max,p__60249); 99 | ;; }> 100 | ;; # 1500 101 | ;; # 102 | ;; # 103 | ;; :on-change nil 104 | ;; :disabled nil 105 | ;; :hide-border nil 106 | ;; :show-time-icon nil 107 | ;; :style nil], 108 | ;; :cljsLevel 0} 109 | -------------------------------------------------------------------------------- /test/re_com/validate_test.cljs: -------------------------------------------------------------------------------- 1 | (ns re-com.validate-test 2 | (:require [cljs.test :refer-macros [is are deftest]] 3 | [reagent.core :as reagent] 4 | [re-com.validate :as validate])) 5 | 6 | (deftest test-hash-map-with-name-keys 7 | (let [obj1 {:name "obj1", :value 1} 8 | obj2 {:name "obj2", :value 2} 9 | dup {:name "obj1", :value 3}] 10 | (are [expected actual] (= expected actual) 11 | {"obj1" obj1} (validate/hash-map-with-name-keys [obj1]) 12 | {"obj1" obj1, "obj2" obj2} (validate/hash-map-with-name-keys [obj1 obj2]) 13 | {"obj1" obj1, "obj2" obj2} (validate/hash-map-with-name-keys [obj1 obj2 obj1]) 14 | {"obj1" dup, "obj2" obj2} (validate/hash-map-with-name-keys [obj1 obj2 dup])))) 15 | 16 | (deftest test-extract-arg-data 17 | (let [arg1 {:name "arg1", :required true, :validate-fn true} 18 | arg2 {:name "arg2", :required false, :validate-fn true} 19 | args [arg1 20 | arg2 21 | {:name "arg3", :required true, :validate-fn false} 22 | {:name "arg4", :required false, :validate-fn false}] 23 | arg-data (validate/extract-arg-data args)] 24 | (is (= #{"arg1", "arg2", "arg3", "arg4"} (:arg-names arg-data))) 25 | (is (= #{"arg1", "arg3"} (:required-args arg-data))) 26 | (is (= {"arg1" arg1, "arg2" arg2} (:validated-args arg-data))))) 27 | 28 | (deftest test-arg-names-valid? 29 | (are [expected actual] (= expected actual) 30 | [] (validate/arg-names-known? #{:arg1} #{:arg1} []) 31 | [{:problem :unknown :arg-name :arg2}] (validate/arg-names-known? #{:arg1} #{:arg2} []) 32 | [] (validate/arg-names-known? #{:arg1 :arg2} #{:arg2} []) 33 | [{:problem :unknown :arg-name :arg3}] (validate/arg-names-known? #{:arg1 :arg2} #{:arg1 :arg3} []))) 34 | 35 | (deftest test-required-args-passed? 36 | (are [expected actual] (= expected actual) 37 | [] (validate/required-args? #{:arg1} #{:arg1} []) 38 | [{:problem :required :arg-name :arg1}] (validate/required-args? #{:arg1} #{:arg2} []) 39 | [{:problem :required :arg-name :arg1}] (validate/required-args? #{:arg1 :arg2} #{:arg2} []) 40 | [{:problem :required :arg-name :arg2}] (validate/required-args? #{:arg1 :arg2} #{:arg1 :arg3} []) 41 | [] (validate/required-args? #{:arg1 :arg2} #{:arg1 :arg2} []) 42 | [] (validate/required-args? #{:arg1 :arg2} #{:arg1 :arg2 :arg3} []))) 43 | 44 | (deftest test-extension-attribute? 45 | (is (validate/extension-attribute? :data-attribute)) 46 | (is (not (validate/extension-attribute? :foo-attribute))) 47 | (is (validate/extension-attribute? :aria-attribute)) 48 | (is (not (validate/extension-attribute? :dataattribute))) 49 | (is (not (validate/extension-attribute? :ariaattribute)))) 50 | 51 | (deftest test-string-or-atom? 52 | (are [expected actual] (= expected actual) 53 | true (validate/string-or-atom? "test") 54 | false (validate/string-or-atom? 1) 55 | true (validate/string-or-atom? (reagent/atom "test")) 56 | false (validate/string-or-atom? (reagent/atom 1)))) 57 | 58 | (deftest test-number-or-string? 59 | (are [expected actual] (= expected actual) 60 | true (validate/number-or-string? "test") 61 | true (validate/number-or-string? 1) 62 | true (validate/number-or-string? (reagent/atom "test")) 63 | true (validate/number-or-string? (reagent/atom 1)) 64 | false (validate/number-or-string? []) 65 | false (validate/number-or-string? (reagent/atom [])))) 66 | --------------------------------------------------------------------------------