├── .changeset ├── README.md ├── config.json └── nine-boats-try.md ├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── actions │ ├── changeset-snapshot │ │ └── action.yml │ └── node-setup │ │ └── action.yml │ ├── chromatic.yml │ ├── codeql-analysis.yml │ ├── generate-docs.yml │ ├── lint.yml │ ├── pre-release.yml │ └── release.yml ├── .gitignore ├── .npmrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── biome.json ├── build.Dockerfile ├── commitlint.config.js ├── lerna.json ├── package.json ├── packages ├── ag-grid-person-cell │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── agPersonCell.tsx │ │ ├── index.ts │ │ ├── personSort.tsx │ │ └── personTooltip.tsx │ └── tsconfig.json ├── ag-grid-utils │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── changeHandlerUtils │ │ │ ├── StatusComponent.tsx │ │ │ ├── StatusIcon.tsx │ │ │ ├── constants.ts │ │ │ ├── dataManipulators.ts │ │ │ ├── defaultOptions.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── index.ts │ └── tsconfig.json ├── context-selector │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── ContextProvider.tsx │ │ ├── ContextSearch.styles.ts │ │ ├── ContextSearch.tsx │ │ ├── ContextSelector.tsx │ │ ├── index.tsx │ │ └── types.ts │ └── tsconfig.json ├── date │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── DateRange.tsx │ │ ├── DateTime.tsx │ │ └── index.tsx │ └── tsconfig.json ├── errorboundary │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.tsx │ │ └── legacy │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── Fallback.tsx │ │ │ ├── FallbackIcon.tsx │ │ │ ├── fallbackRender.tsx │ │ │ ├── index.ts │ │ │ └── types.ts │ └── tsconfig.json ├── filter │ ├── .eslintrc │ ├── CHANGELOG.md │ ├── README.md │ ├── TODO.md │ ├── components │ │ └── package.json │ ├── options │ │ └── package.json │ ├── package.json │ ├── src │ │ ├── FilterProvider.tsx │ │ ├── actions.ts │ │ ├── components │ │ │ ├── filter │ │ │ │ ├── FilterOptionHeader.tsx │ │ │ │ ├── checkbox │ │ │ │ │ ├── CheckboxFilter.style.ts │ │ │ │ │ ├── CheckboxFilter.tsx │ │ │ │ │ ├── CheckboxFilterOption.style.ts │ │ │ │ │ ├── CheckboxFilterOption.tsx │ │ │ │ │ ├── CheckboxFilterOptionAll.tsx │ │ │ │ │ ├── CheckboxFilterOptions.tsx │ │ │ │ │ ├── CheckboxFilterProvider.tsx │ │ │ │ │ ├── context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── index.ts │ │ │ │ ├── search │ │ │ │ │ ├── SearchFilter.tsx │ │ │ │ │ └── index.ts │ │ │ │ └── types.ts │ │ │ ├── index.ts │ │ │ ├── misc │ │ │ │ ├── ClearFilterButton.tsx │ │ │ │ ├── FilterContainer.tsx │ │ │ │ ├── SelectionChips.tsx │ │ │ │ └── index.ts │ │ │ └── panel │ │ │ │ ├── FilterPanel.tsx │ │ │ │ ├── FilterPanelBar.tsx │ │ │ │ ├── FilterPanelFilters.tsx │ │ │ │ ├── FilterPanelProvider.tsx │ │ │ │ ├── FilterPanelSelector.tsx │ │ │ │ └── index.ts │ │ ├── context.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useClearFilter.ts │ │ │ ├── useFilter.ts │ │ │ ├── useFilterData.ts │ │ │ └── useFilterSelection.ts │ │ ├── index.ts │ │ ├── options │ │ │ ├── FilterOptionProvider.tsx │ │ │ ├── actions.ts │ │ │ ├── context.ts │ │ │ ├── create-options.ts │ │ │ ├── index.ts │ │ │ ├── reducer.ts │ │ │ ├── search │ │ │ │ ├── filter-option-match.ts │ │ │ │ ├── filter-option-search.ts │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ └── useFilterOptionSearch.ts │ │ │ └── types.ts │ │ ├── reducers.ts │ │ └── types.ts │ └── tsconfig.json ├── hanging-garden │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Garden.tsx │ │ ├── HangingGarden.tsx │ │ ├── components │ │ │ ├── PopoverContainer │ │ │ │ ├── Arrow.tsx │ │ │ │ └── index.tsx │ │ │ └── Vector2.ts │ │ ├── hooks │ │ │ ├── useHangingGardenData.tsx │ │ │ ├── useHangingGardenErrorMessage.tsx │ │ │ └── useHangingGardenGetData.tsx │ │ ├── index.tsx │ │ ├── models │ │ │ ├── ExpandedColumn.ts │ │ │ ├── GardenData.ts │ │ │ ├── GardenDataError.ts │ │ │ ├── HangingGarden.ts │ │ │ └── RenderContext.ts │ │ ├── renderHooks │ │ │ ├── useColumn.tsx │ │ │ ├── useGarden.tsx │ │ │ ├── useGroupHeader.tsx │ │ │ ├── useHangingGardenContext.tsx │ │ │ ├── useHeader.tsx │ │ │ ├── useItem.tsx │ │ │ ├── useItemDescription.tsx │ │ │ ├── usePixiApp.tsx │ │ │ ├── usePopover.tsx │ │ │ ├── useRenderQueue.tsx │ │ │ ├── useRendererSize.tsx │ │ │ ├── useScrolling.tsx │ │ │ ├── useTextNode.tsx │ │ │ └── useTextureCaches.tsx │ │ └── utils.ts │ ├── tsconfig.json │ └── types │ │ └── index.d.ts ├── list │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── CheckListItem.tsx │ │ │ ├── List.tsx │ │ │ ├── ListItem.tsx │ │ │ └── RadioListItem.tsx │ │ └── index.ts │ └── tsconfig.json ├── markdown │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── MarkdownEditor.tsx │ │ ├── MarkdownViewer.tsx │ │ └── index.tsx │ └── tsconfig.json ├── person │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── PersonAvatar.tsx │ │ ├── PersonCard.tsx │ │ ├── PersonCell.tsx │ │ ├── PersonListItem.tsx │ │ ├── PersonProvider.tsx │ │ ├── PersonSelect.tsx │ │ ├── extract-props.tsx │ │ └── index.tsx │ └── tsconfig.json ├── ripple │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Ripple.tsx │ │ └── index.tsx │ └── tsconfig.json ├── searchable-dropdown │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Dropdown.tsx │ │ ├── DropdownProvider.tsx │ │ ├── SearchableDropdown.tsx │ │ ├── index.tsx │ │ └── useDropdownProviderRef.ts │ └── tsconfig.json ├── side-sheet │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── .DS_Store │ │ │ ├── Actions.tsx │ │ │ ├── Content.tsx │ │ │ ├── Indicator.tsx │ │ │ ├── SideSheet.tsx │ │ │ ├── SideSheetBase.tsx │ │ │ ├── SubTitle.tsx │ │ │ ├── Title.tsx │ │ │ └── icon │ │ │ │ ├── FullscreenIcon.tsx │ │ │ │ ├── HandlerIcon.tsx │ │ │ │ └── index.ts │ │ └── index.ts │ └── tsconfig.json ├── skeleton │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Skeleton.tsx │ │ └── index.tsx │ └── tsconfig.json ├── stepper │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Step.tsx │ │ ├── StepBadge.tsx │ │ ├── StepContent.tsx │ │ ├── StepPane.tsx │ │ ├── Stepper.tsx │ │ ├── StepperContent.tsx │ │ ├── StepperControls.tsx │ │ ├── index.tsx │ │ └── utils.ts │ └── tsconfig.json ├── styles │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── StyleProvider.tsx │ │ ├── ThemeProvider.tsx │ │ ├── create-styles.ts │ │ ├── declarations.d.ts │ │ ├── index.ts │ │ ├── make-styles.ts │ │ ├── theme.ts │ │ └── types.ts │ └── tsconfig.json ├── textarea │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── TextArea.tsx │ │ ├── index.tsx │ │ └── types.ts │ └── tsconfig.json └── utils │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ ├── create-element.ts │ ├── create-synthetic-event.ts │ ├── element-attributes.ts │ ├── hooks │ │ ├── index.ts │ │ ├── useElementEvents.ts │ │ ├── useElementFunctions.ts │ │ ├── useElementProps.ts │ │ └── useForwardRef.ts │ ├── index.ts │ └── shallow-equal.ts │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── storybook ├── .babelrc.json ├── .gitignore ├── .storybook │ ├── main.ts │ ├── manager.ts │ ├── preview-head.html │ ├── preview.tsx │ └── theme.css ├── CHANGELOG.md ├── package.json ├── src │ ├── components │ │ ├── ChangeLog │ │ │ ├── ChangeLog.style.ts │ │ │ ├── ChangeLog.tsx │ │ │ ├── Lit.svg.tsx │ │ │ ├── React.svg.tsx │ │ │ └── index.ts │ │ ├── PackageInfo.tsx │ │ ├── StoryExample.tsx │ │ ├── Theme.tsx │ │ └── index.ts │ ├── stories │ │ ├── 01-intro.mdx │ │ ├── ag-grid-person-cell │ │ │ ├── ag-grid-person-cell.stories.tsx │ │ │ └── ag-person-cell.mdx │ │ ├── ag-grid │ │ │ └── ag-grid-utils.stories.tsx │ │ ├── context-selector │ │ │ ├── context-selector.helpers.tsx │ │ │ ├── context-selector.mdx │ │ │ └── context-selector.stories.tsx │ │ ├── date │ │ │ ├── date.mdx │ │ │ ├── daterange.stories.tsx │ │ │ └── datetime.stories.tsx │ │ ├── error-boundary │ │ │ ├── error-boundary.mdx │ │ │ └── error-boundary.stories.tsx │ │ ├── filter │ │ │ ├── filter.mdx │ │ │ ├── filter.stories.tsx │ │ │ └── generate-data.ts │ │ ├── list │ │ │ ├── list.mdx │ │ │ └── list.stories.tsx │ │ ├── markdown │ │ │ ├── demo.md │ │ │ ├── markdown.editor.stories.tsx │ │ │ ├── markdown.mdx │ │ │ └── markdown.viewer.stories.tsx │ │ ├── person │ │ │ ├── avatar │ │ │ │ ├── avatar.mdx │ │ │ │ └── avatar.stories.tsx │ │ │ ├── card.stories.tsx │ │ │ ├── cell.stories.tsx │ │ │ ├── list-item.stories.tsx │ │ │ ├── person-provider.ts │ │ │ ├── person.mdx │ │ │ ├── provider │ │ │ │ ├── provider.mdx │ │ │ │ └── provider.stories.tsx │ │ │ └── select.stories.tsx │ │ ├── searchable-dropdown │ │ │ ├── component.helpers.tsx │ │ │ ├── component.icons.tsx │ │ │ ├── searchable-dropdown.mdx │ │ │ └── searchable-dropdown.stories.tsx │ │ ├── sidesheet │ │ │ ├── side-sheet.mdx │ │ │ └── side-sheet.stories.tsx │ │ ├── skeleton │ │ │ ├── skeleton.mdx │ │ │ └── skeleton.stories.tsx │ │ ├── stepper │ │ │ ├── stepper.mdx │ │ │ └── stepper.stories.tsx │ │ └── textarea │ │ │ ├── textarea.mdx │ │ │ └── textarea.stories.tsx │ └── vite.config.js └── tsconfig.json ├── tsconfig.base.json └── tsconfig.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", 3 | "changelog": [ 4 | "@changesets/cli/changelog", 5 | { 6 | "repo": "equinor/fusion-react-components" 7 | } 8 | ], 9 | "commit": false, 10 | "fixed": [], 11 | "linked": [], 12 | "access": "public", 13 | "baseBranch": "origin/main", 14 | "updateInternalDependencies": "minor", 15 | "ignore": [] 16 | } 17 | -------------------------------------------------------------------------------- /.changeset/nine-boats-try.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@equinor/fusion-react-ag-grid-person-cell": patch 3 | "@equinor/fusion-react-searchable-dropdown": patch 4 | "@equinor/fusion-react-context-selector": patch 5 | "@equinor/fusion-react-ag-grid-utils": patch 6 | "@equinor/fusion-react-errorboundary": patch 7 | "@equinor/fusion-react-side-sheet": patch 8 | "@equinor/fusion-react-markdown": patch 9 | "@equinor/fusion-react-skeleton": patch 10 | "@equinor/fusion-react-textarea": patch 11 | "@equinor/fusion-react-stepper": patch 12 | "@equinor/fusion-react-filter": patch 13 | "@equinor/fusion-react-person": patch 14 | "@equinor/fusion-react-ripple": patch 15 | "@equinor/fusion-react-styles": patch 16 | "@equinor/fusion-react-utils": patch 17 | "@equinor/fusion-react-date": patch 18 | "@equinor/fusion-react-list": patch 19 | --- 20 | 21 | Change to biome linting rules 22 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | /docs 2 | /node_modules 3 | /packages/*/node_modules 4 | tsconfig.tsbuildinfo 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | open-pull-requests-limit: 50 8 | groups: 9 | storybook: 10 | applies-to: version-updates 11 | patterns: 12 | - "@storybook*" 13 | - "storybook*" 14 | ag-grid: 15 | applies-to: version-updates 16 | patterns: 17 | - "@ag-grid-community/*" 18 | fusion-wc: 19 | applies-to: version-updates 20 | patterns: 21 | - "@equinor/fusion-wc-*" 22 | - package-ecosystem: "github-actions" 23 | # Workflow files stored in the 24 | # default location of `.github/workflows` 25 | directory: "/" 26 | schedule: 27 | interval: "weekly" -------------------------------------------------------------------------------- /.github/workflows/actions/changeset-snapshot/action.yml: -------------------------------------------------------------------------------- 1 | name: release snapshot 2 | description: create a snapshot release 3 | inputs: 4 | tag: 5 | description: name of the tag to release 6 | required: true 7 | npm-token: 8 | description: token for pushing to npm 9 | required: true 10 | gh-token: 11 | description: github token 12 | default: ${{ github.token }} 13 | 14 | runs: 15 | using: composite 16 | steps: 17 | - name: Generate status 18 | id: status 19 | shell: bash 20 | run: | 21 | pnpm changeset status --output changeset.json 22 | REALEASE_COUNT=$(cat changeset.json | jq '.releases | length') 23 | echo "releases=${REALEASE_COUNT}" >> "$GITHUB_OUTPUT" 24 | env: 25 | GITHUB_TOKEN: ${{ inputs.gh-token }} 26 | 27 | - name: Create Release Pull Request or Publish to npm 28 | shell: bash 29 | if: ${{ steps.status.outputs.releases > 0 }} 30 | run: | 31 | pnpm config set '//registry.npmjs.org/:_authToken' "${NPM_AUTH_TOKEN}" 32 | pnpm changeset version --snapshot ${{ inputs.tag }} 33 | pnpm changeset publish --no-git-tag --tag ${{ inputs.tag }} 34 | env: 35 | NPM_AUTH_TOKEN: ${{ inputs.npm-token }} 36 | 37 | - uses: actions/upload-artifact@v3 38 | if: ${{ steps.status.outputs.releases > 0 }} 39 | with: 40 | name: changeset-status 41 | path: changeset.json 42 | -------------------------------------------------------------------------------- /.github/workflows/actions/node-setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup Node 2 | description: General setup of node 3 | inputs: 4 | node-version: 5 | description: 'Setting node version' 6 | default: '22' 7 | 8 | runs: 9 | using: composite 10 | steps: 11 | 12 | - uses: pnpm/action-setup@v4 13 | 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version: ${{ inputs.node-version }} 17 | cache: 'pnpm' 18 | registry-url: "https://registry.npmjs.org" 19 | 20 | - name: Install Node Dependencies 21 | shell: bash 22 | run: pnpm install -------------------------------------------------------------------------------- /.github/workflows/chromatic.yml: -------------------------------------------------------------------------------- 1 | # .github/workflows/chromatic.yml 2 | 3 | # Workflow name 4 | name: 'Chromatic' 5 | 6 | # Event for the workflow 7 | on: 8 | pull_request: 9 | push: 10 | branches: 11 | - main 12 | 13 | # List of jobs 14 | jobs: 15 | chromatic-deployment: 16 | # do not waste render on dependabot 17 | if: contains(fromJson('["dependabot[bot]", "dependabot-preview[bot]"]'), github.actor) == false 18 | # Operating System 19 | runs-on: ubuntu-latest 20 | # Job steps 21 | steps: 22 | # requires v1 @see https://www.chromatic.com/docs/github-actions 23 | - uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 0 26 | 27 | - name: Setup node and install deps 28 | uses: ./.github/workflows/actions/node-setup 29 | 30 | - name: Build libs 31 | run: pnpm build 32 | 33 | # 👇 Checks if the branch is not main and runs Chromatic 34 | - name: Publish to Chromatic 35 | if: github.ref != 'refs/heads/main' 36 | uses: chromaui/action@v12 37 | # Required options for the Chromatic GitHub Action 38 | with: 39 | workingDir: storybook 40 | buildScriptName: build 41 | token: ${{ secrets.GITHUB_TOKEN }} 42 | projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} 43 | 44 | # 👇 Checks if the branch is main and accepts all changes in Chromatic 45 | - name: Publish to Chromatic and auto accept changes 46 | if: github.ref == 'refs/heads/main' 47 | uses: chromaui/action@v12 48 | # Required options for the Chromatic GitHub Action 49 | with: 50 | workingDir: storybook 51 | buildScriptName: build 52 | token: ${{ secrets.GITHUB_TOKEN }} 53 | projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} 54 | autoAcceptChanges: true # 👈 Option to accept all changes -------------------------------------------------------------------------------- /.github/workflows/generate-docs.yml: -------------------------------------------------------------------------------- 1 | name: Generate Docs 2 | 3 | on: 4 | workflow_dispatch: 5 | workflow_call: 6 | 7 | permissions: 8 | contents: read 9 | pages: write 10 | id-token: write 11 | 12 | jobs: 13 | build: 14 | if: github.ref == 'refs/heads/main' 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout docs 18 | uses: actions/checkout@v4 19 | 20 | - name: Setup node and install deps 21 | uses: ./.github/workflows/actions/node-setup 22 | 23 | - name: Build project 24 | run: pnpm build 25 | 26 | - name: Build storybook 27 | run: pnpm storybook:build 28 | 29 | - name: Upload artifact 30 | uses: actions/upload-pages-artifact@v3 31 | with: 32 | path: ./storybook/dist 33 | 34 | deploy: 35 | environment: 36 | name: github-pages 37 | url: ${{ steps.deployment.outputs.page_url }} 38 | runs-on: ubuntu-latest 39 | needs: build 40 | steps: 41 | - name: Deploy to GitHub Pages 42 | id: deployment 43 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint project files 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - ready_for_review 7 | - opened 8 | - reopened 9 | - synchronize 10 | 11 | jobs: 12 | biome: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | pull-requests: write 16 | contents: read 17 | checks: write 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Setup node and install deps 23 | uses: ./.github/workflows/actions/node-setup 24 | 25 | - name: Build project 26 | run: pnpm build 27 | 28 | - name: Setup Biome CLI 29 | uses: biomejs/setup-biome@v2 30 | 31 | - name: Run Biome 32 | run: biome ci 33 | -------------------------------------------------------------------------------- /.github/workflows/pre-release.yml: -------------------------------------------------------------------------------- 1 | name: Pre-release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tag: 7 | description: 'Realse tag (PR)' 8 | required: true 9 | 10 | jobs: 11 | publish: 12 | name: Publish to PR 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout project 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Setup node and install deps 21 | uses: ./.github/workflows/actions/node-setup 22 | 23 | - name: Config git user 24 | run: | 25 | git config --global user.name "${{ github.actor }}" 26 | git config --global user.email "${{ github.actor }}@users.noreply.github.com" 27 | 28 | - name: Create snapshot 29 | uses: ./.github/workflows/actions/changeset-snapshot 30 | with: 31 | tag: ${{ inputs.tag }} 32 | npm-token: ${{ secrets.NPM_AUTH_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | paths: 9 | - 'packages/**' 10 | - '.changeset/*.md' 11 | 12 | jobs: 13 | release-pkg: 14 | name: Create release 15 | runs-on: ubuntu-latest 16 | if: github.ref == 'refs/heads/main' 17 | outputs: 18 | published: ${{ steps.changesets.outputs.published }} 19 | hasChangesets: ${{ steps.changesets.outputs.hasChangesets }} 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | 25 | - name: Setup node and install deps 26 | uses: ./.github/workflows/actions/node-setup 27 | 28 | - name: Config git user 29 | run: | 30 | git config --global user.name "${{ github.actor }}" 31 | git config --global user.email "${{ github.actor }}@users.noreply.github.com" 32 | pnpm config set '//registry.npmjs.org/:_authToken' "${NPM_AUTH_TOKEN}" 33 | env: 34 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 35 | 36 | - name: Create Release Pull Request or Publish to npm 37 | id: changesets 38 | uses: changesets/action@v1 39 | with: 40 | title: '🤖 Bip Bop - Fusion React Components Release' 41 | commit: 'chore: version packages' 42 | createGithubReleases: true 43 | setupGitUser: false 44 | version: pnpm changeset:version 45 | publish: pnpm changeset:publish 46 | env: 47 | GITHUB_TOKEN: ${{ github.token }} 48 | NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 49 | 50 | - name: convert Changeset PR to draf 51 | if: steps.changesets.outputs.published == 'false' && steps.changesets.outputs.pullRequestNumber 52 | run: gh pr ready ${{ steps.changesets.outputs.pullRequestNumber }} --undo 53 | env: 54 | GH_TOKEN: ${{ github.token }} 55 | 56 | documentation: 57 | name: Update documentation 58 | needs: release-pkg 59 | if: needs.release-pkg.outputs.published == 'true' 60 | uses: ./.github/workflows/generate-docs.yml 61 | secrets: inherit 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | eslint-report.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # TypeScript cache 43 | *.tsbuildinfo 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Microbundle cache 52 | .rpt2_cache/ 53 | .rts2_cache_cjs/ 54 | .rts2_cache_es/ 55 | .rts2_cache_umd/ 56 | 57 | # Optional REPL history 58 | .node_repl_history 59 | 60 | # Output of 'npm pack' 61 | *.tgz 62 | 63 | # Yarn Integrity file 64 | .yarn-integrity 65 | 66 | # dotenv environment variables file 67 | .env 68 | .env.test 69 | 70 | # parcel-bundler cache (https://parceljs.org/) 71 | .cache 72 | 73 | # npm local install 74 | .yalc 75 | yalc.lock 76 | 77 | 78 | # ignore lock file of packages - might include later, usally conflixt with hoisting 79 | packages/**/package-lock.json 80 | packages/**/dist 81 | 82 | storybook/package-lock.json 83 | 84 | docs/ 85 | 86 | 87 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | enable-pre-post-scripts=true 3 | auto-install-peers=true 4 | legacy-peer-deps=true 5 | node-linker=hoisted -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Equinor 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fusion-React-Components 2 | 3 | > Mono-repo for React-components used in fusion apps and portals 4 | 5 | This repo is only for custom components used in Fusion, please check [EDS](https://storybook.eds.equinor.com/?path=/docs/introduction--docs) before using any components 6 | 7 | ## [Storybook](https://equinor.github.io/fusion-react-components/?path=/docs/intro--docs/) 8 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | If you discover a security vulnerability in this project, please follow these steps to responsibly disclose it: 2 | 3 | 1. **Do not** create a public GitHub issue for the vulnerability. 4 | 2. Follow our guideline for Responsible Disclosure Policy at [https://www.equinor.com/about-us/csirt](https://www.equinor.com/about-us/csirt) to report the issue 5 | 6 | The following information will help us triage your report more quickly: 7 | 8 | - Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 9 | - Full paths of source file(s) related to the manifestation of the issue 10 | - The location of the affected source code (tag/branch/commit or direct URL) 11 | - Any special configuration required to reproduce the issue 12 | - Step-by-step instructions to reproduce the issue 13 | - Proof-of-concept or exploit code (if possible) 14 | - Impact of the issue, including how an attacker might exploit the issue 15 | 16 | We prefer all communications to be in English. 17 | -------------------------------------------------------------------------------- /build.Dockerfile: -------------------------------------------------------------------------------- 1 | # Test file for building project, emulate GitHub 2 | 3 | FROM ubuntu:20.04 4 | 5 | ARG NODE_VERSION=16 6 | 7 | WORKDIR /app 8 | 9 | RUN apt-get update \ 10 | && apt-get install -y curl \ 11 | && curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - \ 12 | && apt-get install -y nodejs 13 | 14 | COPY . . 15 | 16 | RUN npm ci --legacy-peer-deps --ignore-scripts 17 | RUN npm run bootstrap 18 | RUN npm run build 19 | RUN npm run lint 20 | RUN npm run --prefix storybook build 21 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | extends: ['@commitlint/config-conventional'], 3 | }; 4 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/lerna/schemas/lerna-schema.json", 3 | "version": "independent", 4 | "npmClient": "pnpm", 5 | "packages": ["packages/*", "storybook"] 6 | } 7 | -------------------------------------------------------------------------------- /packages/ag-grid-person-cell/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-ag-grid-person-cell", 3 | "version": "2.0.4", 4 | "description": "React component for displaying person details in AgGrid cell and PersonCard on cell hover", 5 | "keywords": [ 6 | "person", 7 | "ag cell", 8 | "person cell", 9 | "card", 10 | "react", 11 | "fusion", 12 | "equinor", 13 | "people", 14 | "personnel", 15 | "aggrid", 16 | "ag card" 17 | ], 18 | "homepage": "https://github.com/equinor/fusion-react-components/tree/master/packages/ag-grid-person#readme", 19 | "license": "ISC", 20 | "main": "dist/index.js", 21 | "module": "dist/index.js", 22 | "types": "dist/index.d.ts", 23 | "directories": { 24 | "dist": "dist" 25 | }, 26 | "files": ["dist"], 27 | "publishConfig": { 28 | "access": "public" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/equinor/fusion-react-components.git" 33 | }, 34 | "scripts": { 35 | "build": "tsc -b", 36 | "lint": "eslint --ext .ts,.tsx,.js src/", 37 | "lint:fix": "eslint --ext .ts,.tsx,.js src/ --fix", 38 | "prepack": "npm run build" 39 | }, 40 | "bugs": { 41 | "url": "https://github.com/equinor/fusion-react-components/issues" 42 | }, 43 | "dependencies": { 44 | "@equinor/fusion-react-person": "workspace:^", 45 | "@equinor/fusion-react-utils": "workspace:^", 46 | "styled-components": "^6.1.14" 47 | }, 48 | "peerDependencies": { 49 | "@equinor/eds-core-react": "^0.39.0", 50 | "@equinor/eds-tokens": "^0.9.2", 51 | "ag-grid-community": "33", 52 | "react": "^17.0.0 || ^18.0.0" 53 | }, 54 | "devDependencies": { 55 | "@types/react": "^18.2.74", 56 | "ag-grid-community": "33.2.4", 57 | "react": "^18.2.0" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/ag-grid-person-cell/src/index.ts: -------------------------------------------------------------------------------- 1 | export { agGridPersonCell } from './agPersonCell'; 2 | -------------------------------------------------------------------------------- /packages/ag-grid-person-cell/src/personSort.tsx: -------------------------------------------------------------------------------- 1 | export const personSortComparator = 2 | (accessor: (data: T) => string | undefined) => 3 | (_valueA: T, _valueB: T) => { 4 | const a = accessor(_valueA); 5 | const b = accessor(_valueB); 6 | if (!a) return -1; 7 | if (!b) return 1; 8 | return a.localeCompare(b); 9 | }; 10 | 11 | export default personSortComparator; 12 | -------------------------------------------------------------------------------- /packages/ag-grid-person-cell/src/personTooltip.tsx: -------------------------------------------------------------------------------- 1 | import { PersonCard, type PersonCellData } from '@equinor/fusion-react-person'; 2 | 3 | type TooltipCard = { 4 | value: T; 5 | azureId?: (data: T) => string; 6 | upn?: (data: T) => string; 7 | dataSource?: (data: T) => PersonCellData; 8 | }; 9 | 10 | export const personTooltip = (props: TooltipCard): JSX.Element => { 11 | const { value, azureId, upn, dataSource } = props; 12 | const azureResult = azureId ? azureId(value) : undefined; 13 | const upnResult = upn ? upn(value) : undefined; 14 | const dataSourceResult = dataSource ? dataSource(value) : undefined; 15 | 16 | return ( 17 | 18 | ); 19 | }; 20 | 21 | export default personTooltip; 22 | -------------------------------------------------------------------------------- /packages/ag-grid-person-cell/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts", "src/**/*.tsx"], 8 | "exclude": ["node_modules"], 9 | "references": [ 10 | { 11 | "path": "../utils" 12 | }, 13 | { 14 | "path": "../person" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/ag-grid-utils/README.md: -------------------------------------------------------------------------------- 1 | # AG Grid 2 | 3 | ## Change handler 4 | 5 | > utility for showing which fields has changed 6 | [Storybook](https://equinor.github.io/fusion-react-components/?path=/docs/examples-ag-grid-utils--change-handler) 7 | 8 | -------------------------------------------------------------------------------- /packages/ag-grid-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-ag-grid-utils", 3 | "version": "32.0.0", 4 | "description": "React addons for ag-grid utilities", 5 | "keywords": ["ag-grid", "react", "fusion", "equinor"], 6 | "homepage": "https://github.com/equinor/fusion-react-components/tree/master/packages/ag-grid-utils#readme", 7 | "license": "ISC", 8 | "main": "dist/index.js", 9 | "directories": { 10 | "dist": "dist" 11 | }, 12 | "files": ["dist"], 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/equinor/fusion-react-components.git" 19 | }, 20 | "scripts": { 21 | "build": "tsc -b", 22 | "lint": "eslint --ext .ts,.tsx,.js src/", 23 | "lint:fix": "eslint --ext .ts,.tsx,.js src/ --fix", 24 | "prepack": "npm run build" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/equinor/fusion-react-components/issues" 28 | }, 29 | "devDependencies": { 30 | "ag-grid-community": "33.2.4", 31 | "@equinor/eds-icons": "~0.22.0", 32 | "@equinor/eds-tokens": "^0.9.2", 33 | "@types/react": "^18.2.24", 34 | "react": "^18.2.0", 35 | "react-dom": "^18.2.0", 36 | "styled-components": "^6.1.1" 37 | }, 38 | "peerDependencies": { 39 | "ag-grid-community": "33.2.4", 40 | "@equinor/eds-core-react": "^0.45.0", 41 | "@equinor/eds-icons": "~0.22.0", 42 | "@equinor/eds-tokens": "^0.9.2", 43 | "react": "^18", 44 | "react-dom": "^18", 45 | "styled-components": "^6" 46 | }, 47 | "peerDependenciesMeta": { 48 | "react-dom": { 49 | "optional": true 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/ag-grid-utils/src/changeHandlerUtils/StatusComponent.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react'; 2 | 3 | import type { ICellRendererParams } from 'ag-grid-community'; 4 | 5 | import { styled } from 'styled-components'; 6 | 7 | import { StatusIcon } from './StatusIcon'; 8 | 9 | const Styled = { 10 | Container: styled('div')` 11 | display: flex; 12 | align-items: center; 13 | justify-content: center; 14 | height: 100%; 15 | `, 16 | }; 17 | 18 | /** 19 | * Renders a status component for a cell in an ag-grid table. 20 | * @param props - The cell renderer parameters. 21 | * @returns The rendered status component. 22 | */ 23 | export const StatusComponent: FC = (props: ICellRendererParams) => { 24 | const isGroupRow = props.node.group; 25 | if (isGroupRow) return null; 26 | return ( 27 | 28 | 29 | 30 | ); 31 | }; 32 | 33 | export default StatusComponent; 34 | -------------------------------------------------------------------------------- /packages/ag-grid-utils/src/changeHandlerUtils/StatusIcon.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react'; 2 | 3 | import { AGGridDataStatus } from './constants'; 4 | 5 | import { tokens } from '@equinor/eds-tokens'; 6 | 7 | import { Icon } from '@equinor/eds-core-react'; 8 | import { check, check_circle_outlined, new_label } from '@equinor/eds-icons'; 9 | Icon.add({ check, check_circle_outlined, new_label }); 10 | 11 | type StatusIconProps = { readonly status?: AGGridDataStatus }; 12 | 13 | /** 14 | * Renders a status icon based on the provided status. 15 | * @param status - The status of the icon. 16 | * @returns The rendered status icon component. 17 | */ 18 | export const StatusIcon: FC = ({ status }) => { 19 | switch (status) { 20 | case AGGridDataStatus.NEW: 21 | return ; 22 | case AGGridDataStatus.PATCHED: 23 | return ( 24 | 25 | ); 26 | case AGGridDataStatus.FETCHED: 27 | return ; 28 | } 29 | return null; 30 | }; 31 | 32 | export default StatusIcon; 33 | -------------------------------------------------------------------------------- /packages/ag-grid-utils/src/changeHandlerUtils/constants.ts: -------------------------------------------------------------------------------- 1 | export enum AGGridDataStatus { 2 | NEW = 0, 3 | FETCHED = 1, 4 | PATCHED = 2, 5 | } 6 | -------------------------------------------------------------------------------- /packages/ag-grid-utils/src/changeHandlerUtils/dataManipulators.ts: -------------------------------------------------------------------------------- 1 | import { AGGridDataStatus } from './constants'; 2 | import type { AGGridData, RowCompareFunc } from './types'; 3 | 4 | /** 5 | * Default compare function used to compare row values. 6 | * @param value1 - The first value to compare. 7 | * @param value2 - The second value to compare. 8 | * @returns A boolean value indicating whether the values are different. 9 | */ 10 | export const defaultCompareFunc: RowCompareFunc = (value1, value2) => value1 !== value2; 11 | 12 | /** 13 | * Checks if there are any changes in the data row object. 14 | * @param dataRow - The data row object to check for changes. 15 | * @param rowCompareFunc - Optional function used to compare row values. If not provided, a default compare function will be used. 16 | * @returns A boolean value indicating whether there are any changes in the data row. 17 | */ 18 | export const checkForChanges = (dataRow: AGGridData, rowCompareFunc?: RowCompareFunc): boolean => { 19 | const compareFunction = rowCompareFunc ?? defaultCompareFunc; 20 | return Object.entries(dataRow.initial).some(([key, value]) => 21 | compareFunction(dataRow.current[key], value), 22 | ); 23 | }; 24 | 25 | /** 26 | * Add initial, current, status and hasChanges properties to the data row. 27 | * @template T - data type 28 | * @param dataRow - The row to be manipulated. 29 | * @param status - The status of the row. Default value is AGGridDataStatus.FETCHED. 30 | * @param rowCompareFunc - Optional function used to compare row values. If not provided, a default compare function will be used. 31 | * @returns The data row wrapped in AGGridData type. 32 | */ 33 | export const addInitialProps = >( 34 | dataRow: T, 35 | status: AGGridDataStatus = AGGridDataStatus.FETCHED, 36 | rowCompareFunc?: RowCompareFunc, 37 | ): AGGridData => ({ 38 | initial: dataRow, 39 | current: { ...dataRow }, 40 | status, 41 | get hasChanged() { 42 | return checkForChanges(this, rowCompareFunc); 43 | }, 44 | }); 45 | -------------------------------------------------------------------------------- /packages/ag-grid-utils/src/changeHandlerUtils/index.ts: -------------------------------------------------------------------------------- 1 | export { addInitialProps } from './dataManipulators'; 2 | export { 3 | createStatusField, 4 | defaultValueGetter, 5 | defaultValueSetter, 6 | getDefaultId, 7 | } from './defaultOptions'; 8 | export { AGGridDataStatus } from './constants'; 9 | export { StatusComponent } from './StatusComponent'; 10 | 11 | export * from './types'; 12 | -------------------------------------------------------------------------------- /packages/ag-grid-utils/src/changeHandlerUtils/types.ts: -------------------------------------------------------------------------------- 1 | import type { AGGridDataStatus } from './constants'; 2 | 3 | export type AGGridData = Record> = { 4 | initial: T; 5 | current: T; 6 | status: AGGridDataStatus; 7 | readonly hasChanged: boolean; 8 | }; 9 | 10 | export type RowCompareFunc = (value1: unknown, value2: unknown) => boolean; 11 | -------------------------------------------------------------------------------- /packages/ag-grid-utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './changeHandlerUtils'; 2 | -------------------------------------------------------------------------------- /packages/ag-grid-utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts", "src/**/*.tsx"], 8 | "exclude": ["node_modules"], 9 | "references": [] 10 | } 11 | -------------------------------------------------------------------------------- /packages/context-selector/README.md: -------------------------------------------------------------------------------- 1 | 2 | # @equinor/fusion-react-context-selector 3 | 4 | [![Published on npm](https://img.shields.io/npm/v/@equinor/fusion-react-context-selector.svg)](https://www.npmjs.com/package/@equinor/fusion-react-context-selector) 5 | 6 | [Storybook](https://equinor.github.io/fusion-react-components/?path=/docs/data-contextselector) 7 | 8 | Extending [fusion-react-searchable-dropdown](https://equinor.github.io/fusion-react-components/?path=/docs/data-searchable-dropdown) 9 | 10 | Built on Web-Component [Fusion Web Component](https://github.com/equinor/fusion-web-components/tree/main/packages/searchable-dropdown) 11 | 12 | ### Installation 13 | 14 | ```sh 15 | npm install @equinor/fusion-react-context-selector 16 | ``` 17 | -------------------------------------------------------------------------------- /packages/context-selector/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-context-selector", 3 | "version": "1.0.4", 4 | "description": "React component for context selector", 5 | "keywords": [ 6 | "contextselector", 7 | "searchabledropdown", 8 | "textinput", 9 | "list", 10 | "react", 11 | "fusion", 12 | "equinor" 13 | ], 14 | "homepage": "https://github.com/equinor/fusion-react-components/tree/master/packages/context-selector#readme", 15 | "license": "ISC", 16 | "main": "dist/index.js", 17 | "directories": { 18 | "dist": "dist" 19 | }, 20 | "files": ["dist"], 21 | "publishConfig": { 22 | "access": "public" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/equinor/fusion-react-components.git" 27 | }, 28 | "scripts": { 29 | "build": "tsc -b", 30 | "lint": "eslint --ext .ts,.tsx,.js src/", 31 | "lint:fix": "eslint --ext .ts,.tsx,.js src/ --fix", 32 | "prepack": "npm run build" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/equinor/fusion-react-components/issues" 36 | }, 37 | "dependencies": { 38 | "@equinor/fusion-react-searchable-dropdown": "workspace:^", 39 | "@equinor/fusion-react-styles": "workspace:^", 40 | "@equinor/fusion-react-utils": "workspace:^", 41 | "@equinor/fusion-wc-icon": "^2.3.1", 42 | "@equinor/fusion-wc-searchable-dropdown": "^4.0.3" 43 | }, 44 | "devDependencies": { 45 | "@types/react": "^18.2.24", 46 | "react": "^18.2.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/context-selector/src/ContextProvider.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | DropdownProvider, 3 | type SearchableDropdownResolver, 4 | useDropdownProviderRef, 5 | } from '@equinor/fusion-react-searchable-dropdown'; 6 | 7 | import type { ContextProviderProps } from './types'; 8 | 9 | export const ContextProvider = ({ 10 | children, 11 | ...props 12 | }: React.PropsWithChildren): JSX.Element => { 13 | const contextResolverRef = useDropdownProviderRef(props.resolver as SearchableDropdownResolver); 14 | return {children}; 15 | }; 16 | 17 | export default ContextProvider; 18 | -------------------------------------------------------------------------------- /packages/context-selector/src/ContextSelector.tsx: -------------------------------------------------------------------------------- 1 | import { Dropdown } from '@equinor/fusion-react-searchable-dropdown'; 2 | import type { ContextSelectorProps } from './types'; 3 | 4 | export const ContextSelector = ({ 5 | children, 6 | ...props 7 | }: React.PropsWithChildren): JSX.Element => { 8 | return {children}; 9 | }; 10 | 11 | export default ContextSelector; 12 | -------------------------------------------------------------------------------- /packages/context-selector/src/index.tsx: -------------------------------------------------------------------------------- 1 | export { ContextProvider } from './ContextProvider'; 2 | export { ContextSelector } from './ContextSelector'; 3 | export { ContextSearch, type ContextSearchProps, ContextClearEvent } from './ContextSearch'; 4 | export type { 5 | ContextProviderProps, 6 | ContextResolver, 7 | ContextResult, 8 | ContextResultItem, 9 | ContextSelectEvent, 10 | ContextSelectorProps, 11 | } from './types'; 12 | 13 | export { ContextSelector as default } from './ContextSelector'; 14 | -------------------------------------------------------------------------------- /packages/context-selector/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | DropdownProps, 3 | SearchableDropdownResultItem, 4 | SearchableDropdownSelectEvent, 5 | } from '@equinor/fusion-react-searchable-dropdown'; 6 | 7 | export type ContextResultItem = Pick< 8 | SearchableDropdownResultItem, 9 | keyof SearchableDropdownResultItem 10 | >; 11 | export type ContextResult = Array; 12 | export type ContextSelectEvent = SearchableDropdownSelectEvent; 13 | export type ContextSelectorProps = DropdownProps; 14 | 15 | export interface ContextResolver { 16 | searchQuery: (queryString: string) => Promise | ContextResult; 17 | initialResult?: ContextResult; 18 | closeHandler?: (e: MouseEvent) => void; 19 | } 20 | 21 | export type ContextProviderProps = { 22 | resolver?: ContextResolver; 23 | }; 24 | -------------------------------------------------------------------------------- /packages/context-selector/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts", "src/**/*.tsx"], 8 | "exclude": ["node_modules"], 9 | "references": [ 10 | { "path": "../utils" }, 11 | { "path": "../searchable-dropdown" }, 12 | { "path": "../styles" } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/date/README.md: -------------------------------------------------------------------------------- 1 | [Storybook](https://equinor.github.io/fusion-react-components/?path=/docs/data-date) 2 | -------------------------------------------------------------------------------- /packages/date/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-date", 3 | "version": "0.5.3", 4 | "description": "component for formatting and displaying dates", 5 | "keywords": ["date", "react", "fusion", "equinor"], 6 | "homepage": "https://github.com/equinor/fusion-react-components/tree/master/packages/date#readme", 7 | "license": "ISC", 8 | "main": "dist/index.js", 9 | "directories": { 10 | "dist": "dist" 11 | }, 12 | "files": ["dist"], 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/equinor/fusion-react-components.git" 19 | }, 20 | "scripts": { 21 | "build": "tsc -b", 22 | "watch": "tsc -w", 23 | "lint": "eslint --ext .ts,.tsx,.js src/", 24 | "lint:fix": "eslint --ext .ts,.tsx,.js src/ --fix", 25 | "prepack": "npm run build" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/equinor/fusion-react-components/issues" 29 | }, 30 | "dependencies": { 31 | "@equinor/fusion-react-utils": "^2.1.0", 32 | "@equinor/fusion-wc-date": "^1.1.3" 33 | }, 34 | "devDependencies": { 35 | "@types/react": "^18.2.24", 36 | "react": "^18.2.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/date/src/DateRange.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentProps, createComponent } from '@equinor/fusion-react-utils'; 2 | import { 3 | DateRangeElement as HTMLDateRangeCustomElement, 4 | dateRangeTag, 5 | } from '@equinor/fusion-wc-date'; 6 | 7 | type ElementProps = React.PropsWithChildren< 8 | Partial< 9 | Pick< 10 | HTMLDateRangeCustomElement, 11 | | 'from' 12 | | 'to' 13 | | 'format' 14 | | 'suffix' 15 | | 'variant' 16 | | 'locale' 17 | | 'seconds' 18 | | 'weekstart' 19 | | 'capitalize' 20 | > 21 | > 22 | >; 23 | 24 | export const DateRange = createComponent( 25 | HTMLDateRangeCustomElement, 26 | dateRangeTag, 27 | ); 28 | 29 | export type DateRangeProps = ComponentProps; 30 | 31 | export { HTMLDateRangeCustomElement }; 32 | 33 | export default DateRange; 34 | -------------------------------------------------------------------------------- /packages/date/src/DateTime.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentProps, createComponent } from '@equinor/fusion-react-utils'; 2 | import { DateTimeElement as HTMLDateTimeCustomElement, dateTimeTag } from '@equinor/fusion-wc-date'; 3 | 4 | type ElementProps = React.PropsWithChildren< 5 | Partial> 6 | >; 7 | 8 | export const DateTime = createComponent( 9 | HTMLDateTimeCustomElement, 10 | dateTimeTag, 11 | ); 12 | 13 | export type DateTimeProps = ComponentProps; 14 | 15 | export { HTMLDateTimeCustomElement }; 16 | 17 | export default DateTime; 18 | -------------------------------------------------------------------------------- /packages/date/src/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './DateTime'; 2 | export * from './DateRange'; 3 | -------------------------------------------------------------------------------- /packages/date/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/DateRange.sx"], 8 | "exclude": ["node_modules"], 9 | "references": [ 10 | { 11 | "path": "../utils" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/errorboundary/README.md: -------------------------------------------------------------------------------- 1 | Display a Error message if error is thrown in children 2 | 3 | > this component uses the `react-error-boundary` with a predefined `Fallback` 4 | 5 | atm this component is under `@equinor/fusion-react-errorboundary/legacy` since there is a WIP to create a generic component in the future -------------------------------------------------------------------------------- /packages/errorboundary/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-errorboundary", 3 | "version": "1.0.6", 4 | "description": "React component for error boundary", 5 | "keywords": ["error-boundary", "react", "fusion", "equinor"], 6 | "homepage": "https://github.com/equinor/fusion-react-components/tree/master/packages/errorboundary#readme", 7 | "license": "ISC", 8 | "main": "dist/index.js", 9 | "exports": { 10 | ".": "./dist/index.js", 11 | "./legacy": "./dist/legacy/index.js", 12 | "./package.json": "./package.json", 13 | "./README.md": "./README.md", 14 | "./CHANGELOG.md": "./CHANGELOG.md" 15 | }, 16 | "types": "dist/index.d.ts", 17 | "typesVersions": { 18 | "*": { 19 | "legacy": ["dist/legacy/index.d.ts"] 20 | } 21 | }, 22 | "directories": { 23 | "dist": "dist" 24 | }, 25 | "files": ["dist"], 26 | "publishConfig": { 27 | "access": "public" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/equinor/fusion-react-components.git" 32 | }, 33 | "scripts": { 34 | "build": "tsc -b", 35 | "lint": "eslint --ext .ts,.tsx,.js src/", 36 | "lint:fix": "eslint --ext .ts,.tsx,.js src/ --fix", 37 | "prepack": "npm run build" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/equinor/fusion-react-components/issues" 41 | }, 42 | "dependencies": { 43 | "@equinor/fusion-react-styles": "^0.6.0", 44 | "@equinor/fusion-react-utils": "^2.1.0", 45 | "react-error-boundary": "^6.0.0" 46 | }, 47 | "devDependencies": { 48 | "@equinor/eds-icons": "~0.22.0", 49 | "@equinor/eds-tokens": "^0.9.2", 50 | "@types/react": "^18.2.24", 51 | "react": "^18.2.0", 52 | "styled-components": "^6.1.1" 53 | }, 54 | "peerDependencies": { 55 | "@equinor/eds-core-react": "^0.45.0", 56 | "@equinor/eds-icons": "~0.22.0", 57 | "styled-components": "^6.1.1" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/errorboundary/src/index.tsx: -------------------------------------------------------------------------------- 1 | export * from 'react-error-boundary'; 2 | -------------------------------------------------------------------------------- /packages/errorboundary/src/legacy/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ErrorBoundary as ReactErrorBoundary, 3 | type ErrorBoundaryProps as ReactReactErrorBoundary, 4 | } from 'react-error-boundary'; 5 | import { fallbackRender } from './fallbackRender'; 6 | 7 | export type ErrorBoundaryProps = Partial; 8 | 9 | const reload = () => window.location.reload(); 10 | 11 | export const ErrorBoundary = (props: ErrorBoundaryProps): JSX.Element => { 12 | const args = { ...props }; 13 | if (!args.fallbackRender && !args.FallbackComponent && !args.fallback) { 14 | args.fallbackRender = fallbackRender; 15 | } 16 | if (!args.onReset) { 17 | args.onReset = reload; 18 | } 19 | return ; 20 | }; 21 | 22 | export default ErrorBoundary; 23 | -------------------------------------------------------------------------------- /packages/errorboundary/src/legacy/FallbackIcon.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | 3 | import { Icon } from '@equinor/eds-core-react'; 4 | import { tokens } from '@equinor/eds-tokens'; 5 | import { block, warning_outlined, sync_off, type IconName } from '@equinor/eds-icons'; 6 | 7 | import type { ErrorType } from './types'; 8 | import { styled } from 'styled-components'; 9 | 10 | Icon.add({ block, warning_outlined, sync_off }); 11 | 12 | const Styled = { 13 | icon: styled(Icon)` 14 | height: 5rem; 15 | width: 5rem; 16 | color: ${tokens.colors.infographic.primary__energy_red_55.rgba}; 17 | `, 18 | }; 19 | 20 | export const FallbackIcon = (props: { readonly errorType?: ErrorType }): JSX.Element => { 21 | const name = useMemo((): IconName => { 22 | switch (props.errorType) { 23 | case 'accessDenied': 24 | return 'block'; 25 | case 'noData': 26 | case 'failedDependency': 27 | case 'throttle': 28 | return 'sync_off'; 29 | default: 30 | return 'warning_outlined'; 31 | } 32 | }, [props.errorType]); 33 | return ; 34 | }; 35 | 36 | export default FallbackIcon; 37 | -------------------------------------------------------------------------------- /packages/errorboundary/src/legacy/fallbackRender.tsx: -------------------------------------------------------------------------------- 1 | import type { ErrorBoundaryProps } from 'react-error-boundary'; 2 | 3 | import { Fallback, type FallbackProps } from './Fallback'; 4 | 5 | export const fallbackRender: ErrorBoundaryProps['fallbackRender'] = (props) => ( 6 | 7 | ); 8 | 9 | export const createFallbackRender = 10 | (args?: Omit): ErrorBoundaryProps['fallbackRender'] => 11 | /* eslint-disable-next-line react/display-name, react/function-component-definition, react/prop-types*/ 12 | (props) => ; 13 | 14 | export default fallbackRender; 15 | -------------------------------------------------------------------------------- /packages/errorboundary/src/legacy/index.ts: -------------------------------------------------------------------------------- 1 | export { ErrorBoundary, type ErrorBoundaryProps } from './ErrorBoundary'; 2 | export { Fallback, type FallbackProps } from './Fallback'; 3 | export { createFallbackRender } from './fallbackRender'; 4 | -------------------------------------------------------------------------------- /packages/errorboundary/src/legacy/types.ts: -------------------------------------------------------------------------------- 1 | export type ErrorType = 2 | | 'error' 3 | | 'accessDenied' 4 | | 'notFound' 5 | | 'noData' 6 | | 'failedDependency' 7 | | 'throttle'; 8 | -------------------------------------------------------------------------------- /packages/errorboundary/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts", "src/**/*.tsx"], 8 | "exclude": ["node_modules"], 9 | "references": [{ "path": "../utils" }, { "path": "../styles" }] 10 | } 11 | -------------------------------------------------------------------------------- /packages/filter/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc"], 3 | "rules": { 4 | "@typescript-eslint/ban-ts-comment": "off", 5 | "@typescript-eslint/no-explicit-any": "off", 6 | "react/prop-types": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/filter/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## `@equinor/fusion-react-filter` [![Published on npm](https://img.shields.io/npm/v/@equinor/fusion-react-filter.svg)](https://www.npmjs.com/package/@equinor/fusion-react-filter) 3 | 4 | Adds filter functionality through the FilterProvider. 5 | Also comes with various predefined filters and panels for building out a filter. 6 | 7 | Consult the [Storybook](https://equinor.github.io/fusion-react-components/?path=/docs/data-filter) for more detailed examples and documentation. 8 | 9 | ### Installation 10 | 11 | ```sh 12 | npm install @equinor/fusion-react-filter 13 | ``` 14 | 15 | ## Example Usage 16 | 17 | ```html 18 | 19 | 20 | 21 | 22 | ``` 23 | 24 | ### Properties/Attributes 25 | 26 | Name | Type | Description 27 | -------------------- | --------- | ------- 28 | `initialData` | `Generic*` | Dataset for filtering. *Recommended type is TData[] or Record 29 | `initialFilters` | `Record` | Filter selection added at initiation 30 | 31 | 32 | -------------------------------------------------------------------------------- /packages/filter/TODO.md: -------------------------------------------------------------------------------- 1 | ### FilterProvider 2 | 3 | - [ ] clear all filter selection 4 | - [ ] remove pre-selection if options are not in dataset 5 | 6 | ## Future 7 | 8 | - [ ] searchable select filter 9 | - [ ] range slider filter -------------------------------------------------------------------------------- /packages/filter/components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-filter/components", 3 | "private": true, 4 | "types": "../dist/components/index.d.ts", 5 | "main": "../dist/components/index.js", 6 | "module": "../dist/components/index.js", 7 | "sideEffects": false 8 | } 9 | -------------------------------------------------------------------------------- /packages/filter/options/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-filter-options", 3 | "private": true, 4 | "types": "../dist/options/index.d.ts", 5 | "main": "../dist/options/index.js", 6 | "module": "../dist/options/index.js", 7 | "sideEffects": false 8 | } 9 | -------------------------------------------------------------------------------- /packages/filter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-filter", 3 | "version": "1.8.6", 4 | "description": "component for Filtering data", 5 | "author": "", 6 | "homepage": "https://github.com/equinor/fusion-react-components/tree/master/packages/filter#readme", 7 | "keywords": ["Filter", "Filter panel", "react", "fusion", "equinor"], 8 | "license": "ISC", 9 | "private": false, 10 | "publishConfig": { 11 | "access": "public" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/equinor/fusion-react-components.git" 16 | }, 17 | "main": "dist/index.js", 18 | "module": "dist/index.js", 19 | "directories": { 20 | "dist": "dist" 21 | }, 22 | "files": ["dist", "components", "options"], 23 | "scripts": { 24 | "build": "tsc -b", 25 | "watch": "tsc -w", 26 | "lint": "eslint src/**/*.{js,ts,tsx}", 27 | "lint:fix": "eslint src/**/*.{js,ts,tsx} --fix", 28 | "prepack": "npm run build" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/equinor/fusion-react-components/issues" 32 | }, 33 | "dependencies": { 34 | "@equinor/fusion-react-styles": "^0.6.0", 35 | "@equinor/fusion-react-utils": "^2.1.0", 36 | "@equinor/fusion-wc-chip": "^1.2.2", 37 | "rxjs": "^7.8.1", 38 | "typesafe-actions": "^5.1.0" 39 | }, 40 | "devDependencies": { 41 | "@equinor/eds-core-react": "^0.46.0", 42 | "@equinor/eds-icons": "~0.22.0", 43 | "@equinor/fusion-observable": "^8.1.2", 44 | "@types/react": "^18.2.24", 45 | "react": "^18.2.0" 46 | }, 47 | "peerDependencies": { 48 | "@equinor/eds-core-react": "^0.45.0", 49 | "@equinor/eds-icons": "~0.22.0", 50 | "@equinor/fusion-observable": "^8.1.2" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/filter/src/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction, type ActionType } from 'typesafe-actions'; 2 | import type { Filter } from './types'; 3 | 4 | export type SelectionValues = Record; 5 | 6 | export const filter = { 7 | add: createAction('@FILTER/FILTERS_ADD')>(), 8 | remove: createAction('@FILTER/FILTERS_REMOVE')(), 9 | }; 10 | 11 | export const selection = { 12 | set: createAction('@FILTER/SELECTION_SET')(), 13 | initial: createAction('@FILTER/SELECTION_INITIAL')(), 14 | clear: createAction('@FILTER/SELECTION_CLEAR')(), 15 | remove: createAction('@FILTER/SELECTION_REMOVE')(), 16 | }; 17 | 18 | export const source = { 19 | update: createAction('@FILTER/SOURCE_UPDATE')(), 20 | }; 21 | 22 | export const actions = { 23 | filter, 24 | selection, 25 | source, 26 | }; 27 | 28 | export type Actions = ActionType; 29 | 30 | export default actions; 31 | -------------------------------------------------------------------------------- /packages/filter/src/components/filter/checkbox/CheckboxFilter.style.ts: -------------------------------------------------------------------------------- 1 | import { createStyles, makeStyles } from '@equinor/fusion-react-styles'; 2 | 3 | export const useStyles = makeStyles( 4 | (theme) => 5 | createStyles({ 6 | root: { 7 | display: 'flex', 8 | flexFlow: 'column', 9 | // TODO - remove 10 | minWidth: 150, 11 | }, 12 | items: (props: { layout: 'column' | 'row' }) => ({ 13 | display: 'flex', 14 | flexFlow: props.layout, 15 | overflowY: 'auto', 16 | }), 17 | 18 | title: { 19 | ...theme.typography.heading.h3.style, 20 | }, 21 | }), 22 | { name: 'fusion-filter-checkbox-option' }, 23 | ); 24 | 25 | export default useStyles; 26 | -------------------------------------------------------------------------------- /packages/filter/src/components/filter/checkbox/CheckboxFilterOption.style.ts: -------------------------------------------------------------------------------- 1 | import { createStyles, makeStyles } from '@equinor/fusion-react-styles'; 2 | 3 | export const useStyles = makeStyles( 4 | (theme) => 5 | createStyles({ 6 | root: { 7 | display: 'flex', 8 | alignItems: 'center', 9 | justifyContent: 'space-between', 10 | cursor: 'pointer', 11 | backgroundColor: 'transparent', 12 | willChange: 'backgroundColor', 13 | // TODO - fetch from theme 14 | transition: '125ms background-color ease-out', 15 | '&:hover': { 16 | backgroundColor: theme.colors.interactive.primary__hover_alt.getVariable('color'), 17 | }, 18 | // maxWidth: '80px', 19 | }, 20 | inactive: { 21 | filter: 'grayscale(100%)', 22 | opacity: 0.5, 23 | }, 24 | counter: { 25 | ...theme.typography.input.label.style, 26 | whiteSpace: 'nowrap', 27 | margin: `0 ${theme.spacing.comfortable.small.getVariable('padding')}`, 28 | }, 29 | hide: { 30 | display: 'none', 31 | }, 32 | }), 33 | { name: 'fusion-filter-checkbox-option' }, 34 | ); 35 | 36 | export default useStyles; 37 | -------------------------------------------------------------------------------- /packages/filter/src/components/filter/checkbox/CheckboxFilterOption.tsx: -------------------------------------------------------------------------------- 1 | import type React from 'react'; 2 | import { useCallback } from 'react'; 3 | 4 | import { Checkbox } from '@equinor/eds-core-react'; 5 | 6 | import { clsx } from '@equinor/fusion-react-styles'; 7 | import { useStyles } from './CheckboxFilterOption.style'; 8 | import type { CheckboxOption } from './types'; 9 | 10 | export type CheckboxFilterOptionProps = JSX.IntrinsicElements['div'] & 11 | Omit & { 12 | readonly name: string; 13 | readonly checked?: boolean; 14 | readonly indeterminate?: boolean; 15 | readonly count: string | number; 16 | readonly onOptionChange: (item: { name: string; selected?: boolean }) => void; 17 | }; 18 | 19 | export const CheckboxFilterOption = (props: CheckboxFilterOptionProps): JSX.Element => { 20 | const { 21 | name, 22 | label, 23 | count, 24 | checked, 25 | indeterminate, 26 | inactive, 27 | hide, 28 | onOptionChange, 29 | className, 30 | ...args 31 | } = props; 32 | const onCheckboxInput = useCallback( 33 | ({ currentTarget: { name, checked } }: React.FormEvent) => { 34 | return onOptionChange({ name, selected: checked }); 35 | }, 36 | [onOptionChange], 37 | ); 38 | const styles = useStyles(); 39 | return ( 40 | // TODO 41 | // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions 42 |
46 | 53 | {count} 54 |
55 | ); 56 | }; 57 | 58 | export default CheckboxFilterOption; 59 | -------------------------------------------------------------------------------- /packages/filter/src/components/filter/checkbox/CheckboxFilterOptionAll.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | 3 | import { 4 | useObservableRef, 5 | useObservableState, 6 | useObservableSelector, 7 | } from '@equinor/fusion-observable/react'; 8 | 9 | import { useCheckboxFilterOptionContext } from './context'; 10 | import CheckboxFilterOption from './CheckboxFilterOption'; 11 | 12 | export const CheckboxFilterOptionAll = (): JSX.Element => { 13 | const { options$, selection$, setSelection } = useCheckboxFilterOptionContext(); 14 | const { value: options } = useObservableState(options$) || {}; 15 | const optionKeysRef = useObservableRef(useObservableSelector(options$, (x) => Object.keys(x))); 16 | 17 | const { value: selectedKeys } = useObservableState(selection$) || new Set([]); 18 | 19 | const { totalCount } = Object.values(options || {}).reduce( 20 | (acc, value) => { 21 | acc.count += value.count; 22 | acc.totalCount += value.totalCount; 23 | return acc; 24 | }, 25 | { count: 0, totalCount: 0 }, 26 | ); 27 | 28 | const checked = selectedKeys?.size === optionKeysRef.current?.length; 29 | const indeterminate = Boolean(selectedKeys?.size && !checked); 30 | const onOptionChange = useCallback( 31 | ({ selected }: { name: string; selected?: boolean }) => { 32 | setSelection(selected ? new Set(optionKeysRef.current) : new Set([])); 33 | }, 34 | [setSelection, optionKeysRef], 35 | ); 36 | 37 | return ( 38 | 46 | ); 47 | }; 48 | 49 | export default CheckboxFilterOptionAll; 50 | -------------------------------------------------------------------------------- /packages/filter/src/components/filter/checkbox/CheckboxFilterOptions.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | 3 | import { useObservableRef, useObservableState } from '@equinor/fusion-observable/react'; 4 | 5 | import { useCheckboxFilterOptionContext } from './context'; 6 | 7 | import { CheckboxFilterOption, type CheckboxFilterOptionProps } from './CheckboxFilterOption'; 8 | 9 | const defaultSortFn = (a: T, b: T) => a.label.localeCompare(b.label); 10 | 11 | type CheckboxFilterOptionsProps = { 12 | readonly sortFn?: (a: T, b: T) => number; 13 | }; 14 | 15 | export const CheckboxFilterOptions = ({ sortFn }: CheckboxFilterOptionsProps): JSX.Element => { 16 | const context = useCheckboxFilterOptionContext(); 17 | const { value: data } = useObservableState(context.options$); 18 | const selectionRef = useObservableRef(context.selection$); 19 | 20 | const onOptionChange = useCallback( 21 | (item: { name: string; selected?: boolean }) => { 22 | const selection = new Set(selectionRef.current || []); 23 | const { name, selected } = item; 24 | if (selected) { 25 | selection.add(name); 26 | } else { 27 | selection.delete(name); 28 | } 29 | context.setSelection(selection.size ? selection : undefined); 30 | }, 31 | [context, selectionRef], 32 | ); 33 | 34 | const itemProps = Object.entries(data || {}) 35 | .map( 36 | ([key, value]) => 37 | ({ 38 | onOptionChange, 39 | name: key, 40 | label: value.label, 41 | checked: !!value.selected, 42 | count: 43 | value.count === value.totalCount ? value.count : `${value.count} / ${value.totalCount}`, 44 | inactive: !value.count, 45 | hide: value.hide, 46 | }) as CheckboxFilterOptionProps, 47 | ) 48 | .sort(sortFn ?? defaultSortFn); 49 | return ( 50 | <> 51 | {itemProps.map((props) => ( 52 | 53 | ))} 54 | 55 | ); 56 | }; 57 | 58 | export default CheckboxFilterOptions; 59 | -------------------------------------------------------------------------------- /packages/filter/src/components/filter/checkbox/context.ts: -------------------------------------------------------------------------------- 1 | import { useFilterOptionContext, type FilterOptionContext } from '../../../options'; 2 | import type { CheckboxOption } from './types'; 3 | 4 | export const useCheckboxFilterOptionContext = < 5 | TOption extends CheckboxOption = CheckboxOption, 6 | TValue = string, 7 | >(): FilterOptionContext => useFilterOptionContext(); 8 | -------------------------------------------------------------------------------- /packages/filter/src/components/filter/checkbox/index.ts: -------------------------------------------------------------------------------- 1 | export { CheckboxFilter, type CheckboxFilterProps, default } from './CheckboxFilter'; 2 | -------------------------------------------------------------------------------- /packages/filter/src/components/filter/checkbox/types.ts: -------------------------------------------------------------------------------- 1 | import type { FilterOption } from '../../../options'; 2 | 3 | export type CheckboxOption = FilterOption & { 4 | label: string; 5 | count: number; 6 | totalCount: number; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/filter/src/components/filter/index.ts: -------------------------------------------------------------------------------- 1 | export { CheckboxFilter } from './checkbox'; 2 | export { SearchFilter } from './search'; 3 | 4 | export { FilterOptionHeader } from './FilterOptionHeader'; 5 | 6 | export * from './types'; 7 | -------------------------------------------------------------------------------- /packages/filter/src/components/filter/search/index.ts: -------------------------------------------------------------------------------- 1 | export { SearchFilter } from './SearchFilter'; 2 | -------------------------------------------------------------------------------- /packages/filter/src/components/filter/types.ts: -------------------------------------------------------------------------------- 1 | export type FilterComponent = { 2 | /** unique key for filter */ 3 | filterKey: string; 4 | /** title for filter */ 5 | title: string; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/filter/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { FilterComponent, CheckboxFilter, FilterOptionHeader, SearchFilter } from './filter'; 2 | export { ClearFilterButton, FilterContainer, SelectionChips } from './misc'; 3 | export { FilterPanel } from './panel'; 4 | -------------------------------------------------------------------------------- /packages/filter/src/components/misc/ClearFilterButton.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | 3 | import { useForwardRef } from '@equinor/fusion-react-utils'; 4 | import { useObservableSubscription } from '@equinor/fusion-observable/react'; 5 | import { Button, type ButtonProps } from '@equinor/eds-core-react'; 6 | 7 | import { useClearFilter } from '../../hooks/useClearFilter'; 8 | 9 | export type ClearFilterButtonProps = React.PropsWithChildren< 10 | Omit & { ref?: React.ForwardedRef } 11 | >; 12 | 13 | /** 14 | * Component for resetting filter values. 15 | * uses the `useClearFilter` hook 16 | */ 17 | export const ClearFilterButton = (props: ClearFilterButtonProps): JSX.Element => { 18 | const { children, ...args } = props; 19 | const { clear, changed$ } = useClearFilter(); 20 | const ref = useForwardRef(props.ref); 21 | 22 | /** Subscribe to changes and toggle `disable` of button */ 23 | useObservableSubscription( 24 | changed$, 25 | useCallback( 26 | (changed: any) => { 27 | ref.current && (ref.current.disabled = !Object.keys(changed).length); 28 | }, 29 | [ref], 30 | ), 31 | ); 32 | 33 | /** Reset filters on click */ 34 | const onClick = useCallback(() => clear(), [clear]); 35 | 36 | return ( 37 | 40 | ); 41 | }; 42 | 43 | export default ClearFilterButton; 44 | -------------------------------------------------------------------------------- /packages/filter/src/components/misc/FilterContainer.tsx: -------------------------------------------------------------------------------- 1 | import { clsx, createStyles, makeStyles, type theme } from '@equinor/fusion-react-styles'; 2 | import type React from 'react'; 3 | 4 | const useStyles = makeStyles( 5 | (theme) => 6 | createStyles({ 7 | root: ({ spacing }: StyleProps) => ({ 8 | display: 'inline-flex', 9 | flex: 'auto', 10 | gap: theme.spacing.comfortable[spacing || 'medium'].getVariable('padding'), 11 | '&>*': { 12 | flex: 1, 13 | }, 14 | }), 15 | }), 16 | { name: 'fusion-filter-container' }, 17 | ); 18 | 19 | type StyleProps = { 20 | readonly spacing?: keyof typeof theme.spacing.comfortable; 21 | }; 22 | 23 | export type FilterContainerProps = JSX.IntrinsicElements['div'] & StyleProps; 24 | 25 | /** 26 | * Components for displaying filters 27 | */ 28 | export const FilterContainer = ( 29 | props: React.PropsWithChildren, 30 | ): JSX.Element => { 31 | const { children, className, spacing, ...args } = props; 32 | const styles = useStyles({ spacing }); 33 | return ( 34 |
35 | {children} 36 |
37 | ); 38 | }; 39 | 40 | export default FilterContainer; 41 | -------------------------------------------------------------------------------- /packages/filter/src/components/misc/index.ts: -------------------------------------------------------------------------------- 1 | export { ClearFilterButton } from './ClearFilterButton'; 2 | export { FilterContainer } from './FilterContainer'; 3 | export { SelectionChips } from './SelectionChips'; 4 | -------------------------------------------------------------------------------- /packages/filter/src/components/panel/FilterPanelFilters.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { useObservableState } from '@equinor/fusion-observable/react'; 3 | import { useFilterPanelContext } from './FilterPanelProvider'; 4 | 5 | import { EdsProvider } from '@equinor/eds-core-react'; 6 | 7 | import { map } from 'rxjs/operators'; 8 | import { FilterContainer } from '../misc'; 9 | 10 | type FilterPanelFiltersProps = JSX.IntrinsicElements['div'] & { 11 | readonly FilterSelector?: React.FC; 12 | }; 13 | 14 | export const FilterPanelFilters = (props: FilterPanelFiltersProps): JSX.Element => { 15 | const { FilterSelector, ...args } = props; 16 | const { filters$, showFilters } = useFilterPanelContext(); 17 | 18 | const { value: filters } = useObservableState( 19 | useMemo( 20 | () => 21 | filters$.pipe( 22 | map(({ filters, selectedFilters }) => 23 | filters.filter((filter) => selectedFilters.has(filter.props.filterKey)), 24 | ), 25 | ), 26 | [filters$], 27 | ), 28 | ); 29 | return ( 30 | 31 |
32 | {FilterSelector && } 33 | {filters} 34 |
35 |
36 | ); 37 | }; 38 | 39 | export default FilterPanelFilters; 40 | -------------------------------------------------------------------------------- /packages/filter/src/components/panel/FilterPanelSelector.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | 3 | import { Checkbox } from '@equinor/eds-core-react'; 4 | 5 | import { initialState, useFilterPanelContext, actions } from './FilterPanelProvider'; 6 | import { useObservableState } from '@equinor/fusion-observable/react'; 7 | 8 | export type FilterPanelSelectorProps = { 9 | title?: string; 10 | }; 11 | 12 | export const FilterPanelSelector: React.FC = () => { 13 | const { filters$ } = useFilterPanelContext(); 14 | 15 | const { filters, selectedFilters } = useObservableState(filters$).value || initialState; 16 | 17 | const onInput = useCallback( 18 | (e: React.FormEvent) => { 19 | const key = e.currentTarget.value; 20 | const selected = e.currentTarget.checked; 21 | filters$.next(selected ? actions.remove(key) : actions.add(key)); 22 | }, 23 | [filters$], 24 | ); 25 | 26 | return ( 27 |
28 | {filters.map(({ props: { filterKey, title } }) => ( 29 | 36 | ))} 37 |
38 | ); 39 | }; 40 | 41 | export default FilterPanelSelector; 42 | -------------------------------------------------------------------------------- /packages/filter/src/components/panel/index.ts: -------------------------------------------------------------------------------- 1 | export { FilterPanelBar } from './FilterPanelBar'; 2 | export { FilterPanelProvider } from './FilterPanelProvider'; 3 | export { FilterPanelFilters } from './FilterPanelFilters'; 4 | export { FilterPanel, FilterPanelProps } from './FilterPanel'; 5 | -------------------------------------------------------------------------------- /packages/filter/src/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react'; 2 | import type { FilterContext } from './types'; 3 | 4 | const context = createContext({} as FilterContext); 5 | 6 | export const useFilterContext = < 7 | TSelection = any, 8 | TData extends Record = Record, 9 | >(): FilterContext => useContext(context) as FilterContext; 10 | 11 | export const { Provider, Consumer } = context as { Provider: any; Consumer: any }; 12 | 13 | export default useFilterContext; 14 | -------------------------------------------------------------------------------- /packages/filter/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { useClearFilter } from './useClearFilter'; 2 | export { useFilter } from './useFilter'; 3 | export { useFilterSelection } from './useFilterSelection'; 4 | export { useFilterData } from './useFilterData'; 5 | 6 | /** re-exports */ 7 | export { useFilterContext } from '../context'; 8 | -------------------------------------------------------------------------------- /packages/filter/src/hooks/useFilter.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useLayoutEffect } from 'react'; 2 | 3 | import { actions } from '../actions'; 4 | import { useFilterContext } from '../context'; 5 | 6 | import type { Filter } from '../types'; 7 | 8 | /** 9 | * Hook for attaching a filter to the current context 10 | * Must be used within {@link FilterProvider} 11 | */ 12 | export const useFilter = ( 13 | filter: Filter, 14 | ): ((value: TSelection) => void) => { 15 | const context = useFilterContext(); 16 | const { key } = filter; 17 | useLayoutEffect(() => { 18 | /** add filter to store */ 19 | context.filter$.next(actions.filter.add(filter)); 20 | 21 | /** add initial selection to filter selection */ 22 | if (!Object.keys(context.selection$.value).includes(key)) { 23 | context.selection$.next(actions.selection.initial({ [key]: filter.initial })); 24 | } 25 | /** remove filter when consumer unmounts */ 26 | return () => context.filter$.next(actions.filter.remove(filter.key)); 27 | }, [context, filter, key]); 28 | 29 | /** return a function which the consumer can set the selection for the provided filter */ 30 | return useCallback( 31 | (selection: TSelection) => { 32 | context.selection$.next(actions.selection.set({ [key]: selection })); 33 | }, 34 | [context, key], 35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /packages/filter/src/hooks/useFilterSelection.ts: -------------------------------------------------------------------------------- 1 | import { useObservableSelector } from '@equinor/fusion-observable/react'; 2 | import type { Observable } from '@equinor/fusion-observable'; 3 | 4 | import { useFilterContext } from '../context'; 5 | 6 | /** 7 | * Use the filter selection from filter provider context 8 | * @param filterKey {string} the key that the filter was registered on 9 | * @returns {Observable} 10 | */ 11 | export const useFilterSelection = (filterKey: string): Observable> => 12 | useObservableSelector( 13 | useFilterContext().selection$ as Observable>, 14 | filterKey, 15 | ); 16 | 17 | export default useFilterSelection; 18 | -------------------------------------------------------------------------------- /packages/filter/src/index.ts: -------------------------------------------------------------------------------- 1 | export { actions as filterActions, Actions as FilterActions } from './actions'; 2 | 3 | export * from './hooks'; 4 | 5 | export * from './types'; 6 | 7 | /** Common Components */ 8 | 9 | export { FilterProvider, FilterProviderProps } from './FilterProvider'; 10 | export { CheckboxFilter, FilterPanel } from './components'; 11 | -------------------------------------------------------------------------------- /packages/filter/src/options/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction, type ActionType } from 'typesafe-actions'; 2 | import type { FilterOption } from './types'; 3 | 4 | export const actions = { 5 | set: createAction('@FILTER_OPTIONS/SELECTION_SET')>(), 6 | }; 7 | 8 | export type Actions = ActionType; 9 | 10 | export default actions; 11 | -------------------------------------------------------------------------------- /packages/filter/src/options/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react'; 2 | 3 | import type { FilterOption, FilterOptionContext } from './types'; 4 | 5 | // @ts-ignore 6 | const context = createContext({}); 7 | 8 | export const { Provider, Consumer } = context as { Provider: any; Consumer: any }; 9 | 10 | export const useFilterOptionContext = < 11 | TOption extends FilterOption = FilterOption, 12 | TValue = string, 13 | >(): FilterOptionContext => 14 | useContext(context) as FilterOptionContext; 15 | 16 | export default useFilterOptionContext; 17 | -------------------------------------------------------------------------------- /packages/filter/src/options/create-options.ts: -------------------------------------------------------------------------------- 1 | import type { FilterOption, FilterOptionBuilder, FilterOptionSelector } from './types'; 2 | 3 | export const propertySelector = >( 4 | key: Extract, 5 | ): FilterOptionSelector => { 6 | return (data: TData) => ({ key: data[key], value: data[key] }); 7 | }; 8 | 9 | export const createOptionBuilder = 10 | < 11 | TData extends Record, 12 | TOption extends FilterOption, 13 | TOptions extends Record = Record, 14 | TValue = keyof TOptions | string, 15 | >( 16 | selector: FilterOptionSelector, 17 | isSelected: (key: keyof TOptions | string, selection: Set, data?: TData) => boolean = ( 18 | key: keyof TOptions, 19 | selection: Set, 20 | ) => (selection as unknown as Set).has(key), 21 | ): FilterOptionBuilder => 22 | (source: TData[], selection?: Set, data?: TData[]): TOptions => { 23 | const hasEntry = (value: unknown) => data && !!data.find((x) => selector(x).value === value); 24 | const srcCount = (value: unknown) => source.filter((x) => selector(x).value === value).length; 25 | const dataCount = (value: unknown) => 26 | (data || []).filter((x) => selector(x).value === value).length; 27 | const options = source.reduce((acc, item) => { 28 | const { key, value, label } = selector(item); 29 | if (acc[key as keyof typeof acc]) { 30 | return acc; 31 | } 32 | const option = { 33 | label: label || value, 34 | excluded: !hasEntry(value), 35 | selected: selection && isSelected(key, selection, item), 36 | count: dataCount(value), 37 | totalCount: srcCount(value), 38 | }; 39 | // biome-ignore lint/suspicious/noExplicitAny: 40 | (acc as any)[key] = option; 41 | return acc; 42 | }, {} as TOptions); 43 | 44 | return options; 45 | }; 46 | -------------------------------------------------------------------------------- /packages/filter/src/options/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | FilterOptionProvider, 3 | type FilterComponentProviderProps, 4 | default, 5 | } from './FilterOptionProvider'; 6 | export { useFilterOptionSearch } from './search'; 7 | 8 | export * from './types'; 9 | 10 | export { Consumer as FilterOptionConsumer, useFilterOptionContext } from './context'; 11 | 12 | export { propertySelector } from './create-options'; 13 | -------------------------------------------------------------------------------- /packages/filter/src/options/reducer.ts: -------------------------------------------------------------------------------- 1 | import { createReducer } from 'typesafe-actions'; 2 | import { type Actions, actions } from './actions'; 3 | import type { FilterOption } from './types'; 4 | 5 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 6 | export const createOptionReducer = (initial: Record) => 7 | createReducer, Actions>(initial).handleAction( 8 | actions.set, 9 | (_, { payload }) => payload, 10 | ); 11 | -------------------------------------------------------------------------------- /packages/filter/src/options/search/filter-option-match.ts: -------------------------------------------------------------------------------- 1 | import type { FilterOption } from '../types'; 2 | 3 | export const filterOptionMatch = (query: string) => { 4 | const pattern = new RegExp(query, 'i'); 5 | return (key: string, value: TOption): { hide: boolean; changed: boolean } => { 6 | const hide = !(value.label || key).match(pattern); 7 | const changed = hide !== value.hide; 8 | return { hide, changed }; 9 | }; 10 | }; 11 | 12 | export default filterOptionMatch; 13 | -------------------------------------------------------------------------------- /packages/filter/src/options/search/filter-option-search.ts: -------------------------------------------------------------------------------- 1 | import type { FilterOption } from '../types'; 2 | import type { FilterOptionMatchFn } from './types'; 3 | 4 | export const filterOptionSearch = 5 | (matcher: FilterOptionMatchFn) => 6 | (options: Record, query: string): Record => { 7 | const match = matcher(query); 8 | let hasChanged = false; 9 | const res = Object.entries(options).reduce((acc, [key, value]) => { 10 | const { hide, changed } = match(key, value as TOption); 11 | !hasChanged && changed && (hasChanged = true); 12 | return Object.assign(acc, { [key]: { ...value, hide } }); 13 | }, {}); 14 | 15 | return (hasChanged ? res : options) as Record; 16 | }; 17 | 18 | export default filterOptionSearch; 19 | -------------------------------------------------------------------------------- /packages/filter/src/options/search/index.ts: -------------------------------------------------------------------------------- 1 | export { useFilterOptionSearch, default } from './useFilterOptionSearch'; 2 | -------------------------------------------------------------------------------- /packages/filter/src/options/search/types.ts: -------------------------------------------------------------------------------- 1 | import type { FilterOption } from '../types'; 2 | 3 | export type FilterOptionSearchFn> = ( 4 | options: TOptions, 5 | query: string, 6 | ) => Record; 7 | 8 | export type FilterOptionMatchFn< 9 | TOption extends FilterOption, 10 | TResult extends { changed: boolean } = { hide: boolean; changed: boolean }, 11 | > = (query: string) => (key: string, value: TOption) => TResult; 12 | -------------------------------------------------------------------------------- /packages/filter/src/options/search/useFilterOptionSearch.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useMemo, useState } from 'react'; 2 | import { combineLatest, of, Subject } from 'rxjs'; 3 | import type { Observable } from 'rxjs'; 4 | import { switchMap, map } from 'rxjs/operators'; 5 | import { actions } from '../actions'; 6 | 7 | import { useFilterOptionContext } from '../context'; 8 | import type { FilterOption } from '../types'; 9 | 10 | import filterOptionMatchFn from './filter-option-match'; 11 | import filterOptionSearchFn from './filter-option-search'; 12 | import type { FilterOptionMatchFn, FilterOptionSearchFn } from './types'; 13 | 14 | export const useFilterOptionSearch = ( 15 | searchFn?: FilterOptionSearchFn>, 16 | matchFn?: FilterOptionMatchFn, 17 | ): { 18 | setQuery: (query: string) => void; 19 | query$: Observable; 20 | } => { 21 | const { options$ } = useFilterOptionContext>(); 22 | const [query$] = useState>(new Subject()); 23 | const setQuery = useCallback( 24 | (q: string) => { 25 | query$.next(q); 26 | }, 27 | [query$], 28 | ); 29 | 30 | const search = useMemo(() => { 31 | return searchFn || filterOptionSearchFn(matchFn || filterOptionMatchFn); 32 | }, [matchFn, searchFn]); 33 | 34 | useEffect(() => { 35 | const sub = combineLatest([options$, query$]) 36 | .pipe( 37 | switchMap(([options, query]) => { 38 | return of(search(options, query)); 39 | }), 40 | map((x) => actions.set(x)), 41 | ) 42 | .subscribe(options$); 43 | return () => sub.unsubscribe(); 44 | }, [options$, query$, search]); 45 | 46 | return { setQuery, query$ }; 47 | }; 48 | 49 | export default useFilterOptionSearch; 50 | -------------------------------------------------------------------------------- /packages/filter/src/options/types.ts: -------------------------------------------------------------------------------- 1 | import type { Observable, FlowSubject } from '@equinor/fusion-observable'; 2 | import type { Actions } from './actions'; 3 | 4 | export type FilterOption = { 5 | label?: string; 6 | count?: number; 7 | totalCount?: number; 8 | selected?: boolean; 9 | disabled?: boolean; 10 | inactive?: boolean; 11 | excluded?: boolean; 12 | hide?: boolean; 13 | }; 14 | 15 | export type FilterOptionContext = { 16 | options$: FlowSubject, Actions>; 17 | selection$: Observable>; 18 | setSelection: (selection?: Set) => void; 19 | }; 20 | 21 | export type FilterOptionSelector = (data: TData) => { 22 | key: string; 23 | label?: string; 24 | value: string; 25 | }; 26 | 27 | export type FilterOptionBuilder = ( 28 | source: TData[], 29 | selection: Set, 30 | data: TData[], 31 | ) => Record; 32 | 33 | export type FilterOptionType = T extends (a: infer TData, b: infer TSelection) => infer C 34 | ? { data: TData; selection: TSelection; options: C } 35 | : never; 36 | 37 | export type FilterOptionDataType = FilterOptionType['data']; 38 | export type FilterOptionsType = FilterOptionType['options']; 39 | export type FilterOptionSelectionType = FilterOptionType['selection']; 40 | -------------------------------------------------------------------------------- /packages/filter/src/reducers.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ 2 | 3 | import { createReducer, type ActionType } from 'typesafe-actions'; 4 | 5 | import { actions } from './actions'; 6 | import type { Filter } from './types'; 7 | 8 | export const createFilterReducer = >(initial: T) => 9 | createReducer>(initial) 10 | .handleAction(actions.filter.add, (state, { payload }) => ({ 11 | ...state, 12 | [payload.key]: payload, 13 | })) 14 | .handleAction(actions.filter.remove, (state, action) => { 15 | delete state[action.payload as keyof T]; 16 | return { ...state }; 17 | }); 18 | 19 | export const createSelectionReducer = >( 20 | initial: TSelections, 21 | ) => 22 | createReducer>(initial) 23 | .handleAction(actions.selection.set, (state, { payload }) => ({ 24 | ...state, 25 | ...(payload as TSelections), 26 | })) 27 | .handleAction(actions.selection.initial, (state, { payload }) => { 28 | return { 29 | ...state, 30 | ...(payload as TSelections), 31 | }; 32 | }) 33 | .handleAction(actions.selection.remove, (state, { payload }) => { 34 | const keys = Array.isArray(payload) ? payload : [payload]; 35 | return Object.entries(state).reduce((acc, [key, value]) => { 36 | return keys.includes(key) ? acc : Object.assign(acc, { [key]: value }); 37 | }, {}) as TSelections; 38 | }) 39 | .handleAction(actions.selection.clear, () => initial); 40 | 41 | export const createDataReducer = (initial: TData) => 42 | createReducer>(initial).handleAction( 43 | actions.source.update, 44 | (_, action) => action.payload as TData, 45 | ); 46 | -------------------------------------------------------------------------------- /packages/filter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts", "src/**/*.tsx"], 8 | "exclude": ["node_modules"], 9 | "references": [ 10 | { 11 | "path": "../styles" 12 | }, 13 | { 14 | "path": "../utils" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/hanging-garden/README.md: -------------------------------------------------------------------------------- 1 | # `HangingGarden` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const hangingGarden = require('HangingGarden'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/hanging-garden/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-hanging-garden", 3 | "version": "1.7.7", 4 | "description": "component handling rendering of hanging gardens", 5 | "author": "", 6 | "homepage": "https://github.com/equinor/fusion-react-components/tree/master/packages/HangingGarden#readme", 7 | "keywords": [ 8 | "hanginggarden", 9 | "hanging garden", 10 | "react", 11 | "fusion", 12 | "equinor" 13 | ], 14 | "license": "ISC", 15 | "main": "dist/index.js", 16 | "directories": { 17 | "dist": "dist" 18 | }, 19 | "files": [ 20 | "dist" 21 | ], 22 | "publishConfig": { 23 | "access": "public" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/equinor/fusion-react-components.git" 28 | }, 29 | "scripts": { 30 | "build": "tsc -b", 31 | "lint": "eslint --ext .ts,.tsx,.js src/", 32 | "lint:fix": "eslint --ext .ts,.tsx,.js src/ --fix", 33 | "prepack": "npm run build" 34 | }, 35 | "bugs": { 36 | "url": "https://github.com/equinor/fusion-react-components/issues" 37 | }, 38 | "dependencies": { 39 | "@equinor/fusion-react-styles": "^0.6.0", 40 | "@types/history": "4.x", 41 | "date-fns": "4.1.0", 42 | "history": "4.x", 43 | "pixi.js-legacy": "^5.3.7" 44 | }, 45 | "devDependencies": { 46 | "@equinor/fusion": "^3.3.1", 47 | "@types/node": "^22.0.0", 48 | "@types/react": "^18.2.24", 49 | "react": "^18.2.0" 50 | }, 51 | "peerDependencies": { 52 | "@equinor/fusion": "^3.3.1", 53 | "@equinor/fusion-react-styles": "0.6.4", 54 | "@types/history": "4.x", 55 | "date-fns": "4.1.0", 56 | "history": "4.x" 57 | }, 58 | "peerDependenciesMeta": { 59 | "@types/history": { 60 | "optional": true 61 | }, 62 | "@types/react": { 63 | "optional": true 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/hanging-garden/src/components/PopoverContainer/Arrow.tsx: -------------------------------------------------------------------------------- 1 | import { makeStyles, createStyles } from '@equinor/fusion-react-styles'; 2 | 3 | const useStyles = makeStyles(() => 4 | createStyles({ 5 | root: { 6 | position: 'absolute', 7 | width: '12px', 8 | height: '8px', 9 | left: '50%', 10 | bottom: '100%', 11 | transform: 'translateX(-50%)', 12 | }, 13 | }), 14 | ); 15 | 16 | const Arrow = (): JSX.Element => { 17 | const styles = useStyles(); 18 | return ( 19 | 27 | 32 | 36 | 37 | ); 38 | }; 39 | 40 | export default Arrow; 41 | -------------------------------------------------------------------------------- /packages/hanging-garden/src/components/PopoverContainer/index.tsx: -------------------------------------------------------------------------------- 1 | import { ForwardedRef, forwardRef, ReactNode } from 'react'; 2 | 3 | import { makeStyles, createStyles } from '@equinor/fusion-react-styles'; 4 | 5 | import Arrow from './Arrow'; 6 | 7 | const useStyles = makeStyles(() => 8 | createStyles({ 9 | root: { 10 | padding: '16px', 11 | backgroundColor: 'white', 12 | borderRadius: '4px', 13 | position: 'absolute', 14 | boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2), 0px 2px 2px rgba(0, 0, 0, 0.12), 0px 0px 2px rgba(0, 0, 0, 0.14)', 15 | transform: 'translateX(-50%)', 16 | marginTop: '8px', 17 | }, 18 | }), 19 | ); 20 | 21 | const PopoverContainer: React.ForwardRefExoticComponent< 22 | { 23 | children: ReactNode; 24 | } & React.RefAttributes 25 | > = forwardRef((props: { readonly children: ReactNode }, ref: ForwardedRef) => { 26 | const { children } = props; 27 | const styles = useStyles(); 28 | return ( 29 |
30 | 31 |
{children}
32 |
33 | ); 34 | }); 35 | 36 | PopoverContainer.displayName = 'PopoverContainer'; 37 | 38 | export default PopoverContainer; 39 | -------------------------------------------------------------------------------- /packages/hanging-garden/src/components/Vector2.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper class for calculating positions when drawing dashed lines 3 | */ 4 | class Vector2 { 5 | x: number; 6 | y: number; 7 | constructor(x = 0, y = 0) { 8 | this.x = x; 9 | this.y = y; 10 | } 11 | 12 | lerpVectors(v1: { x: number; y: number }, v2: { x: number; y: number }, alpha: number) { 13 | this.x = v1.x + (v2.x - v1.x) * alpha; 14 | this.y = v1.y + (v2.y - v1.y) * alpha; 15 | 16 | return this; 17 | } 18 | } 19 | export { Vector2 }; 20 | -------------------------------------------------------------------------------- /packages/hanging-garden/src/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './models/HangingGarden'; 2 | export * from './models/RenderContext'; 3 | export * from './hooks/useHangingGardenData'; 4 | export * from './hooks/useHangingGardenGetData'; 5 | export * from './hooks/useHangingGardenErrorMessage'; 6 | export * from './HangingGarden'; 7 | -------------------------------------------------------------------------------- /packages/hanging-garden/src/models/ExpandedColumn.ts: -------------------------------------------------------------------------------- 1 | export type ExpandedColumn = { 2 | isExpanded: boolean; 3 | maxWidth: number; 4 | index: number; 5 | }; 6 | 7 | export type ExpandedColumns = Record; 8 | -------------------------------------------------------------------------------- /packages/hanging-garden/src/models/GardenData.ts: -------------------------------------------------------------------------------- 1 | export type GardenData = { 2 | data: T[]; 3 | cacheAge: Date; 4 | cacheDurationInMinutes: number; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/hanging-garden/src/models/GardenDataError.ts: -------------------------------------------------------------------------------- 1 | import { FusionApiErrorMessage } from '@equinor/fusion'; 2 | 3 | export type ErrorTypes = 'error' | 'accessDenied' | 'notFound' | 'noData' | 'failedDependency' | 'throttle'; 4 | 5 | export type ErrorMessageProps = { 6 | hasError?: boolean; 7 | errorType?: ErrorTypes; 8 | message?: unknown; 9 | resourceName?: string; 10 | title?: string; 11 | children?: unknown; 12 | icon?: unknown; 13 | action?: string; 14 | onTakeAction?: (event?: React.SyntheticEvent) => void; 15 | }; 16 | 17 | export type GardenDataErrorTypes = ErrorTypes | 'noCache' | 'NoDataAccess' | 'UnexpectedError'; 18 | 19 | export type GardenDataError = { 20 | errorType: GardenDataErrorTypes; 21 | errorResponse?: FusionApiErrorMessage | null; 22 | }; 23 | 24 | export type ApiErrorGardenResponse = { 25 | error: FusionApiErrorMessage & { 26 | code: GardenDataErrorTypes; 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /packages/hanging-garden/src/models/HangingGarden.ts: -------------------------------------------------------------------------------- 1 | import { MutableRefObject } from 'react'; 2 | import { ItemRenderContext, HeaderRenderContext } from './RenderContext'; 3 | 4 | export type HangingGardenColumnIndex = Record; 5 | 6 | export type ColumnGroupHeader = { 7 | key: string; 8 | type: 'groupHeader'; 9 | level: number; 10 | }; 11 | 12 | export type HangingGardenColumn = { 13 | key: string; 14 | data: T[] | HangingGardenColumn[]; 15 | }; 16 | 17 | export type GardenController = { 18 | clearGarden: () => void; 19 | }; 20 | 21 | export type ColorMode = 'Regular' | 'Color blind'; 22 | 23 | export type HangingGardenProps = { 24 | columns: HangingGardenColumn[]; 25 | highlightedColumnKey: string | null; 26 | highlightedItem: T | null; 27 | itemKeyProp: keyof T; 28 | itemWidth: number; 29 | itemHeight: number; 30 | getItemDescription: (item: T) => string; 31 | onItemClick: (item: T) => void; 32 | headerHeight: number; 33 | renderItemContext: (item: T, context: ItemRenderContext) => void; 34 | renderHeaderContext: (key: string, context: HeaderRenderContext) => void; 35 | provideController?: MutableRefObject; 36 | backgroundColor?: number; 37 | colorMode?: ColorMode; 38 | disableScrollToHighlightedItem?: boolean; 39 | groupLevels?: number; 40 | padding?: number; 41 | }; 42 | -------------------------------------------------------------------------------- /packages/hanging-garden/src/models/RenderContext.ts: -------------------------------------------------------------------------------- 1 | import * as PIXI from 'pixi.js-legacy'; 2 | 3 | export type Position = { 4 | x: number; 5 | y: number; 6 | }; 7 | 8 | export type Size = { 9 | width: number; 10 | height: number; 11 | }; 12 | 13 | export type RenderContext = { 14 | container: PIXI.Container; 15 | width: number; 16 | height: number; 17 | graphics: PIXI.Graphics; 18 | createTextNode: (text: string, color: number) => PIXI.Sprite; 19 | }; 20 | 21 | export type HeaderRenderContext = RenderContext & { 22 | isHighlighted: boolean; 23 | isExpanded: boolean; 24 | }; 25 | 26 | export type ItemRenderContext = RenderContext & { 27 | createRect: (position: Position, size: Size, color: number) => void; 28 | addDot: (color: number, position: Position, borderColor?: number) => void; 29 | addPopover: (hitArea: PIXI.Rectangle, renderPopover: () => JSX.Element) => void; 30 | enquedRender: (key: string, render: (context: ItemRenderContext) => void) => void; 31 | }; 32 | 33 | export type RenderItem = { 34 | key: string; 35 | render: (context: ItemRenderContext) => void; 36 | context: ItemRenderContext; 37 | }; 38 | -------------------------------------------------------------------------------- /packages/hanging-garden/src/renderHooks/usePixiApp.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, useMemo } from 'react'; 2 | import * as PIXI from 'pixi.js-legacy'; 3 | 4 | /** 5 | * set up for the PIXI.js api 6 | * 7 | * This hook is used by the Garden and is not intended to be used or implemented 8 | * outside the Garden component. 9 | */ 10 | type UsePixiApp = { 11 | pixiApp: React.MutableRefObject; 12 | }; 13 | 14 | const usePixiApp = ( 15 | canvas: React.RefObject | null, 16 | container: React.RefObject | null, 17 | backgroundColor: number, 18 | ): UsePixiApp => { 19 | PIXI.utils.skipHello(); // Don't output the pixi message to the console 20 | PIXI.Ticker.shared.autoStart = false; 21 | PIXI.settings.ROUND_PIXELS = true; 22 | 23 | const pixiApp = useRef(null); 24 | 25 | pixiApp.current = useMemo(() => { 26 | if (!canvas?.current || !container?.current) return null; 27 | 28 | return new PIXI.Application({ 29 | view: canvas.current, 30 | width: container.current?.offsetWidth, 31 | height: container.current?.offsetHeight, 32 | backgroundColor, 33 | resolution: 1, 34 | antialias: true, 35 | transparent: true, 36 | sharedTicker: true, 37 | }); 38 | }, [canvas?.current, container?.current, backgroundColor]); 39 | 40 | if (pixiApp.current) { 41 | pixiApp.current.renderer.plugins.interaction.autoPreventDefault = false; 42 | pixiApp.current.renderer.view.style.touchAction = 'auto'; 43 | } 44 | 45 | return { pixiApp }; 46 | }; 47 | 48 | export default usePixiApp; 49 | -------------------------------------------------------------------------------- /packages/hanging-garden/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "types": ["node", "./types"] 7 | }, 8 | "include": ["src/**/*.ts", "src/**/*.tsx", "types/types.d.ts"], 9 | "exclude": ["node_modules"], 10 | "references": [ 11 | { 12 | "path": "../styles" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/hanging-garden/types/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Method for drawing dashed lines 3 | * @param toX x-coordinate you want to draw the dashed lines to 4 | * @param toY y-coordinate you want to draw the dashed lines to 5 | * @param dash length of the dashed lines 6 | * @param gap the gap between the dashed lines 7 | */ 8 | interface drawDashLine { 9 | (toX: number, toY: number, dash?: number, gap?: number): void; 10 | } 11 | 12 | declare namespace PIXI { 13 | export interface Graphics { 14 | drawDashLine: drawDashLine; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/list/README.md: -------------------------------------------------------------------------------- 1 | 2 | # @equinor/fusion-react-list 3 | 4 | [![Published on npm](https://img.shields.io/npm/v/@equinor/fusion-react-list.svg)](https://www.npmjs.com/package/@equinor/fusion-react-list) 5 | 6 | [Storybook](https://equinor.github.io/fusion-react-components/?path=/docs/data-list) 7 | 8 | [Fusion Web Component](https://github.com/equinor/fusion-web-components/tree/main/packages/list) 9 | 10 | ## Installation 11 | 12 | ```sh 13 | npm install @equinor/fusion-react-list 14 | ``` 15 | 16 | -------------------------------------------------------------------------------- /packages/list/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-list", 3 | "version": "0.3.4", 4 | "description": "React components for displaying a list", 5 | "keywords": ["list", "react", "fusion", "equinor"], 6 | "homepage": "https://github.com/equinor/fusion-react-components/tree/master/packages/list#readme", 7 | "license": "ISC", 8 | "main": "dist/index.js", 9 | "directories": { 10 | "dist": "dist" 11 | }, 12 | "files": ["dist"], 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/equinor/fusion-react-components.git" 19 | }, 20 | "scripts": { 21 | "build": "tsc -b", 22 | "lint": "eslint --ext .ts,.tsx,.js src/", 23 | "lint:fix": "eslint --ext .ts,.tsx,.js src/ --fix", 24 | "prepack": "npm run build" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/equinor/fusion-react-components/issues" 28 | }, 29 | "dependencies": { 30 | "@equinor/fusion-react-utils": "^2.1.0", 31 | "@equinor/fusion-wc-list": "^1.1.4" 32 | }, 33 | "devDependencies": { 34 | "@types/react": "^18.2.24", 35 | "react": "^18.2.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/list/src/components/CheckListItem.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentProps, createComponent } from '@equinor/fusion-react-utils'; 2 | import { 3 | CheckListItemElement as HTMLCheckListItemCustomElement, 4 | checkListItemTag as tag, 5 | } from '@equinor/fusion-wc-list'; 6 | 7 | type ElementProps = React.PropsWithChildren< 8 | Partial< 9 | Pick< 10 | HTMLCheckListItemCustomElement, 11 | | 'left' 12 | | 'value' 13 | | 'group' 14 | | 'tabindex' 15 | | 'disabled' 16 | | 'twoline' 17 | | 'activated' 18 | | 'graphic' 19 | | 'hasMeta' 20 | | 'noninteractive' 21 | | 'selected' 22 | | 'text' 23 | > 24 | > 25 | >; 26 | 27 | export const CheckListItem = createComponent( 28 | HTMLCheckListItemCustomElement, 29 | tag, 30 | ); 31 | 32 | export type CheckListItemProps = ComponentProps; 33 | 34 | export { HTMLCheckListItemCustomElement }; 35 | 36 | export default CheckListItem; 37 | -------------------------------------------------------------------------------- /packages/list/src/components/List.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentProps, createComponent } from '@equinor/fusion-react-utils'; 2 | import HTMLListCustomElement, { tag } from '@equinor/fusion-wc-list'; 3 | 4 | type ElementProps = React.PropsWithChildren< 5 | Partial< 6 | Pick< 7 | HTMLListCustomElement, 8 | | 'activatable' 9 | | 'multi' 10 | | 'emptyMessage' 11 | | 'wrapFocus' 12 | | 'itemRoles' 13 | | 'innerRole' 14 | | 'innerAriaLabel' 15 | | 'rootTabbable' 16 | | 'noninteractive' 17 | > 18 | > 19 | >; 20 | 21 | export const List = createComponent( 22 | HTMLListCustomElement, 23 | tag, 24 | ); 25 | export type ListProps = ComponentProps; 26 | 27 | export { HTMLListCustomElement }; 28 | 29 | export default List; 30 | -------------------------------------------------------------------------------- /packages/list/src/components/ListItem.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentProps, createComponent } from '@equinor/fusion-react-utils'; 2 | import { 3 | ListItemElement as HTMLListItemCustomElement, 4 | listItemTag as tag, 5 | } from '@equinor/fusion-wc-list'; 6 | 7 | export type ElementProps = React.PropsWithChildren< 8 | Partial< 9 | Pick< 10 | HTMLListItemCustomElement, 11 | | 'value' 12 | | 'group' 13 | | 'tabindex' 14 | | 'disabled' 15 | | 'twoline' 16 | | 'activated' 17 | | 'graphic' 18 | | 'hasMeta' 19 | | 'noninteractive' 20 | | 'selected' 21 | | 'text' 22 | > 23 | > 24 | >; 25 | 26 | export const ListItem = createComponent( 27 | HTMLListItemCustomElement, 28 | tag, 29 | ); 30 | export type ListItemProps = ComponentProps; 31 | 32 | export { HTMLListItemCustomElement }; 33 | 34 | export default ListItem; 35 | -------------------------------------------------------------------------------- /packages/list/src/components/RadioListItem.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentProps, createComponent } from '@equinor/fusion-react-utils'; 2 | import { 3 | RadioListItemElement as HTMLRadioListItemCustomElement, 4 | radioListItemTag as tag, 5 | } from '@equinor/fusion-wc-list'; 6 | 7 | type ElementProps = React.PropsWithChildren< 8 | Partial< 9 | Pick< 10 | HTMLRadioListItemCustomElement, 11 | | 'left' 12 | | 'value' 13 | | 'group' 14 | | 'tabindex' 15 | | 'disabled' 16 | | 'twoline' 17 | | 'activated' 18 | | 'graphic' 19 | | 'hasMeta' 20 | | 'noninteractive' 21 | | 'selected' 22 | | 'text' 23 | > 24 | > 25 | >; 26 | 27 | export const RadioListItem = createComponent( 28 | HTMLRadioListItemCustomElement, 29 | tag, 30 | ); 31 | export type RadioListItemProps = ComponentProps; 32 | 33 | export { HTMLRadioListItemCustomElement }; 34 | 35 | export default RadioListItem; 36 | -------------------------------------------------------------------------------- /packages/list/src/index.ts: -------------------------------------------------------------------------------- 1 | export { List as default } from './components/List'; 2 | export * from './components/List'; 3 | export * from './components/ListItem'; 4 | export * from './components/CheckListItem'; 5 | export * from './components/RadioListItem'; 6 | -------------------------------------------------------------------------------- /packages/list/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts", "src/**/*.tsx"], 8 | "exclude": ["node_modules"], 9 | "references": [{ "path": "../utils" }] 10 | } 11 | -------------------------------------------------------------------------------- /packages/markdown/README.md: -------------------------------------------------------------------------------- 1 | 2 | # @equinor/fusion-react-markdown 3 | 4 | [![Published on npm](https://img.shields.io/npm/v/@equinor/fusion-react-markdown.svg)](https://www.npmjs.com/package/@equinor/fusion-react-markdown) 5 | 6 | [Storybook](https://equinor.github.io/fusion-react-components/?path=/docs/input-markdown-editor--page) 7 | 8 | [Fusion Web Component](https://equinor.github.io/fusion-web-components/?path=/docs/input-markdown-editor--basic) 9 | 10 | ### Installation 11 | ```sh 12 | npm install @equinor/fusion-react-markdown 13 | ``` 14 | 15 | ### Markdown Editor 16 | 17 | #### Example 18 | 19 | ```tsx 20 | import { MarkdownEditor } from '@equinor/fusion-react-markdown'; 21 | 22 | 23 | **some** markdown *text* 24 | 25 | ``` 26 | 27 | #### Usage 28 | ```tsx 29 | import { MarkdownEditor } from '@equinor/fusion-react-markdown'; 30 | 31 | const markdown = "# my heading here"; 32 | {console.log(e.target._value)}} change={console.log} /> 33 | ``` 34 | 35 | #### Properties/Attributes 36 | | Name | Type | Default | Description 37 | | ---- | ---- | ------- | ----------- 38 | | `menuItems` | `Array*` | `['strong', 'em', 'bullet_list', 'ordered_list']` | List of visible menu buttons 39 | | `minHeight` | `string` | `''` | Markdown Editor minimum height 40 | | `value` | `string` | `''` | Markdown editors value 41 | | `menuSize` | `MenuSizes**` | `'medium'` | Size of the menu buttons 42 | 43 | \* `Array` is list of showing visible menu buttons available as `MdMenuItemType`. 44 | ```ts 45 | type MdMenuItemType = 46 | | 'strong' 47 | | 'em' 48 | | 'link' 49 | | 'ordered_list' 50 | | 'bullet_list' 51 | | 'paragraph' 52 | | 'blockquote' 53 | | 'h1' 54 | | 'h2' 55 | | 'h3'; 56 | ``` 57 | 58 | \* `MenuSizes` type imported from markdown component. 59 | ```ts 60 | type MenuSizes = 'small' | 'medium' | 'large'; 61 | ``` 62 | 63 | 64 | -------------------------------------------------------------------------------- /packages/markdown/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-markdown", 3 | "version": "0.3.4", 4 | "description": "React component for editing morkdown language", 5 | "keywords": ["markdown", "editing", "markdown editor", "fusion", "equinor"], 6 | "homepage": "https://github.com/equinor/fusion-react-components/tree/main/packages/markdown#readme", 7 | "license": "ISC", 8 | "main": "dist/index.js", 9 | "module": "dist/index.js", 10 | "types": "dist/index.d.ts", 11 | "directories": { 12 | "dist": "dist" 13 | }, 14 | "files": ["dist"], 15 | "publishConfig": { 16 | "access": "public" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/equinor/fusion-react-components.git" 21 | }, 22 | "scripts": { 23 | "build": "tsc -b", 24 | "lint": "eslint --ext .ts,.tsx,.js src/", 25 | "lint:fix": "eslint --ext .ts,.tsx,.js src/ --fix", 26 | "prepack": "npm run build" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/equinor/fusion-react-components/issues" 30 | }, 31 | "dependencies": { 32 | "@equinor/fusion-react-utils": "^2.1.0", 33 | "@equinor/fusion-wc-markdown": "^1.3.1" 34 | }, 35 | "devDependencies": { 36 | "@types/react": "^18.2.24", 37 | "react": "^18.2.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/markdown/src/MarkdownEditor.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react'; 2 | import { type ComponentProps, createComponent } from '@equinor/fusion-react-utils'; 3 | import { 4 | MarkdownEditorElement, 5 | markdownEditorTag, 6 | type MenuSizes, 7 | } from '@equinor/fusion-wc-markdown'; 8 | 9 | type ElementProps = PropsWithChildren< 10 | Partial> 11 | >; 12 | 13 | export type MarkdownEditorProps = ComponentProps; 14 | export const MarkdownEditor = createComponent( 15 | MarkdownEditorElement, 16 | markdownEditorTag, 17 | { events: { onInput: 'markdownEvent' } }, 18 | ); 19 | 20 | export { MarkdownEditorElement as HTMLMarkdownEditorCustomElement, type MenuSizes }; 21 | 22 | export default MarkdownEditor; 23 | -------------------------------------------------------------------------------- /packages/markdown/src/MarkdownViewer.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react'; 2 | import { type ComponentProps, createComponent } from '@equinor/fusion-react-utils'; 3 | import { MarkdownViewerElement, markdownViewerTag } from '@equinor/fusion-wc-markdown'; 4 | 5 | type ElementProps = PropsWithChildren>>; 6 | 7 | export type MarkdownViewerProps = ComponentProps; 8 | export const MarkdownViewer = createComponent( 9 | MarkdownViewerElement, 10 | markdownViewerTag, 11 | ); 12 | 13 | export { MarkdownViewerElement as HTMLMarkdownViewerCustomElement }; 14 | 15 | export default MarkdownViewer; 16 | -------------------------------------------------------------------------------- /packages/markdown/src/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './MarkdownEditor'; 2 | export type { MenuSizes } from '@equinor/fusion-wc-markdown'; 3 | export * from './MarkdownViewer'; 4 | -------------------------------------------------------------------------------- /packages/markdown/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts", "src/**/*.tsx"], 8 | "exclude": ["node_modules"], 9 | "references": [{ "path": "../utils" }] 10 | } 11 | -------------------------------------------------------------------------------- /packages/person/README.md: -------------------------------------------------------------------------------- 1 | 2 | # @equinor/fusion-react-person 3 | 4 | [![Published on npm](https://img.shields.io/npm/v/@equinor/fusion-react-person.svg)](https://www.npmjs.com/package/@equinor/fusion-react-person) 5 | 6 | [Storybook](https://equinor.github.io/fusion-react-components/?path=/docs/person-docs--docs) 7 | 8 | [Fusion Web Component](https://equinor.github.com/equinor/fusion-web-components/tree/main/packages/person) 9 | 10 | ### Installation 11 | 12 | ```sh 13 | npm install @equinor/fusion-react-person 14 | ``` 15 | 16 | ## Components 17 | 18 | - [PersonAvatar](https://equinor.github.io/fusion-react-components/?path=/story/person-avatar--basic) - This component displays a person's image with an account type border and provides detailed information on hover. 19 | - [PersonCard](https://equinor.github.io/fusion-react-components/?path=/story/person-card--basic) - Use this component to display comprehensive information about a person,including their department, positions, tasks, and current manager 20 | - [PersonListItem](https://equinor.github.io/fusion-react-components/?path=/story/person-list-item--basic) - This component showcases basic information about a person, with the option to add additional buttons. 21 | - [PersonSelect](https://equinor.github.io/fusion-react-components/?path=/story/person-select--basic) - Searchable dropdown of person in equinor. 22 | - [PersonCell](https://equinor.github.io/fusion-react-components/?path=/story/person-cell--basic) - Person item used in ag-grid and tables 23 | - [PersonProvider](https://equinor.github.io/fusion-react-components/?path=/docs/person-provider--docs) - Create a custom provider for the person components, used when when you need to customize the person search results in resolver. 24 | 25 | 26 | -------------------------------------------------------------------------------- /packages/person/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-person", 3 | "version": "0.10.4", 4 | "description": "React component for displaying an person information", 5 | "keywords": ["person", "card", "react", "fusion", "equinor", "search", "people", "personnel"], 6 | "homepage": "https://github.com/equinor/fusion-react-components/edit/feat/person-card/packages/person#readme", 7 | "license": "ISC", 8 | "main": "dist/index.js", 9 | "module": "dist/index.js", 10 | "types": "dist/index.d.ts", 11 | "directories": { 12 | "dist": "dist" 13 | }, 14 | "files": ["dist"], 15 | "publishConfig": { 16 | "access": "public" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/equinor/fusion-react-components.git" 21 | }, 22 | "scripts": { 23 | "build": "tsc -b", 24 | "lint": "eslint --ext .ts,.tsx,.js src/", 25 | "lint:fix": "eslint --ext .ts,.tsx,.js src/ --fix", 26 | "prepack": "npm run build" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/equinor/fusion-react-components/issues" 30 | }, 31 | "dependencies": { 32 | "@equinor/fusion-react-utils": "workspace:^", 33 | "@equinor/fusion-wc-person": "^3.1.7" 34 | }, 35 | "devDependencies": { 36 | "@types/react": "^18.2.74", 37 | "react": "^18.2.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/person/src/PersonAvatar.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react'; 2 | import { type ComponentProps, createComponent } from '@equinor/fusion-react-utils'; 3 | import HTMLPersonAvatarCustomElement, { 4 | tag, 5 | AvatarSize, 6 | type AvatarData, 7 | } from '@equinor/fusion-wc-person/avatar'; 8 | 9 | type ElementProps = PropsWithChildren< 10 | Partial< 11 | Pick< 12 | HTMLPersonAvatarCustomElement, 13 | 'azureId' | 'upn' | 'dataSource' | 'size' | 'clickable' | 'disabled' | 'trigger' 14 | > 15 | > 16 | >; 17 | 18 | export type PersonAvatarProps = ComponentProps; 19 | 20 | export const PersonAvatar = createComponent( 21 | HTMLPersonAvatarCustomElement, 22 | tag, 23 | ); 24 | 25 | export { AvatarSize }; 26 | export type { HTMLPersonAvatarCustomElement, AvatarData }; 27 | export default PersonAvatar; 28 | -------------------------------------------------------------------------------- /packages/person/src/PersonCard.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react'; 2 | import { type ComponentProps, createComponent } from '@equinor/fusion-react-utils'; 3 | import HTMLPersonCardCustomElement, { tag, type CardData } from '@equinor/fusion-wc-person/card'; 4 | import type { PersonItemSize } from '@equinor/fusion-wc-person'; 5 | 6 | type ElementProps = PropsWithChildren< 7 | Partial< 8 | Pick< 9 | HTMLPersonCardCustomElement, 10 | 'azureId' | 'upn' | 'dataSource' | 'size' | 'maxWidth' | 'contentHeight' 11 | > 12 | > 13 | >; 14 | 15 | export type PersonCardProps = ComponentProps; 16 | export const PersonCard = createComponent( 17 | HTMLPersonCardCustomElement, 18 | tag, 19 | ); 20 | 21 | export type { HTMLPersonCardCustomElement, PersonItemSize, CardData }; 22 | export default PersonCard; 23 | -------------------------------------------------------------------------------- /packages/person/src/PersonCell.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react'; 2 | import { type ComponentProps, createComponent } from '@equinor/fusion-react-utils'; 3 | import HTMLPersonCellCustomElement, { 4 | tag, 5 | type TableCellData as PersonCellData, 6 | } from '@equinor/fusion-wc-person/table-cell'; 7 | 8 | type ElementProps = PropsWithChildren< 9 | Partial< 10 | Pick< 11 | HTMLPersonCellCustomElement, 12 | 'azureId' | 'upn' | 'dataSource' | 'size' | 'heading' | 'subHeading' | 'showAvatar' 13 | > 14 | > 15 | >; 16 | 17 | export type PersonCellProps = ComponentProps; 18 | export const PersonCell = createComponent( 19 | HTMLPersonCellCustomElement, 20 | tag, 21 | ); 22 | 23 | export type { PersonCellData, HTMLPersonCellCustomElement }; 24 | export default PersonCell; 25 | -------------------------------------------------------------------------------- /packages/person/src/PersonListItem.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react'; 2 | import { type ComponentProps, createComponent } from '@equinor/fusion-react-utils'; 3 | import HTMLPersonListItemCustomElement, { 4 | tag, 5 | type ListItemData, 6 | } from '@equinor/fusion-wc-person/list-item'; 7 | 8 | type ElementProps = PropsWithChildren< 9 | Partial< 10 | Pick 11 | > 12 | >; 13 | 14 | export type PersonListItemProps = ComponentProps; 15 | export const PersonListItem = createComponent( 16 | HTMLPersonListItemCustomElement, 17 | tag, 18 | ); 19 | 20 | export type { ListItemData, HTMLPersonListItemCustomElement }; 21 | export default PersonListItem; 22 | -------------------------------------------------------------------------------- /packages/person/src/PersonProvider.tsx: -------------------------------------------------------------------------------- 1 | import { type PropsWithChildren, useLayoutEffect, useRef } from 'react'; 2 | import type { PersonProviderElement, PersonResolver } from '@equinor/fusion-wc-person'; 3 | 4 | /** Person provider properties for the resolver */ 5 | export type PersonProviderProps = { 6 | /** Person resolver interface that contains search, getDetails, getInfo and getPhoto functions */ 7 | readonly resolve: PersonResolver; 8 | }; 9 | 10 | /** 11 | * Person provider component that checks and sets the resolver given as components property 12 | * @param props resolver and children 13 | * @returns wrapped fusion web-components person provider with its reference around children 14 | */ 15 | export const PersonProvider = (props: PropsWithChildren) => { 16 | const { resolve, children } = props; 17 | const providerRef = useRef(null); 18 | 19 | useLayoutEffect(() => { 20 | if (providerRef.current) { 21 | providerRef.current.resolver = resolve; 22 | } 23 | }, [resolve]); 24 | 25 | return {children}; 26 | }; 27 | 28 | export default PersonProvider; 29 | -------------------------------------------------------------------------------- /packages/person/src/PersonSelect.tsx: -------------------------------------------------------------------------------- 1 | import type { BaseSyntheticEvent, PropsWithChildren } from 'react'; 2 | import { type ComponentProps, createComponent } from '@equinor/fusion-react-utils'; 3 | import HTMLPersonSelectCustomElement, { 4 | tag, 5 | type PersonSelectEvent as HTMLPersonSelectEvent, 6 | type PersonSelectElementProps, 7 | } from '@equinor/fusion-wc-person/select'; 8 | 9 | type ElementAttributes = PropsWithChildren< 10 | Partial< 11 | Pick< 12 | PersonSelectElementProps, 13 | | 'autofocus' 14 | | 'disabled' 15 | | 'dropdownHeight' 16 | | 'graphic' 17 | | 'initialText' 18 | | 'label' 19 | | 'leadingIcon' 20 | | 'meta' 21 | | 'multiple' 22 | | 'placeholder' 23 | | 'selectedId' 24 | | 'selectedPerson' 25 | | 'value' 26 | | 'variant' 27 | > 28 | > 29 | >; 30 | 31 | export type PersonSelectEvent = BaseSyntheticEvent; 32 | 33 | type ElementEvents = { 34 | onSelect?: (e: PersonSelectEvent) => void; 35 | onDropdownClosed?: (e: CustomEvent) => void; 36 | }; 37 | 38 | type ElementProps = ElementAttributes & ElementEvents; 39 | 40 | export type PersonSelectProps = ComponentProps; 41 | 42 | export const PersonSelect = createComponent( 43 | HTMLPersonSelectCustomElement, 44 | tag, 45 | { events: { onSelect: 'select', onDropdownClosed: 'dropdownClosed' } }, 46 | ); 47 | 48 | export default PersonSelect; 49 | -------------------------------------------------------------------------------- /packages/person/src/extract-props.tsx: -------------------------------------------------------------------------------- 1 | export const extractProps = >( 2 | props: T | Record, 3 | ): T => 4 | Object.keys(props).reduce((acc, key) => { 5 | const value = props[key]; 6 | return value ? Object.assign(acc, { [key]: value }) : acc; 7 | }, {}) as unknown as T; 8 | 9 | export default extractProps; 10 | -------------------------------------------------------------------------------- /packages/person/src/index.tsx: -------------------------------------------------------------------------------- 1 | // export components 2 | export { PersonAvatar } from './PersonAvatar'; 3 | export { PersonCard } from './PersonCard'; 4 | export { PersonListItem } from './PersonListItem'; 5 | export { PersonCell } from './PersonCell'; 6 | export { PersonSelect } from './PersonSelect'; 7 | export { PersonProvider } from './PersonProvider'; 8 | // export enum 9 | export { AvatarSize } from './PersonAvatar'; 10 | export { PersonAvailability, PersonAccountType } from '@equinor/fusion-wc-person'; 11 | // export types 12 | export type { AvatarData, PersonAvatarProps } from './PersonAvatar'; 13 | export type { CardData, PersonItemSize, PersonCardProps } from './PersonCard'; 14 | export type { ListItemData, PersonListItemProps } from './PersonListItem'; 15 | export type { PersonCellData, PersonCellProps } from './PersonCell'; 16 | export type { PersonSelectProps, PersonSelectEvent } from './PersonSelect'; 17 | export type { PersonResolver } from '@equinor/fusion-wc-person/provider'; 18 | export type { 19 | PersonDetails, 20 | PersonPresence, 21 | PersonPicture, 22 | PersonInfo, 23 | PersonSearchResult, 24 | } from '@equinor/fusion-wc-person'; 25 | -------------------------------------------------------------------------------- /packages/person/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts", "src/**/*.tsx"], 8 | "exclude": ["node_modules"], 9 | "references": [{ "path": "../utils" }] 10 | } 11 | -------------------------------------------------------------------------------- /packages/ripple/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # @equinor/fusion-react-ripple 4 | 5 | [![Published on npm](https://img.shields.io/npm/v/@equinor/fusion-react-ripple.svg)](https://www.npmjs.com/package/@equinor/fusion-react-ripple) 6 | 7 | [Storybook](https://equinor.github.io/fusion-react-components/?path=/docs/data-ripple) 8 | 9 | [Fusion Web Component](https://github.com/equinor/fusion-web-components/tree/main/packages/ripple) 10 | 11 | ## Installation 12 | 13 | ```sh 14 | npm install @equinor/fusion-react-ripple 15 | ``` 16 | 17 | ### Description 18 | 19 | Display a ripple effect on your content when interacting with it for visual feedback. 20 | 21 | ### When To Use 22 | 23 | - When feedback for an action is needed 24 | - When user needs a sense of focus, direction or progress 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/ripple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-ripple", 3 | "version": "0.4.4", 4 | "description": "React component for displaying a ripple effect", 5 | "keywords": ["ripple", "react", "fusion", "equinor"], 6 | "homepage": "https://github.com/equinor/fusion-react-components/tree/master/packages/ripple#readme", 7 | "license": "ISC", 8 | "main": "dist/index.js", 9 | "directories": { 10 | "dist": "dist" 11 | }, 12 | "files": ["dist"], 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/equinor/fusion-react-components.git" 19 | }, 20 | "scripts": { 21 | "build": "tsc -b", 22 | "lint": "eslint --ext .ts,.tsx,.js src/", 23 | "lint:fix": "eslint --ext .ts,.tsx,.js src/ --fix", 24 | "prepack": "npm run build" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/equinor/fusion-react-components/issues" 28 | }, 29 | "dependencies": { 30 | "@equinor/fusion-react-utils": "^2.1.0", 31 | "@equinor/fusion-wc-ripple": "^1.1.1" 32 | }, 33 | "devDependencies": { 34 | "@types/react": "^18.2.24", 35 | "react": "^18.2.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/ripple/src/Ripple.tsx: -------------------------------------------------------------------------------- 1 | import { createComponent } from '@equinor/fusion-react-utils'; 2 | 3 | import HTMLRippleCustomElement, { 4 | tag, 5 | RippleElement, 6 | type RippleElementProps, 7 | RippleHandlers, 8 | } from '@equinor/fusion-wc-ripple'; 9 | 10 | export { HTMLRippleCustomElement, RippleElement, type RippleElementProps, RippleHandlers }; 11 | 12 | type ElementProps = React.PropsWithChildren< 13 | Partial< 14 | Pick< 15 | HTMLRippleCustomElement, 16 | 'primary' | 'accent' | 'unbounded' | 'activated' | 'selected' | 'disabled' 17 | > 18 | > 19 | >; 20 | 21 | export const Ripple = createComponent( 22 | HTMLRippleCustomElement, 23 | tag, 24 | ); 25 | 26 | export type RippleProps = React.ComponentProps; 27 | 28 | export default Ripple; 29 | -------------------------------------------------------------------------------- /packages/ripple/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentProps, createComponent } from '@equinor/fusion-react-utils'; 2 | 3 | import HTMLRippleCustomElement, { 4 | tag, 5 | RippleElement, 6 | RippleHandlers, 7 | } from '@equinor/fusion-wc-ripple'; 8 | 9 | type ElementProps = React.PropsWithChildren< 10 | Partial< 11 | Pick< 12 | HTMLRippleCustomElement, 13 | 'primary' | 'accent' | 'unbounded' | 'activated' | 'selected' | 'disabled' 14 | > 15 | > 16 | >; 17 | 18 | export const Ripple = createComponent( 19 | HTMLRippleCustomElement, 20 | tag, 21 | ); 22 | 23 | export type RippleProps = ComponentProps; 24 | 25 | export { HTMLRippleCustomElement, RippleElement, RippleHandlers }; 26 | 27 | export default Ripple; 28 | -------------------------------------------------------------------------------- /packages/ripple/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts", "src/**/*.tsx"], 8 | "exclude": ["node_modules"], 9 | "references": [{ "path": "../utils" }] 10 | } 11 | -------------------------------------------------------------------------------- /packages/searchable-dropdown/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # @equinor/fusion-react-searchable-dropdown 4 | 5 | [![Published on npm](https://img.shields.io/npm/v/@equinor/fusion-react-searchable-dropdown.svg)](https://www.npmjs.com/package/@equinor/fusion-react-searchable-dropdown) 6 | 7 | [Storybook](https://equinor.github.io/fusion-react-components/?path=/docs/data-searchabledropdown) 8 | 9 | [Fusion Web Component](https://github.com/equinor/fusion-web-components/tree/main/packages/searchable-dropdown) 10 | 11 | ### Installation 12 | 13 | ```sh 14 | npm install @equinor/fusion-react-searchable-dropdown 15 | ``` 16 | -------------------------------------------------------------------------------- /packages/searchable-dropdown/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-searchable-dropdown", 3 | "version": "1.0.4", 4 | "description": "component for displaying searchable dropdown", 5 | "keywords": ["searchable dropdown", "text input", "list", "react", "fusion", "equinor"], 6 | "homepage": "https://github.com/equinor/fusion-react-components/tree/master/packages/searchable-dropdown#readme", 7 | "license": "ISC", 8 | "main": "dist/index.js", 9 | "directories": { 10 | "dist": "dist" 11 | }, 12 | "files": ["dist"], 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/equinor/fusion-react-components.git" 19 | }, 20 | "scripts": { 21 | "build": "tsc -b", 22 | "lint": "eslint --ext .ts,.tsx,.js src/", 23 | "lint:fix": "eslint --ext .ts,.tsx,.js src/ --fix", 24 | "prepack": "npm run build" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/equinor/fusion-react-components/issues" 28 | }, 29 | "dependencies": { 30 | "@equinor/fusion-react-utils": "^2.1.1", 31 | "@equinor/fusion-wc-icon": "^2.3.1", 32 | "@equinor/fusion-wc-searchable-dropdown": "^4.0.3" 33 | }, 34 | "devDependencies": { 35 | "@types/react": "^18.2.64", 36 | "react": "^18.2.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/searchable-dropdown/src/Dropdown.tsx: -------------------------------------------------------------------------------- 1 | import type { BaseSyntheticEvent } from 'react'; 2 | import { type ComponentProps, createComponent } from '@equinor/fusion-react-utils'; 3 | 4 | import { 5 | SearchableDropdownElement, 6 | type SearchableDropdownProps, 7 | type SearchableDropdownSelectEvent as HTMLSearchableDropdownSelectEvent, 8 | } from '@equinor/fusion-wc-searchable-dropdown'; 9 | 10 | export type { 11 | SearchableDropdownResult, 12 | SearchableDropdownResultItem, 13 | SearchableDropdownResolver, 14 | } from '@equinor/fusion-wc-searchable-dropdown'; 15 | 16 | export { IconType } from '@equinor/fusion-wc-icon'; 17 | 18 | type ElementAttributes = Partial>; 19 | 20 | export type SearchableDropdownSelectEvent = BaseSyntheticEvent; 21 | 22 | type ElementEvents = { 23 | onSelect?: (e: SearchableDropdownSelectEvent) => void; 24 | onDropdownClosed?: (e: CustomEvent) => void; 25 | }; 26 | 27 | type ElementProps = ElementAttributes & ElementEvents; 28 | 29 | export type DropdownProps = ComponentProps; 30 | 31 | export const Dropdown = createComponent( 32 | SearchableDropdownElement, 33 | 'fwc-searchable-dropdown', 34 | { events: { onSelect: 'select', onDropdownClosed: 'dropdownClosed' } }, 35 | ); 36 | 37 | export default Dropdown; 38 | -------------------------------------------------------------------------------- /packages/searchable-dropdown/src/DropdownProvider.tsx: -------------------------------------------------------------------------------- 1 | import { SearchableDropdownProviderElement } from '@equinor/fusion-wc-searchable-dropdown'; 2 | import { createComponent } from '@equinor/fusion-react-utils'; 3 | 4 | export const DropdownProvider = createComponent( 5 | SearchableDropdownProviderElement, 6 | 'fwc-searchable-dropdown-provider', 7 | ); 8 | 9 | export default DropdownProvider; 10 | -------------------------------------------------------------------------------- /packages/searchable-dropdown/src/SearchableDropdown.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentProps } from '@equinor/fusion-react-utils'; 2 | import type { 3 | SearchableDropdownElement, 4 | SearchableDropdownResolver, 5 | } from '@equinor/fusion-wc-searchable-dropdown'; 6 | 7 | import DropdownProvider from './DropdownProvider'; 8 | import { Dropdown, type DropdownProps } from './Dropdown'; 9 | import { useDropdownProviderRef } from './useDropdownProviderRef'; 10 | 11 | type ElementProps = DropdownProps & { resolver?: SearchableDropdownResolver }; 12 | 13 | export type SearchableDropdownProps = ComponentProps; 14 | 15 | export const SearchableDropdown = ({ 16 | children, 17 | resolver, 18 | ...props 19 | }: React.PropsWithChildren): JSX.Element => { 20 | const providerRef = useDropdownProviderRef(resolver); 21 | return ( 22 | 23 | {children} 24 | 25 | ); 26 | }; 27 | 28 | export default SearchableDropdown; 29 | -------------------------------------------------------------------------------- /packages/searchable-dropdown/src/index.tsx: -------------------------------------------------------------------------------- 1 | export { 2 | Dropdown, 3 | type DropdownProps, 4 | IconType, 5 | type SearchableDropdownResolver, 6 | type SearchableDropdownResult, 7 | type SearchableDropdownResultItem, 8 | SearchableDropdownSelectEvent, 9 | } from './Dropdown'; 10 | export { DropdownProvider } from './DropdownProvider'; 11 | export { useDropdownProviderRef } from './useDropdownProviderRef'; 12 | 13 | export { SearchableDropdown, SearchableDropdownProps } from './SearchableDropdown'; 14 | 15 | export { 16 | SearchableDropdownElement as HTMLSearchableDropdownCustomElement, 17 | SearchableDropdownProviderElement as HTMLSearchableDropdownProviderCustomElement, 18 | } from '@equinor/fusion-wc-searchable-dropdown'; 19 | 20 | export { Dropdown as default } from './Dropdown'; 21 | -------------------------------------------------------------------------------- /packages/searchable-dropdown/src/useDropdownProviderRef.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect, type MutableRefObject } from 'react'; 2 | import type { 3 | SearchableDropdownProviderElement, 4 | SearchableDropdownResolver, 5 | } from '@equinor/fusion-wc-searchable-dropdown'; 6 | 7 | export const useDropdownProviderRef = ( 8 | resolver?: SearchableDropdownResolver, 9 | ): MutableRefObject => { 10 | const providerRef = useRef(null); 11 | useEffect(() => { 12 | const current = providerRef?.current; 13 | if (current && resolver) { 14 | current.connectResolver(resolver); 15 | return () => { 16 | current.removeResolver(); 17 | }; 18 | } 19 | }, [resolver]); 20 | 21 | return providerRef; 22 | }; 23 | -------------------------------------------------------------------------------- /packages/searchable-dropdown/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts", "src/**/*.tsx"], 8 | "exclude": ["node_modules"], 9 | "references": [{ "path": "../utils" }] 10 | } 11 | -------------------------------------------------------------------------------- /packages/side-sheet/README.md: -------------------------------------------------------------------------------- 1 | 2 | # @equinor/fusion-react-side-sheet 3 | 4 | [![Published on npm](https://img.shields.io/npm/v/@equinor/fusion-react-side-sheet.svg)](https://www.npmjs.com/package/@equinor/fusion-react-side-sheet) 5 | 6 | ## Storybook 7 | 8 | [Storybook](https://equinor.github.io/fusion-react-components/?path=/docs/examples-side-sheet--component) 9 | 10 | 11 | ## Installation 12 | 13 | ```sh 14 | npm install @equinor/fusion-react-side-sheet 15 | ``` 16 | 17 | ## Children 18 | | Name | Props | Description 19 | | ---- | ---- | ----------- 20 | | `Title` | title | Required child for displaying side sheet title. 21 | | `SubTile` | subTitle | ... 22 | | `Actions`| sideSheetRef | ... 23 | | `Indicator` | color | ... 24 | | `Content` | ReactChildElements| ... 25 | 26 | ## Properties/Attributes 27 | | Name | Type | Default | Description 28 | | ---- | ---- | ------- | ----------- 29 | | `enableFullScreen` | `boolean` | `true` | prop for disabling fullScreen action. 30 | | `minWidth` | `number` | `500` | Min with of the side-sheet. 31 | | `onClose` | `function` | - | Callback function triggered onClose button clicked. 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /packages/side-sheet/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-side-sheet", 3 | "version": "1.3.8", 4 | "description": "React component for displaying a side-sheet", 5 | "keywords": ["side-sheet", "react", "fusion", "equinor"], 6 | "homepage": "https://github.com/equinor/fusion-react-components/tree/master/packages/side-sheet#readme", 7 | "license": "ISC", 8 | "main": "dist/index.js", 9 | "directories": { 10 | "dist": "dist" 11 | }, 12 | "files": ["dist"], 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/equinor/fusion-react-components.git" 19 | }, 20 | "scripts": { 21 | "build": "tsc -b", 22 | "lint": "eslint --ext .ts,.tsx,.js src/", 23 | "lint:fix": "eslint --ext .ts,.tsx,.js src/ --fix", 24 | "prepack": "npm run build" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/equinor/fusion-react-components/issues" 28 | }, 29 | "dependencies": { 30 | "re-resizable": "^6.9.11", 31 | "react-keyed-flatten-children": "^5.0.0" 32 | }, 33 | "devDependencies": { 34 | "@equinor/eds-core-react": "^0.46.0", 35 | "@equinor/eds-icons": "~0.22.0", 36 | "@equinor/eds-tokens": "^0.9.2", 37 | "@types/react": "^18.2.29", 38 | "@types/styled-components": "^5.1.29", 39 | "react": "^18.2.0", 40 | "react-dom": "^18.2.0", 41 | "styled-components": "^6.1.0", 42 | "typescript": "^5.2.2" 43 | }, 44 | "peerDependencies": { 45 | "@equinor/eds-core-react": "^0.45.0", 46 | "@equinor/eds-icons": "~0.22.0", 47 | "@equinor/eds-tokens": "^0.9.2", 48 | "react": "^17.0.0 || ^18.0.0", 49 | "styled-components": "^6.1.0" 50 | }, 51 | "peerDependenciesMeta": { 52 | "@types/react": { 53 | "optional": true 54 | }, 55 | "react-dom": { 56 | "optional": true 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/side-sheet/src/components/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equinor/fusion-react-components/63b7e92773724788ecc17b5a87e8c49f5bee30cb/packages/side-sheet/src/components/.DS_Store -------------------------------------------------------------------------------- /packages/side-sheet/src/components/Actions.tsx: -------------------------------------------------------------------------------- 1 | import React, { type PropsWithChildren } from 'react'; 2 | 3 | export type ActionsProps = { 4 | readonly sideSheetRef?: React.RefObject; 5 | }; 6 | 7 | export const Actions = ({ children, sideSheetRef }: PropsWithChildren) => { 8 | const childrenWithProps = React.Children.map(children, (child) => { 9 | if (React.isValidElement(child)) { 10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 11 | return React.cloneElement(child as React.ReactElement, { sideSheetRef }); 12 | } 13 | return child; 14 | }); 15 | return <>{childrenWithProps}; 16 | }; 17 | 18 | export default Actions; 19 | -------------------------------------------------------------------------------- /packages/side-sheet/src/components/Content.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const StyledContentContainer = styled.div` 5 | overflow-y: auto; 6 | height: calc(100vh - 7rem); 7 | padding: 1rem; 8 | `; 9 | 10 | export const Content = ({ children }: PropsWithChildren) => { 11 | return {children}; 12 | }; 13 | -------------------------------------------------------------------------------- /packages/side-sheet/src/components/Indicator.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const StyledIndicator = styled.span<{ color?: HEXString }>` 4 | background-color: ${({ color }) => color || 'red'}; 5 | height: 3rem; 6 | width: 8px; 7 | margin-right: 1rem; 8 | border-radius: 2px; 9 | `; 10 | 11 | export const Indicator = ({ color }: { readonly color?: HEXString }) => { 12 | return ; 13 | }; 14 | 15 | export type HEXString = `#${string}`; 16 | -------------------------------------------------------------------------------- /packages/side-sheet/src/components/SubTitle.tsx: -------------------------------------------------------------------------------- 1 | import { Typography } from '@equinor/eds-core-react'; 2 | 3 | type SubTitleProps = { 4 | readonly subTitle: string; 5 | }; 6 | 7 | export const SubTitle = ({ subTitle }: SubTitleProps) => { 8 | return {subTitle}; 9 | }; 10 | -------------------------------------------------------------------------------- /packages/side-sheet/src/components/Title.tsx: -------------------------------------------------------------------------------- 1 | import { Typography } from '@equinor/eds-core-react'; 2 | 3 | type TitleProps = { 4 | readonly title: string; 5 | }; 6 | 7 | export const Title = ({ title }: TitleProps) => { 8 | return {title}; 9 | }; 10 | -------------------------------------------------------------------------------- /packages/side-sheet/src/components/icon/FullscreenIcon.tsx: -------------------------------------------------------------------------------- 1 | import { Icon } from '@equinor/eds-core-react'; 2 | import { fullscreen, fullscreen_exit } from '@equinor/eds-icons'; 3 | import { useEffect, useState } from 'react'; 4 | 5 | Icon.add({ 6 | fullscreen, 7 | fullscreen_exit, 8 | }); 9 | 10 | export const FullscreenIcon = () => { 11 | const [isFullscreen, setIsFullscreen] = useState(false); 12 | 13 | useEffect(() => { 14 | const handleSetIsFullScreen = () => setIsFullscreen(!!document.fullscreenElement); 15 | document.addEventListener('fullscreenchange', handleSetIsFullScreen); 16 | return () => document.removeEventListener('fullscreenchange', handleSetIsFullScreen); 17 | }, []); 18 | return ; 19 | }; 20 | -------------------------------------------------------------------------------- /packages/side-sheet/src/components/icon/HandlerIcon.tsx: -------------------------------------------------------------------------------- 1 | export const HandlerIcon = () => ( 2 | 3 | Handler Icon 4 | 12 | 20 | 21 | ); 22 | export default HandlerIcon; 23 | -------------------------------------------------------------------------------- /packages/side-sheet/src/components/icon/index.ts: -------------------------------------------------------------------------------- 1 | export { FullscreenIcon } from './FullscreenIcon'; 2 | export { HandlerIcon } from './HandlerIcon'; 3 | -------------------------------------------------------------------------------- /packages/side-sheet/src/index.ts: -------------------------------------------------------------------------------- 1 | export { SideSheet, default } from './components/SideSheet'; 2 | export type { HEXString } from './components/Indicator'; 3 | -------------------------------------------------------------------------------- /packages/side-sheet/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts", "src/**/*.tsx"], 8 | "exclude": ["node_modules"], 9 | "references": [{ "path": "../utils" }] 10 | } 11 | -------------------------------------------------------------------------------- /packages/skeleton/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # @equinor/fusion-react-skeleton 4 | 5 | [![Published on npm](https://img.shields.io/npm/v/@equinor/fusion-react-skeleton.svg)](https://www.npmjs.com/package/@equinor/fusion-react-skeleton) 6 | 7 | [Storybook](https://equinor.github.io/fusion-react-components/?path=/docs/data-skeleton) 8 | 9 | [Fusion Web Component](https://github.com/equinor/fusion-web-components/tree/main/packages/skeleton) 10 | 11 | ## Installation 12 | 13 | ```sh 14 | npm install @equinor/fusion-react-skeleton 15 | ``` 16 | 17 | ### Description 18 | 19 | Display a placeholder preview of your content before the data gets loaded to reduce load-time frustration. 20 | 21 | ### When To Use 22 | 23 | - When a resource needs a long time to load. 24 | - When content for a resource 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/skeleton/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-skeleton", 3 | "version": "0.3.2", 4 | "description": "React component for displaying a skeleton", 5 | "keywords": ["skeleton", "react", "fusion", "equinor"], 6 | "homepage": "https://github.com/equinor/fusion-react-components/tree/master/packages/skeleton#readme", 7 | "license": "ISC", 8 | "main": "dist/index.js", 9 | "directories": { 10 | "dist": "dist" 11 | }, 12 | "files": ["dist"], 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/equinor/fusion-react-components.git" 19 | }, 20 | "scripts": { 21 | "build": "tsc -b", 22 | "lint": "eslint --ext .ts,.tsx,.js src/", 23 | "lint:fix": "eslint --ext .ts,.tsx,.js src/ --fix", 24 | "prepack": "npm run build" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/equinor/fusion-react-components/issues" 28 | }, 29 | "dependencies": { 30 | "@equinor/fusion-react-utils": "^2.1.0", 31 | "@equinor/fusion-wc-skeleton": "^2.1.2" 32 | }, 33 | "devDependencies": { 34 | "@types/react": "^18.2.24", 35 | "react": "^18.2.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/skeleton/src/Skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentProps, createComponent } from '@equinor/fusion-react-utils'; 2 | 3 | import HTMLSkeletonCustomElement, { 4 | tag, 5 | SkeletonVariant, 6 | SkeletonSize, 7 | } from '@equinor/fusion-wc-skeleton'; 8 | 9 | type ElementProps = React.PropsWithChildren< 10 | Partial> 11 | >; 12 | 13 | export const Skeleton = createComponent( 14 | HTMLSkeletonCustomElement, 15 | tag, 16 | ); 17 | 18 | export type SkeletonProps = ComponentProps; 19 | 20 | export { HTMLSkeletonCustomElement, SkeletonVariant, SkeletonSize }; 21 | 22 | export default Skeleton; 23 | -------------------------------------------------------------------------------- /packages/skeleton/src/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './Skeleton'; 2 | 3 | export { default } from './Skeleton'; 4 | -------------------------------------------------------------------------------- /packages/skeleton/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts", "src/**/*.tsx"], 8 | "exclude": ["node_modules"], 9 | "references": [{ "path": "../utils" }] 10 | } 11 | -------------------------------------------------------------------------------- /packages/stepper/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-stepper", 3 | "version": "2.0.1", 4 | "description": "Component for displaying and using stepps in stepper", 5 | "keywords": ["stepper", "stepp", "react", "fusion", "equinor"], 6 | "homepage": "https://github.com/equinor/fusion-react-components/tree/master/packages/stepper#readme", 7 | "license": "ISC", 8 | "main": "dist/index.js", 9 | "directories": { 10 | "dist": "dist" 11 | }, 12 | "files": ["dist"], 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/equinor/fusion-react-components.git" 19 | }, 20 | "scripts": { 21 | "build": "tsc -b", 22 | "lint": "eslint --ext .ts,.tsx,.js src/", 23 | "lint:fix": "eslint --ext .ts,.tsx,.js src/ --fix", 24 | "prepack": "npm run build" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/equinor/fusion-react-components/issues" 28 | }, 29 | "dependencies": { 30 | "@equinor/eds-icons": "~0.22.0", 31 | "@equinor/eds-tokens": "^0.9.2", 32 | "styled-components": "^6.1.8" 33 | }, 34 | "devDependencies": { 35 | "@equinor/eds-core-react": "^0.46.0", 36 | "@types/react": "^18.2.55", 37 | "react": "^18.2.0" 38 | }, 39 | "peerDependencies": { 40 | "@equinor/eds-core-react": "^0.45.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/stepper/src/StepBadge.tsx: -------------------------------------------------------------------------------- 1 | import { Icon } from '@equinor/eds-core-react'; 2 | import { done as doneStepIcon } from '@equinor/eds-icons'; 3 | import styled, { css } from 'styled-components'; 4 | import { tokens } from '@equinor/eds-tokens'; 5 | 6 | const Styled = { 7 | Badge: styled.div<{ $active?: boolean; $done?: boolean }>` 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | width: var(--badge-size, 21px); 12 | height: var(--badge-size, 21px); 13 | ${({ $active, $done }) => { 14 | switch (true) { 15 | case $active: 16 | return css` 17 | border: 1px solid ${tokens.colors.interactive.primary__resting.hex}; 18 | background-color: ${tokens.colors.interactive.primary__resting.hex}; 19 | color: ${tokens.colors.text.static_icons__primary_white.hex}; 20 | `; 21 | case $done: 22 | return css` 23 | border: 1px solid ${tokens.colors.interactive.primary__resting.hex}; 24 | color: ${tokens.colors.interactive.primary__resting.hex}; 25 | `; 26 | default: 27 | return css` 28 | border: 1px solid ${tokens.colors.interactive.disabled__border.hex}; 29 | color: ${tokens.colors.text.static_icons__default.hex}; 30 | `; 31 | } 32 | }} 33 | font-size: calc(${tokens.typography.paragraph.caption.fontSize} * var(--content-resize, 1)); 34 | font-weight: 400; 35 | line-height: 1.2; 36 | border-radius: 50%; 37 | `, 38 | }; 39 | 40 | export type BadgeProps = { 41 | readonly position?: number; 42 | readonly active: boolean; 43 | readonly done?: boolean; 44 | }; 45 | 46 | export const Badge = ({ position, active, done }: BadgeProps): JSX.Element => { 47 | return ( 48 | 49 | {done ? : position} 50 | 51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /packages/stepper/src/StepContent.tsx: -------------------------------------------------------------------------------- 1 | import { Children, type ReactElement, cloneElement, type PropsWithChildren } from 'react'; 2 | 3 | /** Define the props interface for StepContent component */ 4 | type StepContentProps = { 5 | readonly activeStepKey: string | null; 6 | }; 7 | 8 | const StepContent = ({ 9 | children, 10 | activeStepKey, 11 | }: PropsWithChildren): JSX.Element => { 12 | /** Find the active step based on the activeStepKey */ 13 | const active = Children.toArray(children).find( 14 | (child) => (child as ReactElement).props.stepKey === activeStepKey, 15 | ) as ReactElement; 16 | 17 | if (!active) { 18 | return <>; 19 | } 20 | 21 | /** Clone and map the children of the active step */ 22 | const clonedChildren = Children.map(active.props.children, (child) => cloneElement(child)); 23 | 24 | /** Return a Fragment containing the cloned children of the active step */ 25 | return <>{clonedChildren}; 26 | }; 27 | 28 | export default StepContent; 29 | -------------------------------------------------------------------------------- /packages/stepper/src/index.tsx: -------------------------------------------------------------------------------- 1 | export { Step, StepProps } from './Step'; 2 | export type { BadgeProps } from './StepBadge'; 3 | export { Stepper } from './Stepper'; 4 | export type { StepKey, StepKeys, StepperProps } from './Stepper'; 5 | -------------------------------------------------------------------------------- /packages/stepper/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts", "src/**/*.tsx"], 8 | "exclude": ["node_modules"], 9 | "references": [ 10 | { 11 | "path": "../styles" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/styles/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-styles", 3 | "version": "0.6.4", 4 | "description": "style lib inspired by @material-ui/styles", 5 | "keywords": ["styling", "react", "fusion", "equinor"], 6 | "author": "odin thomas rochmann ", 7 | "homepage": "https://github.com/equinor/fusion-react-components/tree/master/packages/styles#readme", 8 | "license": "ISC", 9 | "main": "dist/index.js", 10 | "directories": { 11 | "dist": "dist" 12 | }, 13 | "files": ["dist"], 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/equinor/fusion-react-components.git", 20 | "directory": "packages/styles" 21 | }, 22 | "scripts": { 23 | "build": "tsc -b", 24 | "lint": "eslint --ext .ts,.tsx,.js src/", 25 | "lint:fix": "eslint --ext .ts,.tsx,.js src/ --fix", 26 | "prepack": "npm run build" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/equinor/fusion-react-components/issues" 30 | }, 31 | "dependencies": { 32 | "@equinor/fusion-wc-theme": "^1.1.2", 33 | "@equinor/fusion-web-theme": "^0.1.10", 34 | "@material-ui/styles": "^4.11.4", 35 | "clsx": "^2.1.0" 36 | }, 37 | "peerDependencies": { 38 | "react": "^17.0.0 || ^18.0.0", 39 | "react-dom": "^17.0.0 || ^18.0.0" 40 | }, 41 | "peerDependenciesMeta": { 42 | "@types/react": { 43 | "optional": true 44 | }, 45 | "react-dom": { 46 | "optional": true 47 | } 48 | }, 49 | "devDependencies": { 50 | "@types/react": "^18.2.24", 51 | "react": "^18.2.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/styles/src/StyleProvider.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createGenerateClassName, 3 | StylesProvider as BaseStylesProvider, 4 | type StylesProviderProps, 5 | } from '@material-ui/styles'; 6 | 7 | import Element from '@equinor/fusion-wc-theme'; 8 | Element; 9 | 10 | export const StylesProvider = ( 11 | props: StylesProviderProps & { 12 | readonly seed?: string; 13 | }, 14 | ): JSX.Element => { 15 | const { children, seed, ...args } = props; 16 | if (seed && !args.generateClassName) { 17 | args.generateClassName = createGenerateClassName({ seed }); 18 | } 19 | return {children}; 20 | }; 21 | 22 | export default StylesProvider; 23 | -------------------------------------------------------------------------------- /packages/styles/src/ThemeProvider.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider as BaseThemeProvider, type ThemeProviderProps } from '@material-ui/styles'; 2 | import { styles as defaultTheme } from '@equinor/fusion-web-theme'; 3 | 4 | import Element from '@equinor/fusion-wc-theme'; 5 | Element; 6 | 7 | export const ThemeProvider = (props: ThemeProviderProps): JSX.Element => { 8 | const { children, ...args } = props; 9 | return ( 10 | 11 | {children} 12 | 13 | ); 14 | }; 15 | 16 | export default ThemeProvider; 17 | -------------------------------------------------------------------------------- /packages/styles/src/create-styles.ts: -------------------------------------------------------------------------------- 1 | import type { StyleRules } from './types'; 2 | 3 | // eslint-disable-next-line @typescript-eslint/ban-types 4 | export const createStyles = ( 5 | styles?: StyleRules, 6 | ): StyleRules | never => styles as unknown as StyleRules; 7 | -------------------------------------------------------------------------------- /packages/styles/src/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace JSX { 2 | interface IntrinsicElements { 3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 4 | 'fwc-theme': any; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/styles/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './make-styles'; 2 | export * from './create-styles'; 3 | export * from './theme'; 4 | 5 | export { withStyles, Styles, createGenerateClassName, ClassNameMap } from '@material-ui/styles'; 6 | 7 | export { default as clsx } from 'clsx'; 8 | export { StylesProvider } from './StyleProvider'; 9 | export { ThemeProvider } from './ThemeProvider'; 10 | 11 | export type { StyleRules, StyleCSSProperties } from './types'; 12 | -------------------------------------------------------------------------------- /packages/styles/src/make-styles.ts: -------------------------------------------------------------------------------- 1 | import { type ClassNameMap, makeStyles as makeStylesWithoutDefault } from '@material-ui/styles'; 2 | 3 | import { type FusionTheme, theme as defaultTheme } from './theme'; 4 | import type { Styles } from './types'; 5 | 6 | export const makeStyles = < 7 | Theme = FusionTheme, 8 | Props extends Record = Record, 9 | ClassKey extends string = string, 10 | >( 11 | stylesOrCreator: Styles, 12 | options = {}, 13 | ): keyof Props extends never 14 | ? (props?: Props) => ClassNameMap 15 | : (props: Props) => ClassNameMap => 16 | // TODO 17 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 18 | // @ts-ignore 19 | makeStylesWithoutDefault(stylesOrCreator, { 20 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 21 | // @ts-ignore 22 | defaultTheme, 23 | ...options, 24 | }); 25 | 26 | export default makeStyles; 27 | -------------------------------------------------------------------------------- /packages/styles/src/theme.ts: -------------------------------------------------------------------------------- 1 | import { styles as theme } from '@equinor/fusion-web-theme'; 2 | export type FusionTheme = typeof theme; 3 | export { theme }; 4 | -------------------------------------------------------------------------------- /packages/styles/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts", "src/**/*.tsx"], 8 | "exclude": ["node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/textarea/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-textarea", 3 | "version": "0.6.4", 4 | "description": "component for displaying text area", 5 | "keywords": ["text area", "react", "fusion", "equinor"], 6 | "homepage": "https://github.com/equinor/fusion-react-components/tree/master/packages/textarea#readme", 7 | "license": "ISC", 8 | "main": "dist/index.js", 9 | "directories": { 10 | "dist": "dist" 11 | }, 12 | "files": ["dist"], 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/equinor/fusion-react-components.git" 19 | }, 20 | "scripts": { 21 | "build": "tsc -b", 22 | "lint": "eslint --ext .ts,.tsx,.js src/", 23 | "lint:fix": "eslint --ext .ts,.tsx,.js src/ --fix", 24 | "prepack": "npm run build" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/equinor/fusion-react-components/issues" 28 | }, 29 | "dependencies": { 30 | "@equinor/fusion-react-utils": "^2.1.0", 31 | "@equinor/fusion-wc-textarea": "^1.1.4" 32 | }, 33 | "devDependencies": { 34 | "@types/react": "^18.2.24", 35 | "react": "^18.2.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/textarea/src/TextArea.tsx: -------------------------------------------------------------------------------- 1 | import { tag, TextAreaElement } from '@equinor/fusion-wc-textarea'; 2 | import type { TextAreaInvalidHandler } from './types'; 3 | 4 | import { type ComponentProps, createComponent } from '@equinor/fusion-react-utils'; 5 | 6 | type ElementFunctions = Partial>; 7 | 8 | type ElementAttributes = Partial< 9 | Pick< 10 | TextAreaElement, 11 | | 'cols' 12 | | 'rows' 13 | // 14 | | 'autoValidate' 15 | | 'disabled' 16 | | 'errorMessage' 17 | | 'helper' 18 | | 'helperPersistent' 19 | | 'icon' 20 | | 'iconTrailing' 21 | | 'label' 22 | | 'max' 23 | | 'maxLength' 24 | | 'minLength' 25 | | 'name' 26 | | 'pattern' 27 | | 'placeholder' 28 | | 'prefix' 29 | | 'required' 30 | | 'size' 31 | | 'step' 32 | | 'suffix' 33 | | 'value' 34 | | 'validationMessage' 35 | | 'charCounter' 36 | > 37 | >; 38 | 39 | type ElementEvents = { 40 | onInvalid?: TextAreaInvalidHandler; 41 | }; 42 | 43 | type ElementProps = ElementAttributes & ElementFunctions & ElementEvents; 44 | 45 | export const TextInput = createComponent(TextAreaElement, tag, { 46 | events: { onInvalid: 'invalid' }, 47 | functions: new Set(['validityTransform']), 48 | }); 49 | 50 | export type TextInputProps = ComponentProps; 51 | 52 | export default TextInput; 53 | -------------------------------------------------------------------------------- /packages/textarea/src/index.tsx: -------------------------------------------------------------------------------- 1 | export { TextAreaElement as HTMLTextAreaCustomElement } from '@equinor/fusion-wc-textarea'; 2 | 3 | export * from './TextArea'; 4 | export * from './types'; 5 | 6 | export { default } from './TextArea'; 7 | -------------------------------------------------------------------------------- /packages/textarea/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { TextAreaElement } from '@equinor/fusion-wc-textarea'; 2 | 3 | // #region Events 4 | 5 | export type TextAreaInvalidFn = (validity: ValidityState, el: TextAreaElement) => void; 6 | export type TextAreaInvalidEvent = React.InvalidEvent; 7 | export type TextAreaInvalidHandler = React.EventHandler; 8 | 9 | // #endregion 10 | -------------------------------------------------------------------------------- /packages/textarea/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts", "src/**/*.tsx"], 8 | "exclude": ["node_modules"], 9 | "references": [ 10 | { 11 | "path": "../utils" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@equinor/fusion-react-utils", 3 | "version": "2.2.2", 4 | "description": "Helper and util functions for React", 5 | "keywords": ["utils", "react", "fusion", "equinor"], 6 | "homepage": "https://github.com/equinor/fusion-react-components/tree/master/packages/switch#readme", 7 | "license": "ISC", 8 | "main": "dist/esm/index.js", 9 | "exports": { 10 | ".": "./dist/esm/index.js" 11 | }, 12 | "types": "./dist/types/index.d.ts", 13 | "directories": { 14 | "dist": "dist" 15 | }, 16 | "files": ["dist"], 17 | "publishConfig": { 18 | "access": "public" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/equinor/fusion-react-components.git", 23 | "directory": "packages/util" 24 | }, 25 | "scripts": { 26 | "build": "tsc -b", 27 | "lint": "eslint --ext .ts,.tsx,.js src/", 28 | "lint:fix": "eslint --ext .ts,.tsx,.js src/ --fix", 29 | "prepack": "npm run build" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/equinor/fusion-react-components/issues" 33 | }, 34 | "dependencies": { 35 | "date-fns": "^4.1.0" 36 | }, 37 | "devDependencies": { 38 | "@types/react": "^18.2.24", 39 | "react": "^18.2.0" 40 | }, 41 | "peerDependencies": { 42 | "react": "^17.0.0 || ^18.0.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/utils/src/create-synthetic-event.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Create a wrapper event for React 3 | * @param event Event - native/custom event 4 | */ 5 | export const createSyntheticEvent = ( 6 | event: E, 7 | ): React.SyntheticEvent => { 8 | let isPropagationStopped = false; 9 | return { 10 | nativeEvent: event, 11 | currentTarget: event.currentTarget as EventTarget & T, 12 | target: event.target as EventTarget & T, 13 | bubbles: event.bubbles, 14 | cancelable: event.cancelable, 15 | defaultPrevented: event.defaultPrevented, 16 | eventPhase: event.eventPhase, 17 | isTrusted: event.isTrusted, 18 | preventDefault: () => event.preventDefault(), 19 | isDefaultPrevented: () => event.defaultPrevented, 20 | stopPropagation: () => { 21 | isPropagationStopped = true; 22 | event.stopPropagation(); 23 | }, 24 | isPropagationStopped: () => isPropagationStopped, 25 | persist: () => undefined, 26 | timeStamp: event.timeStamp, 27 | type: event.type, 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /packages/utils/src/element-attributes.ts: -------------------------------------------------------------------------------- 1 | import { formatISO } from 'date-fns'; 2 | 3 | // eslint-disable-next-line @typescript-eslint/ban-types 4 | const objectToString = (object: Object) => { 5 | switch (object.constructor) { 6 | case Date: 7 | return formatISO(object as Date); 8 | default: 9 | return JSON.stringify(object); 10 | } 11 | }; 12 | 13 | /** 14 | * Utility to remove empty attributes from properties, JSX does not comply with the W3 standard 15 | * [W3](https://www.w3.org/TR/2008/WD-html5-20080610/semantics.html#boolean) 16 | */ 17 | export const elementAttributes = < 18 | T extends Partial> = Record, 19 | >( 20 | props: Partial, 21 | ): T => { 22 | return Object.keys(props).reduce((cur, key) => { 23 | const value = props[key as keyof T]; 24 | switch (typeof value) { 25 | case 'string': 26 | return Object.assign(cur, { [key]: value }); 27 | case 'object': 28 | // eslint-disable-next-line @typescript-eslint/ban-types 29 | return Object.assign(cur, { 30 | [key]: key === 'style' ? value : objectToString(value as Object), 31 | }); 32 | default: 33 | return value ? Object.assign(cur, { [key]: value }) : cur; 34 | } 35 | }, {} as T); 36 | }; 37 | 38 | export default elementAttributes; 39 | -------------------------------------------------------------------------------- /packages/utils/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useElementProps'; 2 | export * from './useElementEvents'; 3 | export * from './useElementFunctions'; 4 | export * from './useForwardRef'; 5 | -------------------------------------------------------------------------------- /packages/utils/src/hooks/useElementFunctions.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { useLayoutEffect, useMemo, useRef } from 'react'; 3 | import type { RefObject } from 'react'; 4 | 5 | import { shallowEqual } from '../shallow-equal'; 6 | 7 | const noFns = {}; 8 | 9 | /** 10 | * Works lie `useProps` but will check if prop/function has changed before assignment 11 | * @param ref 12 | * @param functions 13 | * @param functionMap 14 | */ 15 | export const useElementFunctions = < 16 | E extends HTMLElement, 17 | EKey extends string = Extract, 18 | >( 19 | ref: RefObject, 20 | // biome-ignore lint/suspicious/noExplicitAny: 21 | functions?: Partial>, 22 | functionMap?: Set, 23 | ): void => { 24 | // biome-ignore lint/suspicious/noExplicitAny: 25 | type ERecord = Record; 26 | const fnsRef = useRef({} as ERecord); 27 | 28 | const fns = useMemo(() => { 29 | /** early escaping no-op */ 30 | if (!functions || !functionMap) return noFns as ERecord; 31 | 32 | // extract allowed functions 33 | const fns = [...functionMap.values()] 34 | .filter((k) => k in functions) 35 | .reduce((c, v) => Object.assign(c, { [v]: functions[v as EKey] }), {} as ERecord); 36 | 37 | /** compare functions with existing */ 38 | const hasChanged = !shallowEqual(fnsRef.current, fns); 39 | return hasChanged ? fns : fnsRef.current; 40 | }, [functions, functionMap]); 41 | 42 | useLayoutEffect(() => { 43 | const obj = ref.current; 44 | if (!obj || !fns) return; 45 | 46 | for (const fnKey in fns) { 47 | obj[fnKey as keyof E] = fns[fnKey]; 48 | } 49 | fnsRef.current = fns; 50 | }, [ref, fns]); 51 | }; 52 | -------------------------------------------------------------------------------- /packages/utils/src/hooks/useElementProps.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { useLayoutEffect } from 'react'; 3 | import type { RefObject } from 'react'; 4 | 5 | type Constructor = { new (): T }; 6 | 7 | const reservedReactProperties = new Set(['children', 'localName', 'ref', 'style', 'className']); 8 | 9 | /** 10 | * Extract property names of `custom element` 11 | * NOTE: does not extract events and functions 12 | */ 13 | export const extractElementProps = ( 14 | elementClass: Constructor, 15 | ): Set => { 16 | const elementClassProps = new Set(); 17 | for (const p in elementClass.prototype) { 18 | if (!(p in HTMLElement.prototype)) { 19 | if (reservedReactProperties.has(p)) { 20 | console.warn( 21 | `${elementClass.name} contains property ${p} which is a React reserved property. It will be used by React and not set on the element.`, 22 | ); 23 | } else { 24 | elementClassProps.add(p as keyof E); 25 | } 26 | } 27 | } 28 | return elementClassProps; 29 | }; 30 | 31 | /** 32 | * React will try to `toString` all arguments that are provided. 33 | * This hook will set the property/function to the referenced element programmatically. 34 | */ 35 | export const useElementProps = ( 36 | ref: RefObject, 37 | // biome-ignore lint/suspicious/noExplicitAny: 38 | props?: Partial>, 39 | propMap?: Set, 40 | ): void => { 41 | useLayoutEffect(() => { 42 | const el = ref.current; 43 | if (el && propMap && props) { 44 | const elementProps = Object.entries(props).filter(([k]) => propMap.has(k as keyof E)); 45 | for (const [k, v] of elementProps) { 46 | el[k as keyof E] = v; 47 | } 48 | } 49 | }, [ref, propMap, props]); 50 | }; 51 | -------------------------------------------------------------------------------- /packages/utils/src/hooks/useForwardRef.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect, useRef } from 'react'; 2 | 3 | import type { ForwardedRef, RefObject } from 'react'; 4 | 5 | /** 6 | * Simple wrapper for sharing a ref when creating element with `React.forwardRef` 7 | */ 8 | export const useForwardRef = ( 9 | forwardRef?: ForwardedRef, 10 | initial: E | null = null, 11 | ): RefObject => { 12 | const ref = useRef(initial); 13 | useLayoutEffect(() => { 14 | if (typeof forwardRef === 'function') { 15 | (forwardRef as (e: E | null) => void)(ref.current); 16 | } else if (forwardRef) { 17 | (forwardRef as { current: E | null }).current = ref.current; 18 | } 19 | }, [forwardRef]); 20 | return ref; 21 | }; 22 | 23 | export default useForwardRef; 24 | -------------------------------------------------------------------------------- /packages/utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './element-attributes'; 2 | export * from './create-element'; 3 | export * from './create-synthetic-event'; 4 | export * from './hooks'; 5 | export * from './shallow-equal'; 6 | -------------------------------------------------------------------------------- /packages/utils/src/shallow-equal.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Shallow compare of two objects 3 | * @returns boolean - true if objects are the same false if difference 4 | */ 5 | export const shallowEqual = >( 6 | a: Partial, 7 | b: Partial, 8 | ): boolean => 9 | Object.keys(a).length === Object.keys(b).length && 10 | Object.keys(a).every((key) => a[key] === b[key]); 11 | -------------------------------------------------------------------------------- /packages/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist/esm", 6 | "declarationDir": "./dist/types" 7 | }, 8 | "include": ["src/**/*.ts"], 9 | "exclude": ["node_modules"] 10 | } 11 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "packages/*" 3 | - "storybook" -------------------------------------------------------------------------------- /storybook/.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceType": "unambiguous", 3 | "presets": [ 4 | [ 5 | "@babel/preset-env", 6 | { 7 | "targets": { 8 | "chrome": 100, 9 | "safari": 15, 10 | "firefox": 91 11 | } 12 | } 13 | ], 14 | "@babel/preset-typescript", 15 | "@babel/preset-react" 16 | ], 17 | "plugins": [] 18 | } -------------------------------------------------------------------------------- /storybook/.gitignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /storybook/.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from '@storybook/react-vite'; 2 | import path from 'path'; 3 | import { mergeConfig } from 'vite'; 4 | import tsconfigPaths from 'vite-tsconfig-paths'; 5 | 6 | const repoRoot = path.resolve(__dirname, '../..'); 7 | 8 | const config: StorybookConfig = { 9 | stories: [ 10 | '../src/**/*.mdx', 11 | '../src/stories/**/*.stories.@(js|jsx|tsx|ts)', 12 | ], 13 | framework: '@storybook/react-vite', 14 | async viteFinal(config, _options){ 15 | 16 | return mergeConfig(config, { 17 | resolve: { 18 | alias: [ 19 | { 20 | find: '#packages', 21 | replacement: path.resolve(repoRoot, 'packages'), 22 | }, 23 | ] 24 | }, 25 | optimizeDeps: { 26 | exclude: ['@equinor/fusion-react-*'] 27 | }, 28 | plugins: [ 29 | tsconfigPaths({ 30 | root: '../../tsconfig.json', 31 | ignoreConfigErrors: true, 32 | parseNative: false, 33 | loose: true, 34 | }), 35 | ], 36 | }) 37 | }, 38 | 39 | typescript: { 40 | check: false, 41 | reactDocgen: 'react-docgen-typescript', 42 | reactDocgenTypescriptOptions: { 43 | shouldExtractLiteralValuesFromEnum: true, 44 | propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true), 45 | }, 46 | }, 47 | 48 | addons: [ 49 | '@storybook/addon-essentials', 50 | '@storybook/addon-docs', 51 | '@storybook/addon-storysource', 52 | ], 53 | 54 | docs: {} 55 | }; 56 | 57 | export default config; 58 | -------------------------------------------------------------------------------- /storybook/.storybook/manager.ts: -------------------------------------------------------------------------------- 1 | import { addons } from '@storybook/manager-api'; 2 | import { create } from '@storybook/theming'; 3 | 4 | 5 | const theme = create({ 6 | base: 'light', 7 | brandTitle: 'Fusion React Components', 8 | brandUrl: 'https://github.com/equinor/fusion-react-components', 9 | colorPrimary: '#FF1243', 10 | colorSecondary: '#007079', 11 | fontBase: 'Equinor, sans-serif', 12 | fontCode: '"Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace' 13 | }); 14 | 15 | addons.setConfig({theme}); 16 | -------------------------------------------------------------------------------- /storybook/.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /storybook/.storybook/preview.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { type Preview } from '@storybook/react'; 4 | 5 | import {Theme} from '../src/components/Theme' 6 | 7 | import './theme.css'; 8 | 9 | const _customElementsDefine = window.customElements.define; 10 | window.customElements.define = (name, cl, conf) => { 11 | if (!customElements.get(name)) { 12 | _customElementsDefine.call(window.customElements, name, cl, conf); 13 | } 14 | else { 15 | console.debug(`${name} has been defined twice`); 16 | } 17 | }; 18 | 19 | const preview: Preview = { 20 | parameters: { 21 | controls: { 22 | matchers: { 23 | color: /(background|color)$/i, 24 | date: /Date$/, 25 | }, 26 | }, 27 | options: { 28 | storySort: { 29 | order: ['Docs', 'documentation', '*'], 30 | }, 31 | }, 32 | docs: { 33 | source: { 34 | language: 'tsx' 35 | }, 36 | }, 37 | }, 38 | 39 | decorators: [ 40 | (Story) => ( 41 | 42 | 43 | 44 | ), 45 | ], 46 | tags: ['autodocs'], 47 | }; 48 | 49 | export default preview; 50 | -------------------------------------------------------------------------------- /storybook/.storybook/theme.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: 'Equinor'; 3 | --mdc-typography-font-family: 'Equinor'; 4 | } 5 | 6 | blockquote { 7 | background: rgba(0,127,255,.1); 8 | color: #333; 9 | margin: 1rem 0; 10 | padding: 0.5rem 2rem; 11 | border-radius: .5rem; 12 | box-shadow: 1px 1px 3px 1px rgba(0, 0, 0, .1); 13 | min-height: 3rem; 14 | display: flex; 15 | flex-flow: row; 16 | align-items: center; 17 | border-left: none !important; 18 | } 19 | 20 | code { 21 | font-family: "Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace; 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | font-size: 13px; 25 | } 26 | 27 | p code { 28 | display: inline-block; 29 | padding-left: 2px; 30 | padding-right: 2px; 31 | vertical-align: baseline; 32 | color: inherit; 33 | line-height: 1; 34 | margin: 0 2px; 35 | padding: 3px 5px; 36 | white-space: nowrap; 37 | border-radius: 3px; 38 | border: 1px solid #EEEEEE; 39 | color: rgba(51,51,51,0.9); 40 | background-color: #F8F8F8; 41 | } 42 | 43 | .docblock-argstable-body code{ 44 | white-space: pre !important; 45 | } -------------------------------------------------------------------------------- /storybook/src/components/ChangeLog/ChangeLog.style.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles, createStyles } from '@equinor/fusion-react-styles'; 2 | 3 | export const useChangeLogStyles = makeStyles( 4 | createStyles({ 5 | header: { 6 | fontSize: 28, 7 | display: 'flex', 8 | alignItems: 'center', 9 | justifyContent: 'center', 10 | background: 'rgba(0,112,255,.05)', 11 | boxShadow: '2px 5px 7px 0px rgb(0 0 0 / 15%)', 12 | margin: '2rem 0', 13 | '& > svg': { 14 | height: '2em', 15 | }, 16 | }, 17 | changelog: { 18 | '& h1': { 19 | fontSize: 18, 20 | }, 21 | '& h2': { 22 | fontSize: 16, 23 | }, 24 | '& h3': { 25 | fontSize: 14, 26 | margin: '8px 0px', 27 | }, 28 | '& #change-log': { 29 | display: 'none', 30 | }, 31 | '& #features + ul > li, & #bug-fixes + ul > li': { 32 | display: 'inline-flex', 33 | }, 34 | 35 | '& #bug-fixes + ul, & #features + ul': { 36 | margin: 0, 37 | padding: 0, 38 | }, 39 | 40 | '& #bug-fixes + ul, & #features + ul': { 41 | display: 'flex', 42 | flexFlow: 'column', 43 | }, 44 | 45 | '& #bug-fixes + ul > li, & #features + ul > li': { 46 | display: 'inline-flex', 47 | }, 48 | 49 | '& #bug-fixes + ul > li::before': { 50 | content: '"🐞"', 51 | display: 'block', 52 | marginRight: '.5em', 53 | }, 54 | 55 | '& #features + ul > li::before': { 56 | content: '"🚀"', 57 | display: 'block', 58 | marginRight: '.5em', 59 | }, 60 | }, 61 | }) 62 | ); 63 | 64 | export default useChangeLogStyles; 65 | -------------------------------------------------------------------------------- /storybook/src/components/ChangeLog/ChangeLog.tsx: -------------------------------------------------------------------------------- 1 | import { Markdown } from '@storybook/blocks'; 2 | 3 | import ReactIcon from './React.svg'; 4 | import LitIcon from './Lit.svg'; 5 | 6 | import useStyle from './ChangeLog.style'; 7 | 8 | type ChangeLogs = { 9 | react: string; 10 | webComponent?: string; 11 | }; 12 | 13 | export const ChangeLog = ({ changelogs }: { readonly changelogs: ChangeLogs }): React.ReactElement => { 14 | const classes = useStyle(); 15 | return ( 16 | <> 17 |

18 | ReactComponent 19 |

20 |
21 | {changelogs.react} 22 |
23 | {changelogs.webComponent && ( 24 | <> 25 |

26 | WebComponent 27 |

28 |
29 | {changelogs.webComponent} 30 |
31 | 32 | )} 33 | 34 | ); 35 | }; 36 | 37 | export default ChangeLog; 38 | -------------------------------------------------------------------------------- /storybook/src/components/ChangeLog/Lit.svg.tsx: -------------------------------------------------------------------------------- 1 | export const LitIcon = () => ( 2 | 3 | 4 | 5 | 6 | 7 | ); 8 | 9 | export default LitIcon; 10 | -------------------------------------------------------------------------------- /storybook/src/components/ChangeLog/index.ts: -------------------------------------------------------------------------------- 1 | export { ChangeLog, ChangeLog as default } from './ChangeLog'; 2 | -------------------------------------------------------------------------------- /storybook/src/components/StoryExample.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | import React from 'react'; 3 | import { Canvas, Story } from '@storybook/addon-docs'; 4 | 5 | type StoryExampleProps = { 6 | storyId: string; 7 | showSource?: boolean; 8 | }; 9 | export const StoryExample = ({ storyId, showSource }: StoryExampleProps): React.ReactElement => { 10 | return ( 11 |
12 | {/** @ts-ignore */} 13 | 14 | 15 | 16 | Show story 17 |
18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /storybook/src/components/Theme.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider, theme } from '@equinor/fusion-react-styles'; 2 | 3 | export const Theme = ({ children }: React.PropsWithChildren>) => ( 4 | {children} 5 | ); 6 | -------------------------------------------------------------------------------- /storybook/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { ChangeLog } from './ChangeLog'; 2 | export { PackageInfo } from './PackageInfo'; 3 | export { StoryExample } from './StoryExample'; 4 | -------------------------------------------------------------------------------- /storybook/src/stories/01-intro.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Markdown, Title } from '@storybook/blocks'; 2 | import readme from '../../../README.md?raw'; 3 | 4 | 5 | 6 | {readme} 7 | -------------------------------------------------------------------------------- /storybook/src/stories/ag-grid/ag-grid-utils.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/react'; 2 | import { 3 | createStatusField, 4 | defaultValueSetter, 5 | defaultValueGetter, 6 | addInitialProps, 7 | AGGridDataStatus, 8 | } from '@equinor/fusion-react-ag-grid-utils'; 9 | 10 | import { AgGridReact } from 'ag-grid-react'; 11 | 12 | import { 13 | ModuleRegistry, 14 | ClientSideRowModelModule, 15 | TextEditorModule, 16 | ClientSideRowModelApiModule, 17 | } from 'ag-grid-community'; 18 | 19 | import { faker } from '@faker-js/faker'; 20 | import { useRef } from 'react'; 21 | 22 | ModuleRegistry.registerModules([ClientSideRowModelModule, TextEditorModule, ClientSideRowModelApiModule]); 23 | 24 | const meta: Meta = { 25 | tags: ['autodocs'], 26 | title: 'ag-grid/utils', 27 | }; 28 | 29 | export default meta; 30 | 31 | faker.seed(0); 32 | 33 | export const ChangeHandler = () => { 34 | const rowdData = Array.from({ length: 10 }, () => 35 | addInitialProps({ 36 | name: faker.person.firstName(), 37 | lastName: faker.person.lastName(), 38 | date: faker.date.past(), 39 | order: String(faker.number.int()), 40 | }), 41 | ); 42 | 43 | const columnDefs = [ 44 | { ...createStatusField() }, 45 | { field: 'name', editable: true }, 46 | { field: 'lastName', editable: true }, 47 | { field: 'date' }, 48 | { field: 'order', editable: true }, 49 | ]; 50 | const gridRef = useRef(null); 51 | 52 | return ( 53 | <> 54 | 59 |
60 | 69 |
70 | 71 | ); 72 | }; 73 | -------------------------------------------------------------------------------- /storybook/src/stories/context-selector/context-selector.mdx: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react'; 2 | 3 | import { Meta, Markdown, Canvas, ArgTypes } from '@storybook/blocks'; 4 | 5 | import readme from '@equinor/fusion-react-context-selector/README.md?raw'; 6 | import changelog from '@equinor/fusion-react-context-selector/CHANGELOG.md?raw'; 7 | import pkg from '@equinor/fusion-react-context-selector/package.json'; 8 | 9 | import {ChangeLog, PackageInfo} from '../../components'; 10 | 11 | import * as stories from './context-selector.stories'; 12 | 13 | 14 | 15 | {readme} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ## ChangeLog 24 | 25 | -------------------------------------------------------------------------------- /storybook/src/stories/context-selector/context-selector.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, StoryObj } from '@storybook/react'; 2 | import { 3 | ContextProvider, 4 | ContextSelector, 5 | ContextSearch, 6 | ContextSelectEvent, 7 | } from '@equinor/fusion-react-context-selector/src'; 8 | import { _exampleResolver } from './context-selector.helpers'; 9 | 10 | const meta: Meta = { 11 | title: 'data/ContextSelector', 12 | component: ContextSelector, 13 | }; 14 | 15 | export default meta; 16 | 17 | type Story = StoryObj; 18 | 19 | export const ContextHeader: Story = { 20 | args: { 21 | placeholder: 'Start to type to search...', 22 | initialText: 'The initial text result', 23 | variant: 'header', 24 | dropdownHeight: '300px', 25 | onSelect: (e: ContextSelectEvent) => { 26 | e.stopPropagation(); 27 | console.log('Event', e.type, 'fired. Object:', e); 28 | }, 29 | onClearContext: () => { 30 | console.log('Context Clearing'); 31 | }, 32 | }, 33 | render: (args) => ( 34 | 35 | 36 | 37 | ), 38 | }; 39 | -------------------------------------------------------------------------------- /storybook/src/stories/date/date.mdx: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react'; 2 | 3 | import { Meta, Markdown, Canvas, ArgTypes } from '@storybook/blocks'; 4 | 5 | import readme from '#packages/date/README.md?raw'; 6 | import pkg from '#packages/date/package.json'; 7 | 8 | import changelogReact from '#packages/date/CHANGELOG.md?raw'; 9 | import changelogWebComponent from '@equinor/fusion-wc-date/CHANGELOG.md?raw'; 10 | 11 | import {ChangeLog, PackageInfo} from '../../components'; 12 | 13 | import { date_range } from './daterange.stories'; 14 | import { date_time } from './datetime.stories'; 15 | 16 | 17 | 18 | 19 | 20 | {readme} 21 | 22 | ## DateTime 23 | 24 | 25 | 26 | 27 | ## DateRange 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /storybook/src/stories/date/daterange.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import { DateRange } from '@equinor/fusion-react-date/src/DateRange'; 4 | 5 | const meta: Meta = { 6 | title: 'data/Date', 7 | component: DateRange, 8 | }; 9 | 10 | export default meta; 11 | 12 | type Story = StoryObj; 13 | 14 | export const date_range: Story = { 15 | name: 'DateRange', 16 | args: { 17 | from: new Date('2021-09-21T13:18:42.000Z'), 18 | to: new Date('2022-11-21T13:28:42.000Z'), 19 | }, 20 | render: (props) => , 21 | }; 22 | -------------------------------------------------------------------------------- /storybook/src/stories/date/datetime.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import { DateTime } from '@equinor/fusion-react-date/src/DateTime'; 4 | 5 | const meta: Meta = { 6 | title: 'data/Date', 7 | component: DateTime, 8 | }; 9 | 10 | export default meta; 11 | 12 | type Story = StoryObj; 13 | 14 | export const date_time: Story = { 15 | name: 'DateTime', 16 | args: { 17 | date: new Date('2021-09-21T13:18:42.000Z'), 18 | }, 19 | render: (args) => , 20 | } satisfies Story; 21 | -------------------------------------------------------------------------------- /storybook/src/stories/error-boundary/error-boundary.mdx: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react'; 2 | 3 | import { Meta, Markdown, ArgTypes, Canvas } from '@storybook/blocks'; 4 | 5 | import readme from '#packages/errorboundary/README.md?raw'; 6 | import pkg from '#packages/errorboundary/package.json'; 7 | 8 | import changelogReact from '#packages/errorboundary/CHANGELOG.md?raw'; 9 | 10 | import { ChangeLog, PackageInfo } from '../../components'; 11 | 12 | import * as stories from './error-boundary.stories'; 13 | 14 | 15 | 16 | 17 | 18 | {readme} 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ## Changelog 27 | 28 | -------------------------------------------------------------------------------- /storybook/src/stories/error-boundary/error-boundary.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import { userEvent, within } from '@storybook/test'; 4 | 5 | import { ErrorBoundary } from '@equinor/fusion-react-errorboundary/legacy'; 6 | import { useErrorBoundary } from '@equinor/fusion-react-errorboundary'; 7 | 8 | const meta: Meta = { 9 | title: 'data/ErrorBoundary', 10 | component: ErrorBoundary, 11 | }; 12 | 13 | export default meta; 14 | 15 | type Story = StoryObj; 16 | 17 | const ErrorComponent = () => { 18 | const { showBoundary } = useErrorBoundary(); 19 | return ( 20 | 23 | ); 24 | }; 25 | 26 | export const error_boundary: Story = { 27 | name: 'ErrorBoundary', 28 | args: { 29 | children: , 30 | }, 31 | play: async ({ canvasElement }) => { 32 | const canvas = within(canvasElement); 33 | const btn = canvas.getByRole('button'); 34 | await userEvent.click(btn, { delay: 2000 }); 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /storybook/src/stories/filter/filter.mdx: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react'; 2 | 3 | import { Meta, Markdown, ArgTypes, Canvas } from '@storybook/blocks'; 4 | 5 | import readme from '#packages/filter/README.md?raw'; 6 | import pkg from '#packages/filter/package.json'; 7 | 8 | import changelogReact from '#packages/filter/CHANGELOG.md?raw'; 9 | 10 | import { ChangeLog, PackageInfo } from '../../components'; 11 | 12 | import * as stories from './filter.stories'; 13 | 14 | 15 | 16 | 17 | 18 | {readme} 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ## Changelog 27 | 28 | -------------------------------------------------------------------------------- /storybook/src/stories/filter/generate-data.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | 3 | export type DataType = { id: string; firstName: string; lastName: string; company: string; jobType: string }; 4 | 5 | export const generateData = (n: number, y = 5): DataType[] => { 6 | faker.seed(100); 7 | const generated = { 8 | firstName: [...new Array(y)].map(() => faker.name.firstName()), 9 | lastName: [...new Array(y)].map(() => faker.name.lastName()), 10 | company: [...new Array(y)].map(() => faker.company.name()), 11 | jobType: [...new Array(y)].map(() => faker.name.jobType()), 12 | }; 13 | const random = (k: keyof typeof generated) => generated[k][faker.number.int({ min: 0, max: y - 1 })]; //[faker.number.int({ min: 1, max: 5 })]; 14 | return [...new Array(n)].map(() => ({ 15 | id: faker.git.commitSha(), 16 | firstName: random('firstName'), 17 | lastName: random('lastName'), 18 | company: random('company'), 19 | jobType: random('jobType'), 20 | })); 21 | }; 22 | 23 | export default generateData; 24 | -------------------------------------------------------------------------------- /storybook/src/stories/list/list.mdx: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react'; 2 | 3 | import { Meta, Markdown, ArgTypes, Canvas } from '@storybook/blocks'; 4 | 5 | import readme from '#packages/list/README.md?raw'; 6 | import pkg from '#packages/list/package.json'; 7 | 8 | import changelogReact from '#packages/list/CHANGELOG.md?raw'; 9 | 10 | import { ListItem } from '#packages/list/src/components/ListItem'; 11 | 12 | import { ChangeLog, PackageInfo } from '../../components'; 13 | 14 | import * as listStories from './list.stories'; 15 | 16 | 17 | 18 | 19 | 20 | {readme} 21 | 22 | ## List 23 | 24 | 25 | 26 | 27 | ## List item 28 | 29 | 30 | 31 | 32 | 33 | 34 | ### Checklist 35 | 36 | 37 | ### Radiolist 38 | 39 | 40 | ## Changelog 41 | 42 | -------------------------------------------------------------------------------- /storybook/src/stories/markdown/demo.md: -------------------------------------------------------------------------------- 1 | # This is markdown 2 | 3 | ## This is also markdown 4 | 5 | ### This is also markdown 6 | 7 | * Some 8 | * List 9 | * Items -------------------------------------------------------------------------------- /storybook/src/stories/markdown/markdown.editor.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import { MarkdownEditor } from '@equinor/fusion-react-markdown/src/MarkdownEditor'; 4 | 5 | import markdown from './demo.md?raw'; 6 | 7 | const meta: Meta = { 8 | title: 'Data/Markdown/Editor', 9 | component: MarkdownEditor, 10 | argTypes: { 11 | menuSize: { 12 | description: 'Size of the menu buttons', 13 | control: 'radio', 14 | options: ['small', 'medium', 'large'], 15 | table: { 16 | type: { summary: 'MenuSizes' }, 17 | defaultValue: { summary: 'medium' }, 18 | }, 19 | }, 20 | menuItems: { 21 | description: 'List of visible menu buttons', 22 | control: 'check', 23 | options: ['strong', 'em', 'ordered_list', 'bullet_list', 'paragraph', 'blockquote', 'h1', 'h2', 'h3'], 24 | table: { 25 | type: { summary: 'MdMenuItemType[]' }, 26 | defaultValue: { summary: ['strong', 'em', 'bullet_list', 'ordered_list'] }, 27 | }, 28 | }, 29 | minHeight: { 30 | description: 'Markdown Editor minimum height', 31 | table: { 32 | type: { summary: 'string' }, 33 | }, 34 | }, 35 | value: { 36 | description: 'Markdown editors value', 37 | control: 'text', 38 | table: { 39 | type: { summary: 'Markdown' }, 40 | }, 41 | }, 42 | }, 43 | }; 44 | 45 | export default meta; 46 | 47 | type Story = StoryObj; 48 | 49 | export const editor: Story = { 50 | args: { 51 | value: markdown, 52 | menuSize: 'small', 53 | menuItems: ['strong', 'em', 'bullet_list', 'ordered_list', 'h1', 'h2'], 54 | }, 55 | }; 56 | -------------------------------------------------------------------------------- /storybook/src/stories/markdown/markdown.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Markdown, ArgTypes, Canvas } from '@storybook/blocks'; 2 | 3 | import readme from '#packages/markdown/README.md?raw'; 4 | import pkg from '#packages/markdown/package.json'; 5 | 6 | import changelogReact from '#packages/markdown/CHANGELOG.md?raw'; 7 | 8 | import { ChangeLog, PackageInfo } from '../../components'; 9 | 10 | 11 | 12 | 13 | 14 | {readme} 15 | 16 | -------------------------------------------------------------------------------- /storybook/src/stories/markdown/markdown.viewer.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import { MarkdownViewer } from '@equinor/fusion-react-markdown/src/MarkdownViewer'; 4 | 5 | import markdown from './demo.md?raw'; 6 | 7 | const meta: Meta = { 8 | title: 'data/Markdown/Viewer', 9 | component: MarkdownViewer, 10 | }; 11 | 12 | export default meta; 13 | 14 | type Story = StoryObj; 15 | 16 | export const viewer: Story = { 17 | args: { 18 | value: markdown, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /storybook/src/stories/person/avatar/avatar.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Markdown, ArgTypes, Canvas } from '@storybook/blocks'; 2 | import * as avatarStories from './avatar.stories'; 3 | 4 | 5 | 6 | 7 | ## Avatar 8 | 9 | 10 | ### Basic 11 | 12 | 13 | ### Sizes 14 | 15 | 16 | ### withDataSource 17 | 18 | The avatar will not resolve any data about the user when using property `dataSource`, so the persons avatar will be the users initial as provided in `dataSource`. 19 | 20 | When using `dataSource` be sure to add the property `trigger="none"` to prevent the personCard from opening when hovering/clicking on the avatar since it will not resolve a user. 21 | 22 | 23 | 24 | ### Clickable 25 | 26 | 27 | ### Trigger none 28 | 29 | 30 | ### disabled 31 | 32 | -------------------------------------------------------------------------------- /storybook/src/stories/person/card.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import { PersonCard } from '@equinor/fusion-react-person/src/PersonCard'; 4 | import { PersonDetails, type PersonItemSize } from '@equinor/fusion-react-person/src/index'; 5 | import { PersonProvider } from '@equinor/fusion-react-person/src/PersonProvider'; 6 | import { Theme } from '../../components/Theme'; 7 | 8 | import { resolver } from './person-provider'; 9 | 10 | import { faker } from '@faker-js/faker'; 11 | faker.seed(123); 12 | 13 | const meta: Meta = { 14 | title: 'person/Card', 15 | component: PersonCard, 16 | }; 17 | 18 | export default meta; 19 | 20 | type Story = StoryObj; 21 | 22 | export const basic: Story = { 23 | decorators: [ 24 | (Story) => ( 25 | 26 | 27 | 28 | 29 | 30 | ), 31 | ], 32 | args: { 33 | azureId: faker.string.uuid(), 34 | }, 35 | }; 36 | 37 | export const sizes: Story = { 38 | ...basic, 39 | render: (props) => ( 40 |
41 | {(['small', 'medium', 'large'] as PersonItemSize[]).map((size) => ( 42 | 43 | ))} 44 |
45 | ), 46 | }; 47 | 48 | export const withDataSource: Story = { 49 | ...basic, 50 | args: { 51 | dataSource: resolver.getDetails 52 | ? (resolver.getDetails({ azureId: faker.string.uuid() }) as PersonDetails) 53 | : undefined, 54 | }, 55 | }; 56 | -------------------------------------------------------------------------------- /storybook/src/stories/person/provider/provider.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import { PersonProvider, PersonResolver, PersonSelect, PersonSearchResult } from '@equinor/fusion-react-person'; 4 | 5 | import { Theme } from '../../../components/Theme'; 6 | import { resolver as mainResolver } from '../person-provider'; 7 | 8 | const meta: Meta = { 9 | title: 'person/Provider', 10 | component: PersonProvider, 11 | }; 12 | 13 | export default meta; 14 | 15 | export const resolver: PersonResolver = { 16 | search: (args) => { 17 | const method = mainResolver.search; 18 | 19 | if (method) { 20 | const found = method(args) as PersonSearchResult; 21 | return found.slice(0, 3); 22 | } 23 | 24 | return []; 25 | }, 26 | }; 27 | 28 | type Story = StoryObj; 29 | 30 | export const customProvider: Story = { 31 | decorators: [ 32 | (Story) => ( 33 | 34 | 35 | 36 | 37 | 38 | ), 39 | ], 40 | args: { 41 | resolve: resolver, 42 | }, 43 | render: (props) => { 44 | return ( 45 | 46 | 47 | 48 | ); 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /storybook/src/stories/searchable-dropdown/searchable-dropdown.stories.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/rules-of-hooks */ 2 | import { Meta, StoryObj } from '@storybook/react'; 3 | 4 | import { SearchableDropdown } from '@equinor/fusion-react-searchable-dropdown/src/SearchableDropdown'; 5 | 6 | // TODO - simplify 7 | import { _exampleResolver } from './component.helpers'; 8 | 9 | const meta: Meta = { 10 | title: 'data/Searchable dropdown', 11 | component: SearchableDropdown, 12 | }; 13 | 14 | export default meta; 15 | 16 | type Story = StoryObj; 17 | 18 | export const basic: Story = { 19 | args: { 20 | placeholder: 'Search here...', 21 | variant: 'outlined', 22 | initialText: 'See results by searching', 23 | onSelect: (e) => { 24 | /* no need to bubble further up the dom */ 25 | e.stopPropagation(); 26 | console.log('Event', e.type, 'fired. Object:', e); 27 | }, 28 | resolver: _exampleResolver, 29 | }, 30 | render: (props) => { 31 | return ; 32 | }, 33 | }; 34 | 35 | export const multiple: Story = { 36 | args: { 37 | label: 'Select multiple', 38 | placeholder: 'Search here...', 39 | variant: 'outlined', 40 | multiple: true, 41 | onSelect: (e) => { 42 | /* no need to bubble further up the dom */ 43 | e.stopPropagation(); 44 | console.log('Event', e.type, 'fired. Object:', e); 45 | }, 46 | resolver: _exampleResolver, 47 | }, 48 | render: (props) => { 49 | return ; 50 | }, 51 | }; 52 | -------------------------------------------------------------------------------- /storybook/src/stories/sidesheet/side-sheet.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Markdown, ArgTypes, Canvas } from '@storybook/blocks'; 2 | 3 | import readme from '#packages/side-sheet/README.md?raw'; 4 | import pkg from '#packages/side-sheet/package.json'; 5 | 6 | import changelogReact from '#packages/side-sheet/CHANGELOG.md?raw'; 7 | 8 | import { ChangeLog, PackageInfo } from '../../components'; 9 | 10 | import * as stories from './side-sheet.stories'; 11 | 12 | 13 | 14 | 15 | 16 | 17 | {readme} 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /storybook/src/stories/sidesheet/side-sheet.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import { SideSheet } from '@equinor/fusion-react-side-sheet/src/components/SideSheet'; 4 | 5 | import { Button, Icon } from '@equinor/eds-core-react'; 6 | import { help_outline } from '@equinor/eds-icons'; 7 | import { useState } from 'react'; 8 | 9 | Icon.add({ help_outline }); 10 | 11 | const meta: Meta = { 12 | title: 'UI/Side Sheet', 13 | component: SideSheet, 14 | }; 15 | 16 | export default meta; 17 | 18 | type Story = StoryObj; 19 | 20 | let open = false; 21 | 22 | export const basic: Story = { 23 | args: { 24 | isOpen: open, 25 | onClose: () => { 26 | open = false; 27 | }, 28 | enableFullscreen: false, 29 | minWidth: 400, 30 | animate: true, 31 | children: ( 32 | <> 33 | , 34 | , 35 | , 36 | 37 | 45 | 46 | 😎, 47 | 48 | ), 49 | }, 50 | render: (props) => { 51 | // eslint-disable-next-line react-hooks/rules-of-hooks 52 | const [open, setOpen] = useState(false); 53 | return ( 54 |
55 | 56 | setOpen(false)}> 57 |
58 | ); 59 | }, 60 | }; 61 | -------------------------------------------------------------------------------- /storybook/src/stories/skeleton/skeleton.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Markdown, ArgTypes, Canvas } from '@storybook/blocks'; 2 | 3 | import readme from '#packages/skeleton/README.md?raw'; 4 | import pkg from '#packages/skeleton/package.json'; 5 | 6 | import changelogReact from '#packages/skeleton/CHANGELOG.md?raw'; 7 | 8 | import { ChangeLog, PackageInfo } from '../../components'; 9 | 10 | import * as stories from './skeleton.stories'; 11 | 12 | 13 | 14 | 15 | 16 | {readme} 17 | 18 | 19 | 20 | ### Basic 21 | 22 | 23 | ### Sizes 24 | 25 | 26 | ### Variant 27 | 28 | 29 | ### Custom colors 30 | 31 | 32 | -------------------------------------------------------------------------------- /storybook/src/stories/skeleton/skeleton.stories.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | import { Meta, StoryObj } from '@storybook/react'; 3 | 4 | import { Skeleton, SkeletonSize, SkeletonVariant } from '@equinor/fusion-react-skeleton/src/Skeleton'; 5 | 6 | const meta: Meta = { 7 | title: 'ui/Skeleton', 8 | component: Skeleton, 9 | }; 10 | 11 | export default meta; 12 | 13 | type Story = StoryObj; 14 | 15 | export const basic: Story = { 16 | args: { 17 | size: SkeletonSize.Medium, 18 | children: skeleton, 19 | }, 20 | }; 21 | 22 | export const sizes: Story = { 23 | ...basic, 24 | render: (props) => ( 25 |
26 | {Object.values(SkeletonSize).map((size) => ( 27 |
28 | 29 | {size} 30 | 31 |
32 | ))} 33 |
34 | ), 35 | }; 36 | 37 | export const variant: Story = { 38 | ...basic, 39 | render: (props) => ( 40 |
41 | {Object.values(SkeletonVariant).map((variant) => ( 42 |
43 | {variant} 44 | 45 |
46 | ))} 47 |
48 | ), 49 | }; 50 | 51 | export const customColor: Story = { 52 | args: { 53 | ...basic.args, 54 | style: { 55 | // @ts-expect-error 56 | '--fwc-skeleton-fill-color': 'rgba(0, 111, 0, 0.25)', 57 | '--fwc-skeleton-ink-color': 'rgba(111, 0, 0, 0.75)', 58 | }, 59 | }, 60 | }; 61 | -------------------------------------------------------------------------------- /storybook/src/stories/stepper/stepper.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Markdown, ArgTypes, Canvas } from '@storybook/blocks'; 2 | 3 | import readme from '#packages/stepper/README.md?raw'; 4 | import pkg from '#packages/stepper/package.json'; 5 | 6 | import changelogReact from '#packages/stepper/CHANGELOG.md?raw'; 7 | 8 | import { ChangeLog, PackageInfo } from '../../components'; 9 | 10 | import * as stories from './stepper.stories'; 11 | 12 | 13 | 14 | 15 | 16 | 17 | {readme} 18 | 19 | 20 | 21 | ## Basic Uncontrolled 22 | 23 | 24 | ## Vertical Uncontrolled 25 | 26 | 27 | ## Controlled 28 | 29 | 30 | ## Controlled With External Buttons 31 | 32 | 33 | 38 | -------------------------------------------------------------------------------- /storybook/src/stories/textarea/textarea.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Markdown, ArgTypes, Canvas } from '@storybook/blocks'; 2 | 3 | import readme from '#packages/textarea/README.md?raw'; 4 | import pkg from '#packages/textarea/package.json'; 5 | 6 | import changelogReact from '#packages/textarea/CHANGELOG.md?raw'; 7 | 8 | import { ChangeLog, PackageInfo } from '../../components'; 9 | 10 | import * as stories from './textarea.stories'; 11 | 12 | 13 | 14 | 15 | 16 | {readme} 17 | 18 | 19 | 20 | ### Basic 21 | 22 | 23 | ### Sizes 24 | 25 | 26 | ### Required 27 | 28 | 29 | ### Disabled 30 | 31 | 32 | ### With counter 33 | 34 | 35 | ### With help text 36 | 37 | 38 | 43 | -------------------------------------------------------------------------------- /storybook/src/stories/textarea/textarea.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import { TextInput } from '@equinor/fusion-react-textarea/src/TextArea'; 4 | 5 | const meta: Meta = { 6 | title: 'data/Textarea', 7 | component: TextInput, 8 | }; 9 | 10 | export default meta; 11 | 12 | type Story = StoryObj; 13 | 14 | export const basic: Story = { 15 | args: { 16 | label: 'basic', 17 | }, 18 | }; 19 | 20 | export const size: Story = { 21 | args: { 22 | label: 'Multiple rows', 23 | rows: 6, 24 | }, 25 | }; 26 | 27 | export const required: Story = { 28 | args: { 29 | label: 'required', 30 | required: true, 31 | }, 32 | }; 33 | 34 | export const disabled: Story = { 35 | args: { 36 | label: 'required', 37 | disabled: true, 38 | }, 39 | }; 40 | 41 | export const charCounter: Story = { 42 | args: { 43 | label: 'Character counter', 44 | charCounter: true, 45 | maxLength: 200, 46 | }, 47 | }; 48 | 49 | export const withHelpText: Story = { 50 | render: (props) => ( 51 |
52 | 53 | 54 |
55 | ), 56 | }; 57 | -------------------------------------------------------------------------------- /storybook/src/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | }); 7 | -------------------------------------------------------------------------------- /storybook/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "resolveJsonModule": true, 6 | "paths": { 7 | "@equinor/fusion-react-*": ["../packages/*"] 8 | } 9 | }, 10 | "exclude": [ 11 | "node_modules", 12 | "dist" 13 | ], 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "allowSyntheticDefaultImports": true, 5 | "module": "ESNext", 6 | "target": "ESNext", 7 | "moduleResolution": "bundler", 8 | "esModuleInterop": true, 9 | "experimentalDecorators": true, 10 | "skipLibCheck": true, 11 | "declaration": true, 12 | "declarationMap": true, 13 | "composite": true, 14 | "baseUrl": "./packages", 15 | "sourceMap": true, 16 | "rootDir": ".", 17 | "lib": ["esnext", "dom"], 18 | "paths": { 19 | "@equinor/fusion-react-*": ["*/src"] 20 | }, 21 | "jsx": "react-jsx" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | }, 6 | "include": ["packages/**/src/**/*"], 7 | "exclude": ["node_modules", "dist", "lib", "*.spec.ts", "api"], 8 | "files": [], 9 | "references": [ 10 | { 11 | "path": "packages/context-selector" 12 | }, 13 | { 14 | "path": "packages/date" 15 | }, 16 | { 17 | "path": "packages/errorboundary" 18 | }, 19 | { 20 | "path": "packages/filter" 21 | }, 22 | { 23 | "path": "packages/hanging-garden" 24 | }, 25 | { 26 | "path": "packages/list" 27 | }, 28 | { 29 | "path": "packages/markdown" 30 | }, 31 | { 32 | "path": "packages/person" 33 | }, 34 | { 35 | "path": "packages/ripple" 36 | }, 37 | { 38 | "path": "packages/searchable-dropdown" 39 | }, 40 | { 41 | "path": "packages/skeleton" 42 | }, 43 | { 44 | "path": "packages/stepper" 45 | }, 46 | { 47 | "path": "packages/styles" 48 | }, 49 | { 50 | "path": "packages/textarea" 51 | }, 52 | { 53 | "path": "packages/utils" 54 | } 55 | ] 56 | } 57 | --------------------------------------------------------------------------------