mirror of
https://github.com/navikt/aksel.git
synced 2026-10-15 10:12:03 +00:00
Compare commits
29
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34d41ecc78 | ||
|
|
7e62365a60 | ||
|
|
8565f410a1 | ||
|
|
46d70820df | ||
|
|
67e66ffe02 | ||
|
|
e19bf67b33 | ||
|
|
97c5f60e91 | ||
|
|
ea5293c28d | ||
|
|
6a58efa9a9 | ||
|
|
c95f33679e | ||
|
|
b52a5e02a0 | ||
|
|
ab3ef0d34b | ||
|
|
69798f6ee2 | ||
|
|
ee14586beb | ||
|
|
82470ba669 | ||
|
|
6286f0e8e6 | ||
|
|
3c08651df2 | ||
|
|
e6d55fdcd6 | ||
|
|
8c445f020c | ||
|
|
78a76bac5d | ||
|
|
75b334af61 | ||
|
|
5d98c50b80 | ||
|
|
27803fd37c | ||
|
|
1bdb7e3777 | ||
|
|
20bcc28cb0 | ||
|
|
f43ec5c48c | ||
|
|
40f0b3340d | ||
|
|
1f023a84be | ||
|
|
f170a2201c |
No files matched your search
@@ -21,7 +21,7 @@
|
||||
"access": "public",
|
||||
"baseBranch": "master",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": ["nextjs-demo", "shadow-dom"],
|
||||
"ignore": ["nextjs-demo", "shadow-dom", "aksel.nav.no"],
|
||||
|
||||
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
|
||||
"updateInternalDependents": "always"
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
name: Deploy aksel.dev.nav.no
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- next
|
||||
|
||||
env:
|
||||
IMAGE: ghcr.io/navikt/designsystemet
|
||||
|
||||
jobs:
|
||||
build_and_deploy:
|
||||
name: Build and deploy to dev-gcp
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: "14"
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: "next"
|
||||
- name: add tokens to .env
|
||||
run: |
|
||||
echo "SANITY_WRITE_KEY=${{ secrets.SANITY_FEEDBACK_WRITE_KEY }}" >> aksel.nav.no/website/.env
|
||||
echo "ALGOLIA_ADMIN=${{ secrets.ALGOLIA_ADMIN }}" >> aksel.nav.no/website/.env
|
||||
echo "ALGOLIA_ID=${{ secrets.ALGOLIA_ID}}" >> aksel.nav.no/website/.env
|
||||
echo "TRELLO_TOKEN=${{ secrets.TRELLO_TOKEN }}" >> aksel.nav.no/website/.env
|
||||
echo "SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}" >> aksel.nav.no/website/.env
|
||||
echo "SANITY_PREVIEW_TOKEN=${{ secrets.SANITY_PREVIEW_TOKEN }}" >> aksel.nav.no/website/.env
|
||||
echo "SANITY_PRIVATE_NO_DRAFTS=${{ secrets.SANITY_PRIVATE_NO_DRAFTS }}" >> aksel.nav.no/website/.env
|
||||
|
||||
- name: Restore cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-dev-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-dev-
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
- name: Build application
|
||||
run: |
|
||||
yarn boot
|
||||
yarn docgen
|
||||
yarn workspace aksel.nav.no build:next
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@ab80d026d4753220c4243394c07c7d80f9638d06 # Use commit-sha1 instead of tag for security concerns
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get complete tag
|
||||
run: echo "TAG=DEV-$( date +%s )" >> $GITHUB_ENV
|
||||
- name: Create docker image tags
|
||||
run: echo "DOCKER_TAG=${{ env.IMAGE }}:${{ env.TAG }}" >> $GITHUB_ENV
|
||||
- name: Build docker image
|
||||
run: docker build -t ${{ env.DOCKER_TAG }} . -f aksel.nav.no/Dockerfile
|
||||
- name: Push docker image
|
||||
run: |
|
||||
docker push ${{ env.DOCKER_TAG }}
|
||||
|
||||
- name: Deploy to dev-gcp
|
||||
uses: nais/deploy/actions/deploy@master
|
||||
env:
|
||||
APIKEY: ${{ secrets.NAIS_DEPLOY_APIKEY }}
|
||||
RESOURCE: aksel.nav.no/nais-dev.yaml
|
||||
CLUSTER: dev-gcp
|
||||
IMAGE: ${{ env.DOCKER_TAG }}
|
||||
@@ -0,0 +1,81 @@
|
||||
name: Deploy aksel.nav.no
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- "aksel.nav.no/**"
|
||||
|
||||
env:
|
||||
IMAGE: ghcr.io/navikt/designsystemet
|
||||
|
||||
jobs:
|
||||
build_and_deploy:
|
||||
name: Build and deploy to prod-gcp
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: "14"
|
||||
- uses: actions/checkout@v2
|
||||
- name: add tokens to .env
|
||||
run: |
|
||||
echo "SANITY_WRITE_KEY=${{ secrets.SANITY_FEEDBACK_WRITE_KEY }}" >> aksel.nav.no/website/.env
|
||||
echo "ALGOLIA_ADMIN=${{ secrets.ALGOLIA_ADMIN }}" >> aksel.nav.no/website/.env
|
||||
echo "ALGOLIA_ID=${{ secrets.ALGOLIA_ID}}" >> aksel.nav.no/website/.env
|
||||
echo "TRELLO_TOKEN=${{ secrets.TRELLO_TOKEN }}" >> aksel.nav.no/website/.env
|
||||
echo "SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}" >> aksel.nav.no/website/.env
|
||||
echo "SANITY_PREVIEW_TOKEN=${{ secrets.SANITY_PREVIEW_TOKEN }}" >> aksel.nav.no/website/.env
|
||||
echo "SANITY_PRIVATE_NO_DRAFTS=${{ secrets.SANITY_PRIVATE_NO_DRAFTS }}" >> aksel.nav.no/website/.env
|
||||
|
||||
- name: Restore cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-prod-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-prod-
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
- name: build packages
|
||||
run: |
|
||||
yarn boot
|
||||
yarn docgen
|
||||
|
||||
- name: Tests
|
||||
run: yarn workspace aksel.nav.no test
|
||||
|
||||
- name: E2E
|
||||
run: NEXT_PUBLIC_TEST=true yarn workspace website e2e
|
||||
|
||||
- name: Update sanity sync
|
||||
run: yarn workspace aksel.nav.no sanity:update
|
||||
|
||||
- name: Build application
|
||||
run: yarn workspace aksel.nav.no build:next
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@ab80d026d4753220c4243394c07c7d80f9638d06 # Use commit-sha1 instead of tag for security concerns
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get complete tag
|
||||
run: echo "TAG=$( date +%s )" >> $GITHUB_ENV
|
||||
- name: Create docker image tags
|
||||
run: echo "DOCKER_TAG=${{ env.IMAGE }}:${{ env.TAG }}" >> $GITHUB_ENV
|
||||
- name: Build docker image
|
||||
run: docker build -t ${{ env.DOCKER_TAG }} . -f aksel.nav.no/Dockerfile
|
||||
- name: Push docker image
|
||||
run: |
|
||||
docker push ${{ env.DOCKER_TAG }}
|
||||
|
||||
- name: Deploy to prod-gcp
|
||||
uses: nais/deploy/actions/deploy@master
|
||||
env:
|
||||
APIKEY: ${{ secrets.NAIS_DEPLOY_APIKEY }}
|
||||
RESOURCE: aksel.nav.no/nais-prod.yaml
|
||||
CLUSTER: prod-gcp
|
||||
IMAGE: ${{ env.DOCKER_TAG }}
|
||||
@@ -0,0 +1,41 @@
|
||||
name: Redeploy image to for aksel.nav.no
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Docker image tag"
|
||||
required: true
|
||||
default: ""
|
||||
dry:
|
||||
description: "Dry-run"
|
||||
required: true
|
||||
default: "false"
|
||||
|
||||
env:
|
||||
IMAGE: ghcr.io/navikt/designsystemet
|
||||
|
||||
jobs:
|
||||
redeploy:
|
||||
name: Redeploy image
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: "14"
|
||||
- uses: actions/checkout@v2
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@ab80d026d4753220c4243394c07c7d80f9638d06 # Use commit-sha1 instead of tag for security concerns
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create docker image tag
|
||||
run: echo "DOCKER_TAG=${{ env.IMAGE }}:${{ github.event.inputs.tag }}" >> $GITHUB_ENV
|
||||
- name: Deploy to prod-gcp
|
||||
uses: nais/deploy/actions/deploy@master
|
||||
env:
|
||||
APIKEY: ${{ secrets.NAIS_DEPLOY_APIKEY }}
|
||||
RESOURCE: aksel.nav.no/nais-prod.yaml
|
||||
CLUSTER: prod-gcp
|
||||
IMAGE: ${{ env.DOCKER_TAG }}
|
||||
DRY_RUN: ${{ github.event.inputs.dry }}
|
||||
@@ -0,0 +1,39 @@
|
||||
name: Backup sanity dateset to gcp
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# 12.00 every 3 days
|
||||
- cron: "0 0 */3 * *"
|
||||
jobs:
|
||||
execute:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Required to retrieve git history
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
- name: Restore cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-build-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
- name: Export dataset
|
||||
run: SANITY_TOKEN=${{secrets.SANITY_DATASET_EDITOR_TOKEN}} yarn workspace aksel.nav.no sanity:backup
|
||||
- name: Upload GCP bucket
|
||||
uses: "google-github-actions/auth@v0"
|
||||
with:
|
||||
credentials_json: "${{ secrets.gcp_credentials_prod }}"
|
||||
- uses: "google-github-actions/upload-cloud-storage@v0"
|
||||
with:
|
||||
path: ./aksel.nav.no/sanity
|
||||
destination: "aksel-website-prod/backups"
|
||||
glob: "**/backup-*.tar.gz"
|
||||
@@ -4,11 +4,10 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
- "next"
|
||||
paths:
|
||||
- "@navikt/**"
|
||||
pull_request:
|
||||
branches: [master]
|
||||
branches: [master, next]
|
||||
paths:
|
||||
- "@navikt/**"
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ on:
|
||||
- master
|
||||
- next
|
||||
pull_request:
|
||||
types: [opened, edited]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, next]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [master]
|
||||
schedule:
|
||||
- cron: "41 8 * * 6"
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: ["javascript"]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
@@ -0,0 +1,56 @@
|
||||
name: E2E / Playwright Tests
|
||||
on:
|
||||
push:
|
||||
branches: [master, next]
|
||||
paths:
|
||||
- "aksel.nav.no/**"
|
||||
pull_request:
|
||||
branches: [master, next]
|
||||
paths:
|
||||
- "aksel.nav.no/**"
|
||||
|
||||
jobs:
|
||||
playwright:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: add tokens to .env
|
||||
run: |
|
||||
echo "SANITY_WRITE_KEY=${{ secrets.SANITY_FEEDBACK_WRITE_KEY }}" >> aksel.nav.no/website/.env
|
||||
echo "ALGOLIA_ADMIN=${{ secrets.ALGOLIA_ADMIN }}" >> aksel.nav.no/website/.env
|
||||
echo "ALGOLIA_ID=${{ secrets.ALGOLIA_ID}}" >> aksel.nav.no/website/.env
|
||||
echo "TRELLO_TOKEN=${{ secrets.TRELLO_TOKEN }}" >> aksel.nav.no/website/.env
|
||||
echo "SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}" >> aksel.nav.no/website/.env
|
||||
echo "SANITY_PREVIEW_TOKEN=${{ secrets.SANITY_PREVIEW_TOKEN }}" >> aksel.nav.no/website/.env
|
||||
echo "SANITY_PRIVATE_NO_DRAFTS=${{ secrets.SANITY_PRIVATE_NO_DRAFTS }}" >> aksel.nav.no/website/.env
|
||||
|
||||
- name: Restore cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-e2e-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-e2e-
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
- name: build packages
|
||||
run: |
|
||||
yarn boot
|
||||
yarn docgen
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
- name: Run Playwright tests
|
||||
run: yarn workspace website e2e
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: aksel.nav.no/website/playwright-report/
|
||||
retention-days: 10
|
||||
@@ -27,9 +27,9 @@ jobs:
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-test-${{ github.sha }}
|
||||
key: ${{ runner.os }}-prod-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-test
|
||||
${{ runner.os }}-prod
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
name: Indexing search
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# 01.00 & 12.00 every day
|
||||
- cron: "00 01,12 * * *"
|
||||
|
||||
jobs:
|
||||
searchcrawl:
|
||||
name: Search-indexing aksel.nav.no
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
APPLICATION_ID: ${{ secrets.ALGOLIA_ID}}
|
||||
API_KEY: ${{ secrets.ALGOLIA_ADMIN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Read crawler.json
|
||||
id: algolia_config
|
||||
run: echo "::set-output name=config::$(cat ./aksel.nav.no/crawler.json | jq -r tostring)"
|
||||
- name: Push indices to Algolia
|
||||
uses: signcl/docsearch-scraper-action@master
|
||||
env:
|
||||
APPLICATION_ID: ${{ secrets.ALGOLIA_ID}}
|
||||
API_KEY: ${{ secrets.ALGOLIA_ADMIN }}
|
||||
CONFIG: ${{ steps.algolia_config.outputs.config }}
|
||||
+19
-1
@@ -129,4 +129,22 @@ _docs.json
|
||||
*/icons/src
|
||||
*/icons/png
|
||||
*/core/icons
|
||||
/storybook-static
|
||||
/storybook-static
|
||||
|
||||
# Aksel.nav.no
|
||||
.next
|
||||
!*/website/lib/
|
||||
!*/website/public/
|
||||
!aksel.nav.no/**/*.d.ts
|
||||
!*/sanity/lib/
|
||||
!*/sanity/static/
|
||||
out
|
||||
test-urls.json
|
||||
videos
|
||||
sanity/scripts/renameField.js
|
||||
tmp.json
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
playwright-report/
|
||||
/test-results/
|
||||
@@ -0,0 +1,2 @@
|
||||
**/nais-dev.yaml
|
||||
**/nais-prod.yaml
|
||||
@@ -15,3 +15,7 @@
|
||||
.rowgap[data-theme="dark"] {
|
||||
background-color: #262626;
|
||||
}
|
||||
|
||||
.rowgap-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@@ -17,7 +17,6 @@ module.exports = {
|
||||
},
|
||||
stories: () => [...getStories(), "./*.stories.mdx"],
|
||||
addons: [
|
||||
"@storybook/addon-docs",
|
||||
"@storybook/addon-a11y",
|
||||
{
|
||||
name: "@storybook/addon-storysource",
|
||||
|
||||
@@ -1,3 +1,25 @@
|
||||
# @navikt/ds-codemod
|
||||
|
||||
## 1.5.2
|
||||
|
||||
## 1.5.1
|
||||
|
||||
## 1.5.0
|
||||
|
||||
## 1.4.4
|
||||
|
||||
## 1.4.3
|
||||
|
||||
## 1.4.2
|
||||
|
||||
## 1.4.1
|
||||
|
||||
## 1.4.0
|
||||
|
||||
## 1.3.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [`40f0b3340`](https://github.com/navikt/Designsystemet/commit/40f0b3340d01bf85fecc04f2f3a8e8b2acf996f1) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Bump for latest-tag
|
||||
|
||||
## 1.3.38
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-codemod",
|
||||
"version": "1.3.38",
|
||||
"version": "1.5.2",
|
||||
"description": "NAV designsystem codemods",
|
||||
"author": "NAV Designsystem team",
|
||||
"license": "MIT",
|
||||
@@ -36,7 +36,7 @@
|
||||
"globby": "11.0.1",
|
||||
"inquirer": "7.3.3",
|
||||
"is-git-clean": "1.1.0",
|
||||
"jest": "^27.5.0",
|
||||
"jest": "^29.0.0",
|
||||
"jscodeshift": "0.13.1",
|
||||
"jscodeshift-add-imports": "1.0.10",
|
||||
"lodash": "4.17.21",
|
||||
@@ -44,10 +44,12 @@
|
||||
"prettier": "2.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@types/jest": "^29.0.0",
|
||||
"@types/jscodeshift": "0.11.5",
|
||||
"@types/react": "^18.0.0",
|
||||
"del": "7.0.0",
|
||||
"typescript": "^4.7.2"
|
||||
"typescript": "^4.8.0"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"engines": {
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function transformer(file, api, options) {
|
||||
});
|
||||
});
|
||||
|
||||
if (!!j(file.source).findJSXElements(localName)) {
|
||||
if (j(file.source).findJSXElements(localName)) {
|
||||
renameProps({
|
||||
root,
|
||||
componentName: localName,
|
||||
|
||||
@@ -54,7 +54,7 @@ export default function transformer(file, api, options) {
|
||||
});
|
||||
});
|
||||
|
||||
if (!!j(file.source).findJSXElements(localName)) {
|
||||
if (j(file.source).findJSXElements(localName)) {
|
||||
root.findJSXElements(`${localName}`).forEach((parent) => {
|
||||
const skip = !!parent.value.openingElement?.attributes.find(
|
||||
(x) => x.name.name === "data-version" && x.value.value === "v1"
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function transformer(file, api, options) {
|
||||
});
|
||||
});
|
||||
|
||||
if (!!j(file.source).findJSXElements(localName)) {
|
||||
if (j(file.source).findJSXElements(localName)) {
|
||||
renameProps({
|
||||
root,
|
||||
componentName: `${localName}.List`,
|
||||
|
||||
@@ -1,3 +1,47 @@
|
||||
# @navikt/ds-css
|
||||
|
||||
## 1.5.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- :memo: Rename FilterChips to ToggleChips
|
||||
|
||||
## 1.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Flex-wrap chips.css
|
||||
|
||||
## 1.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1684](https://github.com/navikt/Designsystemet/pull/1684) [`e19bf67b3`](https://github.com/navikt/Designsystemet/commit/e19bf67b337dea39989c68b5e9c2591cf0d5b40f) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :sparkles: Oppdaterte tag-varianter
|
||||
|
||||
- [#1668](https://github.com/navikt/Designsystemet/pull/1668) [`97c5f60e9`](https://github.com/navikt/Designsystemet/commit/97c5f60e9111da7e08f55c8d0aa29581f0a9b1ca) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Chips komponent :sparkles:
|
||||
|
||||
## 1.4.4
|
||||
|
||||
## 1.4.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- :sparkles: Validering og inputFormat i Date-komponenter
|
||||
|
||||
## 1.4.2
|
||||
|
||||
## 1.4.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [`3c08651df`](https://github.com/navikt/Designsystemet/commit/3c08651df28c3e19dd8c8a7a1d0032200bec473d) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Date-komponenter følger språkrådets dato-formatering for måneder.
|
||||
|
||||
## 1.4.0
|
||||
|
||||
## 1.3.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [`40f0b3340`](https://github.com/navikt/Designsystemet/commit/40f0b3340d01bf85fecc04f2f3a8e8b2acf996f1) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Bump for latest-tag
|
||||
|
||||
## 1.3.38
|
||||
@@ -0,0 +1,125 @@
|
||||
.navds-chips {
|
||||
display: flex;
|
||||
gap: var(--navds-spacing-2);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.navds-chips :where(li) {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.navds-chips__chip {
|
||||
all: unset;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.125rem;
|
||||
margin: 0;
|
||||
padding: 0 var(--navds-spacing-3);
|
||||
text-decoration: none;
|
||||
border-radius: var(--navds-border-radius-full);
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.navds-chips--small .navds-chips__chip {
|
||||
min-height: 24px;
|
||||
padding: 0 var(--navds-spacing-2);
|
||||
}
|
||||
|
||||
.navds-chips__toggle {
|
||||
box-shadow: inset 0 0 0 1px var(--ac-chip-toggle-border, var(--navds-global-color-gray-500));
|
||||
background-color: var(--ac-chip-toggle-bg, rgb(0 0 0 / 0.055));
|
||||
}
|
||||
|
||||
.navds-chips__toggle:hover {
|
||||
box-shadow: inset 0 0 0 1px var(--ac-chip-toggle-border-hover, var(--navds-global-color-gray-600));
|
||||
background-color: var(--ac-chip-toggle-bg-hover, rgb(0 0 0 / 0.1));
|
||||
}
|
||||
|
||||
.navds-chips__toggle[aria-pressed="true"] {
|
||||
box-shadow: none;
|
||||
background-color: var(--ac-chip-toggle-bg-pressed, var(--navds-global-color-deepblue-500));
|
||||
color: var(--ac-chip-toggle-text-pressed, var(--navds-global-color-white));
|
||||
}
|
||||
|
||||
.navds-chips__toggle[aria-pressed="true"]:hover {
|
||||
background-color: var(--ac-chip-toggle-bg-pressed-hover, var(--navds-global-color-deepblue-600));
|
||||
}
|
||||
|
||||
.navds-chips__toggle:focus-visible {
|
||||
box-shadow: 0 0 0 2px var(--navds-global-color-blue-800);
|
||||
}
|
||||
|
||||
.navds-chips__toggle[aria-pressed="true"]:focus-visible,
|
||||
.navds-chips__toggle:active:focus-visible {
|
||||
box-shadow: inset 0 0 0 1px var(--navds-global-color-white), 0 0 0 2px var(--navds-global-color-blue-800);
|
||||
}
|
||||
|
||||
.navds-chips__removable--action {
|
||||
background-color: var(--ac-chip-removable-action-bg, var(--navds-global-color-deepblue-500));
|
||||
color: var(--ac-chip-removable-action-text, var(--navds-global-color-white));
|
||||
}
|
||||
|
||||
.navds-chips__removable--neutral {
|
||||
color: var(--ac-chip-removable-neutral-text, var(--navds-global-color-gray-900));
|
||||
background-color: var(--ac-chip-removable-neutral-bg, rgb(0 0 0/ 0.055));
|
||||
box-shadow: inset 0 0 0 1px var(--ac-chip-removable-neutral-border, var(--navds-global-color-gray-500));
|
||||
}
|
||||
|
||||
.navds-chips__removable-icon,
|
||||
.navds-chips__toggle-icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: var(--navds-border-radius-full);
|
||||
}
|
||||
|
||||
.navds-chips--small .navds-chips__removable-icon {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.navds-chips--small .navds-chips__removable-icon > svg {
|
||||
width: 0.75rem;
|
||||
}
|
||||
|
||||
.navds-chips__removable--action:focus-visible {
|
||||
box-shadow: inset 0 0 0 1px var(--navds-global-color-white), 0 0 0 2px var(--navds-global-color-blue-800);
|
||||
}
|
||||
|
||||
.navds-chips__removable--neutral:focus-visible {
|
||||
box-shadow: 0 0 0 2px var(--navds-global-color-blue-800);
|
||||
}
|
||||
|
||||
.navds-chips__removable--action:hover {
|
||||
background-color: var(--ac-chip-removable-action-bg-hover, var(--navds-global-color-deepblue-700));
|
||||
}
|
||||
|
||||
.navds-chips__removable--neutral:hover {
|
||||
background-color: var(--ac-chip-removable-action-bg-hover, rgb(0 0 0 / 0.1));
|
||||
box-shadow: inset 0 0 0 1px var(--ac-chip-removable-neutral-border-hover, var(--navds-global-color-gray-600));
|
||||
}
|
||||
|
||||
.navds-chips--icon-left {
|
||||
padding-left: var(--navds-spacing-1);
|
||||
}
|
||||
|
||||
.navds-chips--icon-right {
|
||||
padding-right: var(--navds-spacing-1);
|
||||
}
|
||||
|
||||
.navds-chips--small .navds-chips--icon-right {
|
||||
padding-right: 0.125rem;
|
||||
}
|
||||
|
||||
.navds-chips--small .navds-chips--icon-left {
|
||||
padding-left: 0.125rem;
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
@import "button.css";
|
||||
@import "content-container.css";
|
||||
@import "chat.css";
|
||||
@import "chips.css";
|
||||
@import "guide-panel.css";
|
||||
@import "form/index.css";
|
||||
@import "help-text.css";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-css",
|
||||
"version": "1.3.38",
|
||||
"version": "1.5.2",
|
||||
"description": "Css for NAV Designsystem components",
|
||||
"author": "NAV Designsystem team",
|
||||
"keywords": [
|
||||
@@ -20,11 +20,11 @@
|
||||
"watch": "postcss --watch --use postcss-import -o dist/index.css index.css"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-tokens": "^1.3.38",
|
||||
"@navikt/ds-tokens": "^1.5.2",
|
||||
"normalize.css": "^8.0.1",
|
||||
"postcss": "^8.3.6",
|
||||
"postcss-cli": "^8.3.1",
|
||||
"postcss": "^8.4.0",
|
||||
"postcss-cli": "^9.0.0",
|
||||
"postcss-combine-duplicated-selectors": "10.0.3",
|
||||
"postcss-import": "^14.0.2"
|
||||
"postcss-import": "^14.1.0"
|
||||
}
|
||||
}
|
||||
+93
-14
@@ -14,38 +14,117 @@
|
||||
|
||||
.navds-tag {
|
||||
border: 1px solid;
|
||||
border-color: var(--navds-tag-color-border);
|
||||
border-radius: var(--navds-border-radius-medium);
|
||||
background-color: var(--navds-tag-color-background);
|
||||
border-radius: var(--navds-border-radius-small);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.125rem var(--navds-spacing-3);
|
||||
padding: 0.125rem var(--navds-spacing-2);
|
||||
min-height: 32px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.navds-tag--small {
|
||||
min-height: 24px;
|
||||
padding-right: var(--navds-spacing-1);
|
||||
padding-left: var(--navds-spacing-1);
|
||||
padding: 0 0.375rem;
|
||||
}
|
||||
|
||||
.navds-tag--xsmall {
|
||||
min-height: 20px;
|
||||
padding: 0 var(--navds-spacing-1);
|
||||
}
|
||||
|
||||
.navds-tag--error {
|
||||
background-color: var(--navds-tag-color-error-background);
|
||||
border-color: var(--navds-tag-color-error-border);
|
||||
border-color: var(--ac-tag-error-border, var(--navds-global-color-red-500));
|
||||
background-color: var(--ac-tag-error-bg, var(--navds-global-color-red-50));
|
||||
color: var(--ac-tag-error-text, var(--navds-global-color-gray-900));
|
||||
}
|
||||
|
||||
.navds-tag--error-filled {
|
||||
border-color: transparent;
|
||||
background-color: var(--ac-tag-error-filled-bg, var(--navds-global-color-red-500));
|
||||
color: var(--ac-tag-error-filled-text, var(--navds-global-color-white));
|
||||
}
|
||||
|
||||
.navds-tag--success {
|
||||
background-color: var(--navds-tag-color-success-background);
|
||||
border-color: var(--navds-tag-color-success-border);
|
||||
border-color: var(--ac-tag-success-border, var(--navds-global-color-green-500));
|
||||
background-color: var(--ac-tag-success-bg, var(--navds-global-color-green-50));
|
||||
color: var(--ac-tag-success-text, var(--navds-global-color-gray-900));
|
||||
}
|
||||
|
||||
.navds-tag--success-filled {
|
||||
border-color: transparent;
|
||||
background-color: var(--ac-tag-success-filled-bg, var(--navds-global-color-green-500));
|
||||
color: var(--ac-tag-success-filled-text, var(--navds-global-color-white));
|
||||
}
|
||||
|
||||
.navds-tag--warning {
|
||||
background-color: var(--navds-tag-color-warning-background);
|
||||
border-color: var(--navds-tag-color-warning-border);
|
||||
border-color: var(--ac-tag-warning-border, var(--navds-global-color-orange-600));
|
||||
background-color: var(--ac-tag-warning-bg, var(--navds-global-color-orange-50));
|
||||
color: var(--ac-tag-warning-text, var(--navds-global-color-gray-900));
|
||||
}
|
||||
|
||||
.navds-tag--warning-filled {
|
||||
border-color: transparent;
|
||||
background-color: var(--ac-tag-warning-filled-bg, var(--navds-global-color-orange-500));
|
||||
color: var(--ac-tag-warning-filled-text, var(--navds-global-color-gray-900));
|
||||
}
|
||||
|
||||
.navds-tag--info {
|
||||
background-color: var(--navds-tag-color-info-background);
|
||||
border-color: var(--navds-tag-color-info-border);
|
||||
border-color: var(--ac-tag-info-border, var(--navds-global-color-lightblue-700));
|
||||
background-color: var(--ac-tag-info-bg, var(--navds-global-color-lightblue-50));
|
||||
color: var(--ac-tag-info-text, var(--navds-global-color-gray-900));
|
||||
}
|
||||
|
||||
.navds-tag--info-filled {
|
||||
border-color: transparent;
|
||||
background-color: var(--ac-tag-info-filled-bg, var(--navds-global-color-lightblue-500));
|
||||
color: var(--ac-tag-info-filled-text, var(--navds-global-color-gray-900));
|
||||
}
|
||||
|
||||
.navds-tag--neutral {
|
||||
border-color: var(--ac-tag-neutral-border, var(--navds-global-color-gray-500));
|
||||
background-color: var(--ac-tag-neutral-bg, var(--navds-global-color-gray-100));
|
||||
color: var(--ac-tag-neutral-text, var(--navds-global-color-gray-900));
|
||||
}
|
||||
|
||||
.navds-tag--neutral-filled {
|
||||
border-color: transparent;
|
||||
background-color: var(--ac-tag-neutral-filled-bg, var(--navds-global-color-gray-800));
|
||||
color: var(--ac-tag-neutral-filled-text, var(--navds-global-color-white));
|
||||
}
|
||||
|
||||
.navds-tag--alt1 {
|
||||
border-color: var(--ac-tag-alt1-border, var(--navds-global-color-deepblue-400));
|
||||
background-color: var(--ac-tag-alt1-bg, var(--navds-global-color-deepblue-100));
|
||||
color: var(--ac-tag-alt1-text, var(--navds-global-color-gray-900));
|
||||
}
|
||||
|
||||
.navds-tag--alt1-filled {
|
||||
border-color: transparent;
|
||||
background-color: var(--ac-tag-alt1-filled-bg, var(--navds-global-color-deepblue-500));
|
||||
color: var(--ac-tag-alt1-filled-text, var(--navds-global-color-white));
|
||||
}
|
||||
|
||||
.navds-tag--alt2 {
|
||||
border-color: var(--ac-tag-alt2-border, var(--navds-global-color-purple-400));
|
||||
background-color: var(--ac-tag-alt2-bg, var(--navds-global-color-purple-100));
|
||||
color: var(--ac-tag-alt2-text, var(--navds-global-color-gray-900));
|
||||
}
|
||||
|
||||
.navds-tag--alt2-filled {
|
||||
border-color: transparent;
|
||||
background-color: var(--ac-tag-alt2-filled-bg, var(--navds-global-color-purple-400));
|
||||
color: var(--ac-tag-alt2-filled-text, var(--navds-global-color-white));
|
||||
}
|
||||
|
||||
.navds-tag--alt3 {
|
||||
border-color: var(--ac-tag-alt3-border, var(--navds-global-color-limegreen-500));
|
||||
background-color: var(--ac-tag-alt3-bg, var(--navds-global-color-limegreen-100));
|
||||
color: var(--ac-tag-alt3-text, var(--navds-global-color-gray-900));
|
||||
}
|
||||
|
||||
.navds-tag--alt3-filled {
|
||||
border-color: transparent;
|
||||
background-color: var(--ac-tag-alt3-filled-bg, var(--navds-global-color-limegreen-400));
|
||||
color: var(--ac-tag-alt3-filled-text, var(--navds-global-color-gray-900));
|
||||
}
|
||||
@@ -1,5 +1,90 @@
|
||||
# @navikt/ds-react
|
||||
|
||||
## 1.5.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- :memo: Rename FilterChips to ToggleChips
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-icons@1.5.2
|
||||
|
||||
## 1.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Flex-wrap chips.css
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-icons@1.5.1
|
||||
|
||||
## 1.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1684](https://github.com/navikt/Designsystemet/pull/1684) [`e19bf67b3`](https://github.com/navikt/Designsystemet/commit/e19bf67b337dea39989c68b5e9c2591cf0d5b40f) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :sparkles: Oppdaterte tag-varianter
|
||||
|
||||
- [#1668](https://github.com/navikt/Designsystemet/pull/1668) [`97c5f60e9`](https://github.com/navikt/Designsystemet/commit/97c5f60e9111da7e08f55c8d0aa29581f0a9b1ca) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Chips komponent :sparkles:
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-icons@1.5.0
|
||||
|
||||
## 1.4.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Eksponerer validation-typer
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-icons@1.4.4
|
||||
|
||||
## 1.4.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- :sparkles: Validering og inputFormat i Date-komponenter
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-icons@1.4.3
|
||||
|
||||
## 1.4.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-icons@1.4.2
|
||||
|
||||
## 1.4.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [`3c08651df`](https://github.com/navikt/Designsystemet/commit/3c08651df28c3e19dd8c8a7a1d0032200bec473d) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Date-komponenter følger språkrådets dato-formatering for måneder.
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-icons@1.4.1
|
||||
|
||||
## 1.4.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [`1bdb7e377`](https://github.com/navikt/Designsystemet/commit/1bdb7e3777ece28d153991b78dbdd289366fca57) Thanks [@KenAJoh](https://github.com/KenAJoh)! - [#1702](https://github.com/navikt/Designsystemet/pull/1702) [`20bcc28cb`](https://github.com/navikt/Designsystemet/commit/20bcc28cb0a886aa40d2c1b042fb2706a144c014) Thanks [@andnorda](https://github.com/andnorda)! - Global Provider komponent for håndtering av global config
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-icons@1.4.0
|
||||
|
||||
## 1.3.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [`40f0b3340`](https://github.com/navikt/Designsystemet/commit/40f0b3340d01bf85fecc04f2f3a8e8b2acf996f1) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Bump for latest-tag
|
||||
|
||||
- Updated dependencies [[`40f0b3340`](https://github.com/navikt/Designsystemet/commit/40f0b3340d01bf85fecc04f2f3a8e8b2acf996f1)]:
|
||||
- @navikt/ds-icons@1.3.39
|
||||
|
||||
## 1.3.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-react",
|
||||
"version": "1.3.38",
|
||||
"version": "1.5.2",
|
||||
"description": "NAV designsystem react components",
|
||||
"author": "NAV Designsystem team",
|
||||
"license": "MIT",
|
||||
@@ -24,19 +24,20 @@
|
||||
"/cjs",
|
||||
"/esm",
|
||||
"/src",
|
||||
"_docs.json"
|
||||
"_docs.json",
|
||||
"!**/*.tsbuildinfo"
|
||||
],
|
||||
"scripts": {
|
||||
"docgen": "yarn ts-node ../../../scripts/docgen.ts",
|
||||
"create-package-json-pointers-to-esm": "node ../../../scripts/createPackageJsonsWithESMPointers.js",
|
||||
"clean": "rimraf cjs esm",
|
||||
"build": "yarn run clean && concurrently \"tsc\" \"tsc -p tsconfig.esm.json\" && yarn create-package-json-pointers-to-esm",
|
||||
"build": "concurrently \"tsc\" \"tsc -p tsconfig.esm.json\" && yarn create-package-json-pointers-to-esm",
|
||||
"watch": "tsc --watch -p tsconfig.esm.json",
|
||||
"test": "jest"
|
||||
"test": "TZ=UTC jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/react-dom-interactions": "0.9.2",
|
||||
"@navikt/ds-icons": "^1.3.38",
|
||||
"@navikt/ds-icons": "^1.5.2",
|
||||
"@radix-ui/react-tabs": "1.0.0",
|
||||
"@radix-ui/react-toggle-group": "1.0.0",
|
||||
"clsx": "^1.2.1",
|
||||
@@ -50,21 +51,22 @@
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^14.2.0",
|
||||
"@types/faker": "5.5.8",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/jest": "^29.0.0",
|
||||
"@types/node": "^17.0.35",
|
||||
"@types/react-modal": "^3.13.1",
|
||||
"concurrently": "7.2.1",
|
||||
"copyfiles": "^2.4.1",
|
||||
"faker": "5.5.3",
|
||||
"fast-glob": "3.2.11",
|
||||
"jest": "^27.5.0",
|
||||
"jest": "^29.0.0",
|
||||
"jest-environment-jsdom": "^29.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"rimraf": "3.0.2",
|
||||
"swr": "^1.1.2",
|
||||
"ts-jest": "^27.1.0",
|
||||
"ts-node": "10.7.0",
|
||||
"typescript": "^4.7.2"
|
||||
"ts-jest": "^29.0.0",
|
||||
"ts-node": "10.9.0",
|
||||
"typescript": "^4.8.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.30 || ^18.0.0",
|
||||
|
||||
@@ -32,7 +32,6 @@ const Bubble: BubbleType = forwardRef(
|
||||
ref={ref}
|
||||
className={cl("navds-chat__bubble", className)}
|
||||
style={{ backgroundColor: backgroundColor }}
|
||||
tabIndex={0}
|
||||
{...rest}
|
||||
>
|
||||
{(timestamp || name) && (
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef, HTMLAttributes } from "react";
|
||||
import ToggleChips, { ToggleChipsType } from "./Toggle";
|
||||
import RemovableChips, { RemovableChipsType } from "./Removable";
|
||||
|
||||
export interface ChipsProps extends HTMLAttributes<HTMLUListElement> {
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Changes padding and font-sizes
|
||||
* @default "medium"
|
||||
*/
|
||||
size?: "medium" | "small";
|
||||
}
|
||||
|
||||
interface ChipsComponent
|
||||
extends React.ForwardRefExoticComponent<
|
||||
ChipsProps & React.RefAttributes<HTMLUListElement>
|
||||
> {
|
||||
Toggle: ToggleChipsType;
|
||||
Removable: RemovableChipsType;
|
||||
}
|
||||
|
||||
export const Chips: ChipsComponent = forwardRef<HTMLUListElement, ChipsProps>(
|
||||
({ className, size = "medium", children, ...rest }, ref) => {
|
||||
return (
|
||||
<ul
|
||||
{...rest}
|
||||
ref={ref}
|
||||
className={cl("navds-chips", className, `navds-chips--${size}`, {
|
||||
"navds-body-short navds-body-short--small": size === "medium",
|
||||
"navds-detail navds-detail--small": size === "small",
|
||||
})}
|
||||
>
|
||||
{React.Children.map(children, (chip, index) => {
|
||||
return <li key={index + (chip?.toString() ?? "")}>{chip}</li>;
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
) as ChipsComponent;
|
||||
|
||||
Chips.Toggle = ToggleChips;
|
||||
Chips.Removable = RemovableChips;
|
||||
|
||||
export default Chips;
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Close } from "@navikt/ds-icons";
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef } from "react";
|
||||
import { OverridableComponent } from "..";
|
||||
|
||||
export interface RemovableChipsProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children: string;
|
||||
/**
|
||||
* Chip-variants
|
||||
* @default "action"
|
||||
*/
|
||||
variant?: "action" | "neutral";
|
||||
/**
|
||||
* Replaces label read for screen-readers
|
||||
* @default "slett filter"
|
||||
*/
|
||||
removeLabel?: string;
|
||||
/**
|
||||
* Click callback
|
||||
*/
|
||||
onDelete?: () => void;
|
||||
}
|
||||
|
||||
export interface RemovableChipsType
|
||||
extends OverridableComponent<RemovableChipsProps, HTMLButtonElement> {}
|
||||
|
||||
export const RemovableChips: OverridableComponent<
|
||||
RemovableChipsProps,
|
||||
HTMLButtonElement
|
||||
> = forwardRef(
|
||||
(
|
||||
{
|
||||
className,
|
||||
children,
|
||||
variant = "action",
|
||||
as: Component = "button",
|
||||
removeLabel = "slett",
|
||||
onDelete,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<Component
|
||||
{...rest}
|
||||
ref={ref}
|
||||
className={cl(
|
||||
"navds-chips__chip navds-chips__removable navds-chips--icon-right",
|
||||
className,
|
||||
`navds-chips__removable--${variant}`
|
||||
)}
|
||||
aria-label={`${children} ${removeLabel}`}
|
||||
onClick={(e) => {
|
||||
onDelete?.();
|
||||
rest?.onClick?.(e);
|
||||
}}
|
||||
>
|
||||
<span className="navds-chips__chip-text">{children}</span>
|
||||
<span className="navds-chips__removable-icon">
|
||||
<Close aria-hidden />
|
||||
</span>
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
) as RemovableChipsType;
|
||||
|
||||
export default RemovableChips;
|
||||
@@ -0,0 +1,41 @@
|
||||
import { SuccessStroke } from "@navikt/ds-icons";
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef } from "react";
|
||||
import { OverridableComponent } from "..";
|
||||
|
||||
export interface ToggleChipsProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children: string;
|
||||
/**
|
||||
* Toggles aria-pressed and visual-changes
|
||||
*/
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
export interface ToggleChipsType
|
||||
extends OverridableComponent<ToggleChipsProps, HTMLButtonElement> {}
|
||||
|
||||
export const ToggleChips: ToggleChipsType = forwardRef(
|
||||
(
|
||||
{ className, children, selected, as: Component = "button", ...rest },
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<Component
|
||||
{...rest}
|
||||
ref={ref}
|
||||
className={cl("navds-chips__chip navds-chips__toggle", className, {
|
||||
"navds-chips--icon-left": selected,
|
||||
})}
|
||||
aria-pressed={selected}
|
||||
>
|
||||
{selected && (
|
||||
<SuccessStroke aria-hidden className="navds-chips__toggle-icon" />
|
||||
)}
|
||||
<span className="navds-chips__chip-text">{children}</span>
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default ToggleChips;
|
||||
@@ -0,0 +1,165 @@
|
||||
import React, { useState } from "react";
|
||||
import { Chips } from ".";
|
||||
|
||||
export default {
|
||||
title: "ds-react/Chips",
|
||||
component: Chips,
|
||||
argTypes: {
|
||||
type: {
|
||||
control: {
|
||||
type: "radio",
|
||||
options: ["toggle", "removable"],
|
||||
},
|
||||
},
|
||||
size: {
|
||||
control: {
|
||||
type: "radio",
|
||||
options: ["medium", "small"],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const options = ["Norsk", "Dansk", "Svensk", "Tysk", "Spansk"];
|
||||
|
||||
export const Default = (props) => {
|
||||
const [selected, setSelected] = useState(["Dansk", "Svensk"]);
|
||||
const [filter, setFilter] = useState(options);
|
||||
|
||||
if (props.type === "toggle") {
|
||||
return (
|
||||
<Chips size={props.size ?? "medium"}>
|
||||
{options.map((c) => (
|
||||
<Chips.Toggle
|
||||
selected={selected.includes(c)}
|
||||
key={c}
|
||||
onClick={() =>
|
||||
setSelected(
|
||||
selected.includes(c)
|
||||
? selected.filter((x) => x !== c)
|
||||
: [...selected, c]
|
||||
)
|
||||
}
|
||||
>
|
||||
{c}
|
||||
</Chips.Toggle>
|
||||
))}
|
||||
</Chips>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Chips size={props.size ?? "medium"}>
|
||||
{filter.map((c) => (
|
||||
<Chips.Removable
|
||||
key={c}
|
||||
onClick={() => setFilter((x) => x.filter((y) => y !== c))}
|
||||
>
|
||||
{c}
|
||||
</Chips.Removable>
|
||||
))}
|
||||
</Chips>
|
||||
);
|
||||
};
|
||||
|
||||
export const Toggle = () => {
|
||||
const [selected, setSelected] = useState<number[]>([]);
|
||||
return (
|
||||
<Chips>
|
||||
{options.map((c, y) => (
|
||||
<Chips.Toggle
|
||||
selected={selected.includes(y)}
|
||||
onClick={() =>
|
||||
setSelected(
|
||||
selected.includes(y)
|
||||
? selected.filter((x) => x !== y)
|
||||
: [...selected, y]
|
||||
)
|
||||
}
|
||||
key={y}
|
||||
>
|
||||
{c}
|
||||
</Chips.Toggle>
|
||||
))}
|
||||
</Chips>
|
||||
);
|
||||
};
|
||||
|
||||
export const Removable = () => {
|
||||
return (
|
||||
<div className="colgap">
|
||||
<Chips>
|
||||
{options.map((c, y) => (
|
||||
<Chips.Removable key={y}>{c}</Chips.Removable>
|
||||
))}
|
||||
</Chips>
|
||||
<Chips>
|
||||
{options.map((c, y) => (
|
||||
<Chips.Removable variant="neutral" key={y}>
|
||||
{c}
|
||||
</Chips.Removable>
|
||||
))}
|
||||
</Chips>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Regular = () => {
|
||||
const [selected, setSelected] = useState<number[]>([]);
|
||||
return (
|
||||
<div className="colgap">
|
||||
<Chips>
|
||||
{options.map((c, y) => (
|
||||
<Chips.Removable key={y}>{c}</Chips.Removable>
|
||||
))}
|
||||
</Chips>
|
||||
<Chips>
|
||||
{options.map((c, y) => (
|
||||
<Chips.Toggle
|
||||
selected={selected.includes(y)}
|
||||
onClick={() =>
|
||||
setSelected(
|
||||
selected.includes(y)
|
||||
? selected.filter((x) => x !== y)
|
||||
: [...selected, y]
|
||||
)
|
||||
}
|
||||
key={y}
|
||||
>
|
||||
{c}
|
||||
</Chips.Toggle>
|
||||
))}
|
||||
</Chips>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Small = () => {
|
||||
const [selected, setSelected] = useState<number[]>([]);
|
||||
return (
|
||||
<div className="colgap">
|
||||
<Chips size="small">
|
||||
{options.map((c, y) => (
|
||||
<Chips.Removable key={y}>{c}</Chips.Removable>
|
||||
))}
|
||||
</Chips>
|
||||
<Chips size="small">
|
||||
{options.map((c, y) => (
|
||||
<Chips.Toggle
|
||||
selected={selected.includes(y)}
|
||||
onClick={() =>
|
||||
setSelected(
|
||||
selected.includes(y)
|
||||
? selected.filter((x) => x !== y)
|
||||
: [...selected, y]
|
||||
)
|
||||
}
|
||||
key={y}
|
||||
>
|
||||
{c}
|
||||
</Chips.Toggle>
|
||||
))}
|
||||
</Chips>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Chips, ChipsProps } from "./Chips";
|
||||
@@ -1,4 +1,3 @@
|
||||
import isSaturday from "date-fns/isSaturday";
|
||||
import React, { useId, useState } from "react";
|
||||
import { UNSAFE_useDatepicker, UNSAFE_useRangeDatepicker } from "..";
|
||||
import { Button } from "../..";
|
||||
@@ -221,8 +220,13 @@ export const UserControlled = () => {
|
||||
};
|
||||
|
||||
export const Validering = () => {
|
||||
const { datepickerProps, selectedDay, inputProps } = UNSAFE_useDatepicker({
|
||||
fromDate: new Date("Aug 23 2019"),
|
||||
const [error, setError] = useState(false);
|
||||
const { datepickerProps, inputProps } = UNSAFE_useDatepicker({
|
||||
fromDate: new Date("Aug 2 2019"),
|
||||
onValidate: (val) => setError(val.isWeekend),
|
||||
defaultSelected: new Date("Nov 26 2022"),
|
||||
disableWeekends: true,
|
||||
onDateChange: console.log,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -230,8 +234,8 @@ export const Validering = () => {
|
||||
<DatePicker {...datepickerProps}>
|
||||
<DatePicker.Input
|
||||
error={
|
||||
selectedDay && isSaturday(selectedDay)
|
||||
? "NAV-kontoret er ikke åpent på lørdager. Velg en annen dag."
|
||||
error
|
||||
? "NAV-kontoret er ikke åpent i helger. Velg en annen dag."
|
||||
: undefined
|
||||
}
|
||||
{...inputProps}
|
||||
@@ -261,3 +265,24 @@ export const ErrorInput = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const UseRangedDatepickerValidation = () => {
|
||||
const { datepickerProps, fromInputProps, toInputProps } =
|
||||
UNSAFE_useRangeDatepicker({
|
||||
fromDate: new Date("Aug 23 2019"),
|
||||
disableWeekends: true,
|
||||
disabled: [new Date("Oct 10 2022")],
|
||||
onValidate: console.table,
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", gap: "1rem" }}>
|
||||
<DatePicker {...datepickerProps}>
|
||||
<div style={{ display: "flex", gap: "1rem" }}>
|
||||
<DatePicker.Input {...fromInputProps} label="Fra" />
|
||||
<DatePicker.Input {...toInputProps} label="Til" />
|
||||
</div>
|
||||
</DatePicker>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,9 @@
|
||||
export { useDatepicker as UNSAFE_useDatepicker } from "./useDatepicker";
|
||||
export type { DateValidationT } from "./useDatepicker";
|
||||
export { useRangeDatepicker as UNSAFE_useRangeDatepicker } from "./useRangeDatepicker";
|
||||
export type { RangeValidationT } from "./useRangeDatepicker";
|
||||
export { useMonthpicker as UNSAFE_useMonthpicker } from "./useMonthPicker";
|
||||
export type { MonthValidationT } from "./useMonthPicker";
|
||||
export { useDateInputContext, DateContext } from "./useDateInputContext";
|
||||
export {
|
||||
useSharedMonthContext,
|
||||
|
||||
@@ -36,6 +36,15 @@ export interface UseDatepickerOptions
|
||||
* Callback for changed state
|
||||
*/
|
||||
onDateChange?: (val?: Date) => void;
|
||||
/**
|
||||
* Input-format
|
||||
* @default "dd.MM.yyyy"
|
||||
*/
|
||||
inputFormat?: string;
|
||||
/**
|
||||
* validation-callback
|
||||
*/
|
||||
onValidate?: (val: DateValidationT) => void;
|
||||
}
|
||||
|
||||
interface UseDatepickerValue {
|
||||
@@ -62,6 +71,27 @@ interface UseDatepickerValue {
|
||||
setSelected: (date?: Date) => void;
|
||||
}
|
||||
|
||||
export type DateValidationT = {
|
||||
isDisabled: boolean;
|
||||
isWeekend: boolean;
|
||||
isEmpty: boolean;
|
||||
isInvalid: boolean;
|
||||
isValidDate: boolean;
|
||||
isBefore: boolean;
|
||||
isAfter: boolean;
|
||||
};
|
||||
|
||||
const getValidationMessage = (val = {}): DateValidationT => ({
|
||||
isDisabled: false,
|
||||
isWeekend: false,
|
||||
isEmpty: false,
|
||||
isInvalid: false,
|
||||
isBefore: false,
|
||||
isAfter: false,
|
||||
isValidDate: true,
|
||||
...val,
|
||||
});
|
||||
|
||||
export const useDatepicker = (
|
||||
opt: UseDatepickerOptions = {}
|
||||
): UseDatepickerValue => {
|
||||
@@ -75,6 +105,8 @@ export const useDatepicker = (
|
||||
disabled,
|
||||
disableWeekends,
|
||||
onDateChange,
|
||||
inputFormat,
|
||||
onValidate,
|
||||
} = opt;
|
||||
|
||||
const locale = getLocaleFromString(_locale);
|
||||
@@ -90,7 +122,7 @@ export const useDatepicker = (
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const defaultInputValue = defaultSelected
|
||||
? formatDateForInput(defaultSelected, locale, "date")
|
||||
? formatDateForInput(defaultSelected, locale, "date", inputFormat)
|
||||
: "";
|
||||
const [inputValue, setInputValue] = useState(defaultInputValue);
|
||||
|
||||
@@ -99,6 +131,11 @@ export const useDatepicker = (
|
||||
setSelectedDay(date);
|
||||
};
|
||||
|
||||
const updateValidation = (val: Partial<DateValidationT> = {}) => {
|
||||
const msg = getValidationMessage(val);
|
||||
onValidate?.(msg);
|
||||
};
|
||||
|
||||
const handleFocusIn = useCallback(
|
||||
(e) => {
|
||||
if (!e?.target || !e?.target?.nodeType) {
|
||||
@@ -134,7 +171,9 @@ export const useDatepicker = (
|
||||
const setSelected = (date: Date | undefined) => {
|
||||
updateDate(date);
|
||||
setMonth(date ?? today);
|
||||
setInputValue(date ? formatDateForInput(date, locale, "date") : "");
|
||||
setInputValue(
|
||||
date ? formatDateForInput(date, locale, "date", inputFormat) : ""
|
||||
);
|
||||
};
|
||||
|
||||
const handleFocus: React.FocusEventHandler<HTMLInputElement> = (e) => {
|
||||
@@ -143,13 +182,14 @@ export const useDatepicker = (
|
||||
let day = parseDate(e.target.value, today, locale, "date");
|
||||
if (isValidDate(day)) {
|
||||
setMonth(day);
|
||||
setInputValue(formatDateForInput(day, locale, "date"));
|
||||
setInputValue(formatDateForInput(day, locale, "date", inputFormat));
|
||||
}
|
||||
};
|
||||
|
||||
const handleBlur: React.FocusEventHandler<HTMLInputElement> = (e) => {
|
||||
let day = parseDate(e.target.value, today, locale, "date");
|
||||
isValidDate(day) && setInputValue(formatDateForInput(day, locale, "date"));
|
||||
isValidDate(day) &&
|
||||
setInputValue(formatDateForInput(day, locale, "date", inputFormat));
|
||||
};
|
||||
|
||||
/* Only allow de-selecting if not required */
|
||||
@@ -162,11 +202,15 @@ export const useDatepicker = (
|
||||
if (!required && selected) {
|
||||
updateDate(undefined);
|
||||
setInputValue("");
|
||||
updateValidation({ isValidDate: false, isEmpty: true });
|
||||
return;
|
||||
}
|
||||
updateDate(day);
|
||||
updateValidation();
|
||||
setMonth(day);
|
||||
setInputValue(day ? formatDateForInput(day, locale, "date") : "");
|
||||
setInputValue(
|
||||
day ? formatDateForInput(day, locale, "date", inputFormat) : ""
|
||||
);
|
||||
};
|
||||
|
||||
// When changing the input field, save its value in state and check if the
|
||||
@@ -176,22 +220,39 @@ export const useDatepicker = (
|
||||
setInputValue(e.target.value);
|
||||
const day = parseDate(e.target.value, today, locale, "date");
|
||||
|
||||
const isBefore =
|
||||
fromDate && day && differenceInCalendarDays(fromDate, day) > 0;
|
||||
const isAfter = toDate && day && differenceInCalendarDays(day, toDate) > 0;
|
||||
|
||||
if (
|
||||
!isValidDate(day) ||
|
||||
(disabled &&
|
||||
((disableWeekends && isWeekend(day)) || isMatch(day, disabled)))
|
||||
(disableWeekends && isWeekend(day)) ||
|
||||
(disabled && isMatch(day, disabled))
|
||||
) {
|
||||
updateDate(undefined);
|
||||
updateValidation({
|
||||
isInvalid: isValidDate(day),
|
||||
isWeekend: disableWeekends && isWeekend(day),
|
||||
isDisabled: disabled && isMatch(day, disabled),
|
||||
isValidDate: false,
|
||||
isEmpty: !e.target.value,
|
||||
isBefore: isBefore ?? false,
|
||||
isAfter: isAfter ?? false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const isBefore = fromDate && differenceInCalendarDays(fromDate, day) > 0;
|
||||
const isAfter = toDate && differenceInCalendarDays(day, toDate) > 0;
|
||||
if (isBefore || isAfter) {
|
||||
updateDate(undefined);
|
||||
updateValidation({
|
||||
isValidDate: false,
|
||||
isBefore: isBefore ?? false,
|
||||
isAfter: isAfter ?? false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
updateDate(day);
|
||||
updateValidation();
|
||||
setMonth(day);
|
||||
};
|
||||
|
||||
@@ -217,13 +278,15 @@ export const useDatepicker = (
|
||||
month,
|
||||
onMonthChange: (month) => setMonth(month),
|
||||
onDayClick: handleDayClick,
|
||||
selected: selectedDay,
|
||||
selected: selectedDay ?? new Date("Invalid date"),
|
||||
locale: _locale,
|
||||
fromDate,
|
||||
toDate,
|
||||
today,
|
||||
open,
|
||||
onOpenToggle: () => setOpen((x) => !x),
|
||||
disabled,
|
||||
disableWeekends,
|
||||
ref: daypickerRef,
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,15 @@ export interface UseMonthPickerOptions
|
||||
* Callback for month-change
|
||||
*/
|
||||
onMonthChange?: (date?: Date) => void;
|
||||
/**
|
||||
* Input-format
|
||||
* @default "MMMM yyyy"
|
||||
*/
|
||||
inputFormat?: string;
|
||||
/**
|
||||
* validation-callback
|
||||
*/
|
||||
onValidate?: (val: MonthValidationT) => void;
|
||||
}
|
||||
|
||||
interface UseMonthPickerValue {
|
||||
@@ -48,6 +57,25 @@ interface UseMonthPickerValue {
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
export type MonthValidationT = {
|
||||
isDisabled: boolean;
|
||||
isEmpty: boolean;
|
||||
isInvalid: boolean;
|
||||
isValidMonth: boolean;
|
||||
isBefore: boolean;
|
||||
isAfter: boolean;
|
||||
};
|
||||
|
||||
const getValidationMessage = (val = {}): MonthValidationT => ({
|
||||
isDisabled: false,
|
||||
isEmpty: false,
|
||||
isInvalid: false,
|
||||
isBefore: false,
|
||||
isAfter: false,
|
||||
isValidMonth: true,
|
||||
...val,
|
||||
});
|
||||
|
||||
export const useMonthpicker = (
|
||||
opt: UseMonthPickerOptions = {}
|
||||
): UseMonthPickerValue => {
|
||||
@@ -59,6 +87,8 @@ export const useMonthpicker = (
|
||||
disabled,
|
||||
required,
|
||||
onMonthChange,
|
||||
inputFormat,
|
||||
onValidate,
|
||||
} = opt;
|
||||
|
||||
const [defaultSelected, setDefaultSelected] = useState(_defaultSelected);
|
||||
@@ -75,7 +105,7 @@ export const useMonthpicker = (
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const defaultInputValue = defaultSelected
|
||||
? formatDateForInput(defaultSelected, locale, "month")
|
||||
? formatDateForInput(defaultSelected, locale, "month", inputFormat)
|
||||
: "";
|
||||
|
||||
const [inputValue, setInputValue] = useState(defaultInputValue);
|
||||
@@ -85,6 +115,11 @@ export const useMonthpicker = (
|
||||
setSelectedMonth(date);
|
||||
};
|
||||
|
||||
const updateValidation = (val: Partial<MonthValidationT> = {}) => {
|
||||
const msg = getValidationMessage(val);
|
||||
onValidate?.(msg);
|
||||
};
|
||||
|
||||
const handleFocusIn = useCallback(
|
||||
(e) => {
|
||||
if (!e?.target || !e?.target?.nodeType) {
|
||||
@@ -120,7 +155,9 @@ export const useMonthpicker = (
|
||||
const setSelected = (date: Date | undefined) => {
|
||||
updateMonth(date);
|
||||
setYear(date ?? today);
|
||||
setInputValue(date ? formatDateForInput(date, locale, "month") : "");
|
||||
setInputValue(
|
||||
date ? formatDateForInput(date, locale, "month", inputFormat) : ""
|
||||
);
|
||||
};
|
||||
|
||||
const handleFocus: React.FocusEventHandler<HTMLInputElement> = (e) => {
|
||||
@@ -128,13 +165,14 @@ export const useMonthpicker = (
|
||||
let day = parseDate(e.target.value, today, locale, "month");
|
||||
if (isValidDate(day)) {
|
||||
setYear(day);
|
||||
setInputValue(formatDateForInput(day, locale, "month"));
|
||||
setInputValue(formatDateForInput(day, locale, "month", inputFormat));
|
||||
}
|
||||
};
|
||||
|
||||
const handleBlur: React.FocusEventHandler<HTMLInputElement> = (e) => {
|
||||
let day = parseDate(e.target.value, today, locale, "month");
|
||||
isValidDate(day) && setInputValue(formatDateForInput(day, locale, "month"));
|
||||
isValidDate(day) &&
|
||||
setInputValue(formatDateForInput(day, locale, "month", inputFormat));
|
||||
};
|
||||
|
||||
/* Only allow de-selecting if not required */
|
||||
@@ -146,11 +184,15 @@ export const useMonthpicker = (
|
||||
|
||||
if (!required && !month) {
|
||||
updateMonth(undefined);
|
||||
updateValidation({ isValidMonth: false, isEmpty: true });
|
||||
setInputValue("");
|
||||
return;
|
||||
}
|
||||
updateMonth(month);
|
||||
setInputValue(month ? formatDateForInput(month, locale, "month") : "");
|
||||
updateValidation();
|
||||
setInputValue(
|
||||
month ? formatDateForInput(month, locale, "month", inputFormat) : ""
|
||||
);
|
||||
};
|
||||
|
||||
// When changing the input field, save its value in state and check if the
|
||||
@@ -160,32 +202,48 @@ export const useMonthpicker = (
|
||||
setInputValue(e.target.value);
|
||||
const month = parseDate(e.target.value, today, locale, "month");
|
||||
|
||||
if (!isValidDate(month) || (disabled && isMatch(month, disabled))) {
|
||||
updateMonth(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const isBefore =
|
||||
fromDate &&
|
||||
month &&
|
||||
(fromDate.getFullYear() > month.getFullYear() ||
|
||||
(fromDate.getFullYear() === month.getFullYear() &&
|
||||
fromDate.getMonth() > month.getMonth()));
|
||||
|
||||
const isAfter =
|
||||
toDate &&
|
||||
month &&
|
||||
(toDate.getFullYear() < month.getFullYear() ||
|
||||
(toDate.getFullYear() === month.getFullYear() &&
|
||||
toDate.getMonth() < month.getMonth()));
|
||||
|
||||
if (!isValidDate(month) || (disabled && isMatch(month, disabled))) {
|
||||
updateMonth(undefined);
|
||||
updateValidation({
|
||||
isInvalid: isValidDate(month),
|
||||
isDisabled: disabled && isMatch(month, disabled),
|
||||
isValidMonth: false,
|
||||
isEmpty: !e.target.value,
|
||||
isBefore: isBefore ?? false,
|
||||
isAfter: isAfter ?? false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
isAfter ||
|
||||
isBefore ||
|
||||
(fromDate && toDate && !isMatch(month, [{ from: fromDate, to: toDate }]))
|
||||
) {
|
||||
updateMonth(undefined);
|
||||
updateValidation({
|
||||
isValidMonth: false,
|
||||
isBefore: isBefore ?? false,
|
||||
isAfter: isAfter ?? false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
updateMonth(month);
|
||||
updateValidation();
|
||||
setYear(month);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/* eslint-disable react/jsx-pascal-case */
|
||||
import { act, render } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import React from "react";
|
||||
import { UNSAFE_DatePicker, UNSAFE_useRangeDatepicker } from "..";
|
||||
|
||||
const RangeDemo = () => {
|
||||
const { datepickerProps, fromInputProps, selectedRange, toInputProps } =
|
||||
UNSAFE_useRangeDatepicker({
|
||||
fromDate: new Date("Aug 23 2019"),
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", gap: "1rem" }}>
|
||||
<UNSAFE_DatePicker {...datepickerProps}>
|
||||
<UNSAFE_DatePicker.Input {...fromInputProps} label="Fra" />
|
||||
<UNSAFE_DatePicker.Input {...toInputProps} label="Til" />
|
||||
</UNSAFE_DatePicker>
|
||||
<div title="res">{JSON.stringify(selectedRange)}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
describe("Writing in input sets correct values", () => {
|
||||
it("useRangeDatepicker same date", async () => {
|
||||
const utils = render(<RangeDemo />);
|
||||
|
||||
const fraInput = utils.getByRole("textbox", { name: "Fra" });
|
||||
const tilInput = utils.getByRole("textbox", { name: "Til" });
|
||||
await act(async () => {
|
||||
await userEvent.type(fraInput, "03.08.2022");
|
||||
await userEvent.type(tilInput, "03.08.2022");
|
||||
});
|
||||
const res = utils.getByTitle("res");
|
||||
expect(res.innerHTML).toEqual(
|
||||
JSON.stringify({
|
||||
from: "2022-08-03T00:00:00.000Z",
|
||||
to: "2022-08-03T00:00:00.000Z",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("useRangeDatepicker before after to ", async () => {
|
||||
const utils = render(<RangeDemo />);
|
||||
|
||||
const fraInput = utils.getByRole("textbox", { name: "Fra" });
|
||||
const tilInput = utils.getByRole("textbox", { name: "Til" });
|
||||
await act(async () => {
|
||||
await userEvent.type(fraInput, "03.08.2022");
|
||||
await userEvent.type(tilInput, "02.08.2022");
|
||||
});
|
||||
const res = utils.getByTitle("res");
|
||||
expect(res.innerHTML).toEqual(
|
||||
JSON.stringify({
|
||||
from: "2022-08-03T00:00:00.000Z",
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -10,10 +10,42 @@ import {
|
||||
isValidDate,
|
||||
parseDate,
|
||||
} from "../utils";
|
||||
import { UseDatepickerOptions } from "./useDatepicker";
|
||||
import { DateValidationT, UseDatepickerOptions } from "./useDatepicker";
|
||||
|
||||
export type RangeValidationT = {
|
||||
from: DateValidationT;
|
||||
to: DateValidationT & { isBeforeFrom?: boolean };
|
||||
};
|
||||
|
||||
const getValidationMessage = (from = {}, to = {}): RangeValidationT => ({
|
||||
from: {
|
||||
isDisabled: false,
|
||||
isWeekend: false,
|
||||
isEmpty: false,
|
||||
isInvalid: false,
|
||||
isBefore: false,
|
||||
isAfter: false,
|
||||
isValidDate: true,
|
||||
...from,
|
||||
},
|
||||
to: {
|
||||
isDisabled: false,
|
||||
isWeekend: false,
|
||||
isEmpty: false,
|
||||
isInvalid: false,
|
||||
isBefore: false,
|
||||
isAfter: false,
|
||||
isBeforeFrom: false,
|
||||
isValidDate: true,
|
||||
...to,
|
||||
},
|
||||
});
|
||||
|
||||
interface UseRangeDatepickerOptions
|
||||
extends Omit<UseDatepickerOptions, "defaultSelected" | "onDateChange"> {
|
||||
extends Omit<
|
||||
UseDatepickerOptions,
|
||||
"defaultSelected" | "onDateChange" | "onValidate"
|
||||
> {
|
||||
/**
|
||||
* The initially selected DateRange
|
||||
*/
|
||||
@@ -22,6 +54,10 @@ interface UseRangeDatepickerOptions
|
||||
* Callback for changed state
|
||||
*/
|
||||
onRangeChange?: (val?: DateRange) => void;
|
||||
/**
|
||||
* validation-callback
|
||||
*/
|
||||
onValidate?: (val: RangeValidationT) => void;
|
||||
}
|
||||
|
||||
interface UseRangeDatepickerValue {
|
||||
@@ -65,6 +101,92 @@ const RANGE = {
|
||||
|
||||
type RangeT = typeof RANGE[keyof typeof RANGE];
|
||||
|
||||
const fromValidation = (day: Date, opt?: UseRangeDatepickerOptions) => {
|
||||
const isBefore =
|
||||
opt?.fromDate && day && differenceInCalendarDays(opt?.fromDate, day) > 0;
|
||||
const isAfter =
|
||||
opt?.toDate && day && differenceInCalendarDays(day, opt?.toDate) > 0;
|
||||
|
||||
if (
|
||||
isValidDate(day) &&
|
||||
!(opt?.disableWeekends && isWeekend(day)) &&
|
||||
!(opt?.disabled && isMatch(day, opt.disabled))
|
||||
) {
|
||||
return {
|
||||
isValidDate: false,
|
||||
isInvalid: !isValidDate(day),
|
||||
isWeekend: opt?.disableWeekends && isWeekend(day),
|
||||
isDisabled: opt?.disabled && isMatch(day, opt.disabled),
|
||||
isBefore: isBefore,
|
||||
isAfter: isAfter,
|
||||
};
|
||||
}
|
||||
if (isBefore || isAfter) {
|
||||
return {
|
||||
isValidDate: false,
|
||||
isBefore: isBefore,
|
||||
isAfter: isAfter,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const toValidation = (
|
||||
day: Date,
|
||||
from: Date,
|
||||
opt?: UseRangeDatepickerOptions
|
||||
) => {
|
||||
const isBefore =
|
||||
opt?.fromDate && day && differenceInCalendarDays(opt?.fromDate, day) > 0;
|
||||
const isAfter =
|
||||
opt?.toDate && day && differenceInCalendarDays(day, opt?.toDate) > 0;
|
||||
|
||||
const isBeforeFrom =
|
||||
(from && differenceInCalendarDays(from, day) > 0) ?? false;
|
||||
|
||||
if (
|
||||
isValidDate(day) &&
|
||||
!(opt?.disableWeekends && isWeekend(day)) &&
|
||||
!(opt?.disabled && isMatch(day, opt.disabled))
|
||||
) {
|
||||
return {
|
||||
isValidDate: false,
|
||||
isInvalid: !isValidDate(day),
|
||||
isWeekend: opt?.disableWeekends && isWeekend(day),
|
||||
isDisabled: opt?.disabled && isMatch(day, opt.disabled),
|
||||
isBefore,
|
||||
isAfter,
|
||||
isBeforeFrom,
|
||||
};
|
||||
}
|
||||
if (isBefore || isAfter || isBeforeFrom) {
|
||||
return {
|
||||
isValidDate: false,
|
||||
isBefore,
|
||||
isAfter,
|
||||
isBeforeFrom,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const initialValidation = (
|
||||
range?: DateRange,
|
||||
opt?: UseRangeDatepickerOptions
|
||||
): RangeValidationT => {
|
||||
if (!range || !range?.from) {
|
||||
return getValidationMessage(
|
||||
{ isEmpty: true, isValidDate: false },
|
||||
{ isEmpty: true, isValidDate: false }
|
||||
);
|
||||
}
|
||||
|
||||
const fromVal = fromValidation(range.from, opt);
|
||||
const toVal = range.to
|
||||
? toValidation(range.to, range.from, opt)
|
||||
: { isEmpty: true, isValidDate: false };
|
||||
|
||||
return getValidationMessage({ ...fromVal }, { ...toVal });
|
||||
};
|
||||
|
||||
export const useRangeDatepicker = (
|
||||
opt: UseRangeDatepickerOptions = {}
|
||||
): UseRangeDatepickerValue => {
|
||||
@@ -77,6 +199,8 @@ export const useRangeDatepicker = (
|
||||
disabled,
|
||||
disableWeekends,
|
||||
onRangeChange,
|
||||
inputFormat,
|
||||
onValidate,
|
||||
} = opt;
|
||||
|
||||
const locale = getLocaleFromString(_locale);
|
||||
@@ -97,15 +221,20 @@ export const useRangeDatepicker = (
|
||||
|
||||
const [fromInputValue, setFromInputValue] = useState(
|
||||
defaultSelected?.from
|
||||
? formatDateForInput(defaultSelected.from, locale, "date")
|
||||
? formatDateForInput(defaultSelected.from, locale, "date", inputFormat)
|
||||
: ""
|
||||
);
|
||||
|
||||
const [toInputValue, setToInputValue] = useState(
|
||||
defaultSelected?.to
|
||||
? formatDateForInput(defaultSelected.to, locale, "date")
|
||||
? formatDateForInput(defaultSelected.to, locale, "date", inputFormat)
|
||||
: ""
|
||||
);
|
||||
|
||||
const [validation, setValidation] = useState<RangeValidationT>(
|
||||
initialValidation(selectedRange, opt)
|
||||
);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const updateRange = (range?: DateRange) => {
|
||||
@@ -113,6 +242,15 @@ export const useRangeDatepicker = (
|
||||
setSelectedRange(range);
|
||||
};
|
||||
|
||||
const updateValidation = (
|
||||
from: Partial<RangeValidationT["from"]> = {},
|
||||
to: Partial<RangeValidationT["to"]> = {}
|
||||
) => {
|
||||
const msg = getValidationMessage(from, to);
|
||||
setValidation(msg);
|
||||
onValidate?.(msg);
|
||||
};
|
||||
|
||||
const handleFocusIn = useCallback(
|
||||
(e) => {
|
||||
if (!e?.target || !e?.target?.nodeType) {
|
||||
@@ -143,14 +281,20 @@ export const useRangeDatepicker = (
|
||||
const reset = () => {
|
||||
updateRange(defaultSelected ?? { from: undefined, to: undefined });
|
||||
setMonth(defaultSelected ? defaultSelected?.from : today);
|
||||
setValidation(
|
||||
initialValidation(
|
||||
defaultSelected ?? { from: undefined, to: undefined },
|
||||
opt
|
||||
)
|
||||
);
|
||||
setFromInputValue(
|
||||
defaultSelected?.from
|
||||
? formatDateForInput(defaultSelected.from, locale, "date")
|
||||
? formatDateForInput(defaultSelected.from, locale, "date", inputFormat)
|
||||
: ""
|
||||
);
|
||||
setToInputValue(
|
||||
defaultSelected?.to
|
||||
? formatDateForInput(defaultSelected.to, locale, "date")
|
||||
? formatDateForInput(defaultSelected.to, locale, "date", inputFormat)
|
||||
: ""
|
||||
);
|
||||
setDefaultSelected(_defaultSelected);
|
||||
@@ -159,11 +303,16 @@ export const useRangeDatepicker = (
|
||||
const setSelected = (range?: DateRange) => {
|
||||
updateRange(range);
|
||||
setFromInputValue(
|
||||
range?.from ? formatDateForInput(range.from, locale, "date") : ""
|
||||
range?.from
|
||||
? formatDateForInput(range.from, locale, "date", inputFormat)
|
||||
: ""
|
||||
);
|
||||
setToInputValue(
|
||||
range?.to ? formatDateForInput(range?.to, locale, "date") : ""
|
||||
range?.to
|
||||
? formatDateForInput(range?.to, locale, "date", inputFormat)
|
||||
: ""
|
||||
);
|
||||
setValidation(initialValidation(range, opt));
|
||||
};
|
||||
|
||||
const handleFocus = (e, src: RangeT) => {
|
||||
@@ -172,52 +321,10 @@ export const useRangeDatepicker = (
|
||||
if (isValidDate(day)) {
|
||||
setMonth(day);
|
||||
src === RANGE.FROM
|
||||
? setFromInputValue(formatDateForInput(day, locale, "date"))
|
||||
: setToInputValue(formatDateForInput(day, locale, "date"));
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputs = (day: Date, src: RangeT) => {
|
||||
if (src === RANGE.FROM) {
|
||||
const isAfter =
|
||||
toInputValue &&
|
||||
differenceInCalendarDays(
|
||||
day,
|
||||
parseDate(toInputValue, today, locale, "date")
|
||||
) > 0;
|
||||
|
||||
if (isAfter) {
|
||||
setFromInputValue(
|
||||
formatDateForInput(
|
||||
parseDate(toInputValue, today, locale, "date"),
|
||||
locale,
|
||||
"date"
|
||||
? setFromInputValue(
|
||||
formatDateForInput(day, locale, "date", inputFormat)
|
||||
)
|
||||
);
|
||||
setToInputValue(formatDateForInput(day, locale, "date"));
|
||||
} else {
|
||||
setFromInputValue(formatDateForInput(day, locale, "date"));
|
||||
}
|
||||
} else if (src === RANGE.TO) {
|
||||
const isBefore =
|
||||
fromInputValue &&
|
||||
differenceInCalendarDays(
|
||||
parseDate(fromInputValue, today, locale, "date"),
|
||||
day
|
||||
) > 0;
|
||||
|
||||
if (isBefore) {
|
||||
setToInputValue(
|
||||
formatDateForInput(
|
||||
parseDate(fromInputValue, today, locale, "date"),
|
||||
locale,
|
||||
"date"
|
||||
)
|
||||
);
|
||||
setFromInputValue(formatDateForInput(day, locale, "date"));
|
||||
} else {
|
||||
setToInputValue(formatDateForInput(day, locale, "date"));
|
||||
}
|
||||
: setToInputValue(formatDateForInput(day, locale, "date", inputFormat));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -227,7 +334,19 @@ export const useRangeDatepicker = (
|
||||
return;
|
||||
}
|
||||
|
||||
handleInputs(day, src);
|
||||
if (src === RANGE.FROM) {
|
||||
setFromInputValue(formatDateForInput(day, locale, "date", inputFormat));
|
||||
} else if (src === RANGE.TO) {
|
||||
setToInputValue(formatDateForInput(day, locale, "date", inputFormat));
|
||||
}
|
||||
};
|
||||
|
||||
const validateDay = (day: any) => {
|
||||
return (
|
||||
isValidDate(day) &&
|
||||
!(disableWeekends && isWeekend(day)) &&
|
||||
!(disabled && isMatch(day, disabled))
|
||||
);
|
||||
};
|
||||
|
||||
const handleSelect = (range) => {
|
||||
@@ -238,68 +357,140 @@ export const useRangeDatepicker = (
|
||||
!selectedRange?.from && selectedRange?.to ? selectedRange?.to : range?.to;
|
||||
|
||||
range?.from
|
||||
? setFromInputValue(formatDateForInput(range?.from, locale, "date"))
|
||||
? setFromInputValue(
|
||||
formatDateForInput(range?.from, locale, "date", inputFormat)
|
||||
)
|
||||
: setFromInputValue("");
|
||||
prevToRange
|
||||
? setToInputValue(formatDateForInput(prevToRange, locale, "date"))
|
||||
? setToInputValue(
|
||||
formatDateForInput(prevToRange, locale, "date", inputFormat)
|
||||
)
|
||||
: setToInputValue("");
|
||||
updateValidation(
|
||||
{ isValidDate: !!range?.from, isEmpty: !range?.from },
|
||||
{ isValidDate: !!range?.to, isEmpty: !prevToRange }
|
||||
);
|
||||
updateRange({ from: range?.from, to: prevToRange });
|
||||
};
|
||||
|
||||
/* live-update datepicker based on changes in inputfields */
|
||||
const handleChange = (e, src: RangeT) => {
|
||||
src === RANGE.FROM
|
||||
? setFromInputValue(e.target.value)
|
||||
: setToInputValue(e.target.value);
|
||||
const day = parseDate(e.target.value, today, locale, "date");
|
||||
|
||||
if (
|
||||
!isValidDate(day) ||
|
||||
(disabled &&
|
||||
((disableWeekends && isWeekend(day)) || isMatch(day, disabled)))
|
||||
) {
|
||||
updateRange(
|
||||
src === RANGE.FROM
|
||||
? { ...selectedRange, from: undefined }
|
||||
: { from: selectedRange?.from, to: undefined }
|
||||
const fromChange = (
|
||||
val: string = "",
|
||||
day: Date,
|
||||
isBefore = false,
|
||||
isAfter = false
|
||||
) => {
|
||||
setFromInputValue(val);
|
||||
if (!validateDay(day)) {
|
||||
updateRange({ ...selectedRange, from: undefined });
|
||||
updateValidation(
|
||||
{
|
||||
isEmpty: !val,
|
||||
isValidDate: false,
|
||||
isInvalid: !isValidDate(day),
|
||||
isWeekend: disableWeekends && isWeekend(day),
|
||||
isDisabled: disabled && isMatch(day, disabled),
|
||||
isBefore,
|
||||
isAfter,
|
||||
},
|
||||
validation.to
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (isBefore || isAfter) {
|
||||
updateRange({ ...selectedRange, from: undefined });
|
||||
updateValidation(
|
||||
{
|
||||
isValidDate: false,
|
||||
isBefore,
|
||||
isAfter,
|
||||
},
|
||||
validation.to
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
selectedRange?.to &&
|
||||
differenceInCalendarDays(day, selectedRange?.to) > 0
|
||||
) {
|
||||
updateRange({ to: day, from: day });
|
||||
setToInputValue(formatDateForInput(day, locale, "date", inputFormat));
|
||||
setMonth(day);
|
||||
updateValidation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (toInputValue && !selectedRange?.to) {
|
||||
const toDay = parseDate(toInputValue, today, locale, "date");
|
||||
if (validateDay(toDay)) {
|
||||
updateRange({ from: day, to: toDay });
|
||||
setMonth(day);
|
||||
updateValidation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
updateRange({ ...selectedRange, from: day });
|
||||
updateValidation({}, validation.to);
|
||||
setMonth(day);
|
||||
};
|
||||
|
||||
const toChange = (
|
||||
val: string = "",
|
||||
day: Date,
|
||||
isBefore = false,
|
||||
isAfter = false
|
||||
) => {
|
||||
setToInputValue(val);
|
||||
if (!validateDay(day)) {
|
||||
updateRange({ from: selectedRange?.from, to: undefined });
|
||||
updateValidation(validation.from, {
|
||||
isEmpty: !val,
|
||||
isValidDate: false,
|
||||
isInvalid: !isValidDate(day),
|
||||
isWeekend: disableWeekends && isWeekend(day),
|
||||
isDisabled: disabled && isMatch(day, disabled),
|
||||
isBefore,
|
||||
isAfter,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBefore || isAfter) {
|
||||
updateRange({ from: selectedRange?.from, to: undefined });
|
||||
updateValidation(validation.from, {
|
||||
isValidDate: false,
|
||||
isBefore,
|
||||
isAfter,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
/* If to-value < from-value */
|
||||
if (
|
||||
selectedRange?.from &&
|
||||
differenceInCalendarDays(selectedRange?.from, day) > 0
|
||||
) {
|
||||
updateRange({ from: selectedRange?.from, to: undefined });
|
||||
updateValidation(validation.from, {
|
||||
isValidDate: false,
|
||||
isBeforeFrom: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
updateRange({ from: selectedRange?.from, to: day });
|
||||
updateValidation(validation.from, {});
|
||||
setMonth(day);
|
||||
};
|
||||
|
||||
/* live-update datepicker based on changes in inputfields */
|
||||
const handleChange = (e, src: RangeT) => {
|
||||
const day = parseDate(e.target.value, today, locale, "date");
|
||||
const isBefore = fromDate && differenceInCalendarDays(fromDate, day) > 0;
|
||||
const isAfter = toDate && differenceInCalendarDays(day, toDate) > 0;
|
||||
if (isBefore || isAfter) {
|
||||
src === RANGE.FROM
|
||||
? updateRange({ ...selectedRange, from: undefined })
|
||||
: updateRange({ from: selectedRange?.from, to: undefined });
|
||||
return;
|
||||
}
|
||||
|
||||
/* If to-value < from-value, switch places in state */
|
||||
if (
|
||||
src === RANGE.TO &&
|
||||
selectedRange?.from &&
|
||||
differenceInCalendarDays(selectedRange?.from, day) >= 0
|
||||
) {
|
||||
updateRange({ from: day, to: selectedRange?.from });
|
||||
setMonth(day);
|
||||
return;
|
||||
}
|
||||
|
||||
/* If from-value > to-value , switch places in state */
|
||||
if (
|
||||
src === RANGE.FROM &&
|
||||
selectedRange?.to &&
|
||||
differenceInCalendarDays(day, selectedRange?.to) >= 0
|
||||
) {
|
||||
updateRange({ to: day, from: selectedRange?.to });
|
||||
setMonth(day);
|
||||
return;
|
||||
}
|
||||
|
||||
src === RANGE.FROM && updateRange({ ...selectedRange, from: day });
|
||||
src === RANGE.TO && updateRange({ from: selectedRange?.from, to: day });
|
||||
setMonth(day);
|
||||
return src === RANGE.FROM
|
||||
? fromChange(e.target.value, day, isBefore, isAfter)
|
||||
: toChange(e.target.value, day, isBefore, isAfter);
|
||||
};
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
@@ -336,6 +527,8 @@ export const useRangeDatepicker = (
|
||||
mode: "range" as const,
|
||||
open,
|
||||
onOpenToggle: () => setOpen((x) => !x),
|
||||
disabled,
|
||||
disableWeekends,
|
||||
ref: datePickerRef,
|
||||
};
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ export {
|
||||
UNSAFE_useDatepicker,
|
||||
UNSAFE_useMonthpicker,
|
||||
UNSAFE_useRangeDatepicker,
|
||||
RangeValidationT,
|
||||
DateValidationT,
|
||||
MonthValidationT,
|
||||
} from "./hooks";
|
||||
export {
|
||||
default as UNSAFE_MonthPicker,
|
||||
|
||||
@@ -90,6 +90,26 @@ export const UseMonthpicker = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const UseMonthpickerFormat = () => {
|
||||
const { inputProps, monthpickerProps } = UNSAFE_useMonthpicker({
|
||||
disabled: [new Date("Apr 1 2022")],
|
||||
onMonthChange: console.log,
|
||||
inputFormat: "MM.yyyy",
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={{ height: "20rem" }}>
|
||||
<MonthPicker {...monthpickerProps}>
|
||||
<MonthPicker.Input
|
||||
{...inputProps}
|
||||
label="Velg måned"
|
||||
variant="monthpicker"
|
||||
/>
|
||||
</MonthPicker>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Required = () => {
|
||||
const { inputProps, monthpickerProps } = UNSAFE_useMonthpicker({
|
||||
locale: "nb",
|
||||
|
||||
@@ -6,16 +6,16 @@ const parse = (inp: string) => parseDate(inp, new Date(), nb, "date");
|
||||
|
||||
describe("Format date to correct output", () => {
|
||||
test("formatDateForInput", () => {
|
||||
expect(formatDateForInput(parse("15/05/22"), nb, "date")).toEqual(
|
||||
expect(formatDateForInput(parse("15/05/2022"), nb, "date")).toEqual(
|
||||
"15.05.2022"
|
||||
);
|
||||
expect(formatDateForInput(parse("1/5/22"), nb, "date")).toEqual(
|
||||
expect(formatDateForInput(parse("1/5/2022"), nb, "date")).toEqual(
|
||||
"01.05.2022"
|
||||
);
|
||||
expect(formatDateForInput(parse("1/05/22"), nb, "date")).toEqual(
|
||||
expect(formatDateForInput(parse("1/05/2022"), nb, "date")).toEqual(
|
||||
"01.05.2022"
|
||||
);
|
||||
expect(formatDateForInput(parse("15/5/22"), nb, "date")).toEqual(
|
||||
expect(formatDateForInput(parse("15/5/2022"), nb, "date")).toEqual(
|
||||
"15.05.2022"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2,7 +2,6 @@ import { parseDate } from "..";
|
||||
import { isValidDate } from "..";
|
||||
import nb from "date-fns/locale/nb";
|
||||
import getMonth from "date-fns/getMonth";
|
||||
import getYear from "date-fns/getYear";
|
||||
|
||||
const check = (inp: string) =>
|
||||
expect(isValidDate(parseDate(inp, new Date(), nb, "date")));
|
||||
@@ -11,71 +10,32 @@ const parse = (inp: string) => parseDate(inp, new Date(), nb, "date");
|
||||
|
||||
describe("Parse date-inputs", () => {
|
||||
test("No spaces", () => {
|
||||
check("150522").toBeTruthy();
|
||||
check("11052022").toBeTruthy();
|
||||
check("15052022").toBeTruthy();
|
||||
});
|
||||
|
||||
test(". divider", () => {
|
||||
check("1.5.22").toBeTruthy();
|
||||
check("1.5.2022").toBeTruthy();
|
||||
check("11.05.22").toBeTruthy();
|
||||
check("11.05.2022").toBeTruthy();
|
||||
});
|
||||
|
||||
test("/ divider", () => {
|
||||
check("1/5/22").toBeTruthy();
|
||||
check("10/5/22").toBeTruthy();
|
||||
check("1/5/2022").toBeTruthy();
|
||||
check("10/5/2022").toBeTruthy();
|
||||
check("1/05/22").toBeTruthy();
|
||||
check("10/05/22").toBeTruthy();
|
||||
check("1/05/2022").toBeTruthy();
|
||||
check("10/05/2022").toBeTruthy();
|
||||
});
|
||||
|
||||
test("- divider", () => {
|
||||
check("1-5-22").toBeTruthy();
|
||||
check("10-5-22").toBeTruthy();
|
||||
check("1-5-2022").toBeTruthy();
|
||||
check("10-5-2022").toBeTruthy();
|
||||
check("1-05-22").toBeTruthy();
|
||||
check("10-05-22").toBeTruthy();
|
||||
check("1-05-2022").toBeTruthy();
|
||||
check("10-05-2022").toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Parse date-inputs to correct year", () => {
|
||||
test("No spaces", () => {
|
||||
expect(getYear(parse("150522"))).toEqual(2022);
|
||||
});
|
||||
|
||||
test(". divider", () => {
|
||||
expect(getYear(parse("1.05.22"))).toEqual(2022);
|
||||
expect(getYear(parse("11.05.22"))).toEqual(2022);
|
||||
expect(getYear(parse("1.5.22"))).toEqual(2022);
|
||||
expect(getYear(parse("11.5.22"))).toEqual(2022);
|
||||
});
|
||||
|
||||
test("/ divider", () => {
|
||||
expect(getYear(parse("1/5/22"))).toEqual(2022);
|
||||
expect(getYear(parse("10/5/22"))).toEqual(2022);
|
||||
expect(getYear(parse("1/05/22"))).toEqual(2022);
|
||||
expect(getYear(parse("10/05/22"))).toEqual(2022);
|
||||
});
|
||||
|
||||
test("- divider", () => {
|
||||
expect(getYear(parse("1-5-22"))).toEqual(2022);
|
||||
expect(getYear(parse("10-5-22"))).toEqual(2022);
|
||||
expect(getYear(parse("1-05-22"))).toEqual(2022);
|
||||
expect(getYear(parse("10-05-22"))).toEqual(2022);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Parse date-inputs to correct month", () => {
|
||||
test("No spaces", () => {
|
||||
expect(getMonth(parse("150522"))).toEqual(4);
|
||||
expect(getMonth(parse("11052022"))).toEqual(4);
|
||||
expect(getMonth(parse("15052022"))).toEqual(4);
|
||||
});
|
||||
|
||||
@@ -7,11 +7,13 @@ import {
|
||||
export const formatDateForInput = (
|
||||
date: Date,
|
||||
locale: Locale,
|
||||
type: "date" | "month"
|
||||
type: "date" | "month",
|
||||
inputFormat?: string
|
||||
) => {
|
||||
const INPUT_DATE_STRING_FORMAT =
|
||||
type === "date"
|
||||
inputFormat ??
|
||||
(type === "date"
|
||||
? INPUT_DATE_STRING_FORMAT_DATE
|
||||
: INPUT_DATE_STRING_FORMAT_MONTH;
|
||||
: INPUT_DATE_STRING_FORMAT_MONTH);
|
||||
return format(date, INPUT_DATE_STRING_FORMAT, { locale });
|
||||
};
|
||||
@@ -6,16 +6,10 @@ export const INPUT_DATE_STRING_FORMAT_DATE = "dd.MM.yyyy";
|
||||
export const INPUT_DATE_STRING_FORMAT_MONTH = "MMMM yyyy";
|
||||
|
||||
const ALLOWED_INPUT_FORMATS_DATE = [
|
||||
"ddMMyy",
|
||||
"d.M.yy",
|
||||
"dd.MM.yy",
|
||||
"dd/MM/yy",
|
||||
"dd-MM-yy",
|
||||
INPUT_DATE_STRING_FORMAT_DATE,
|
||||
"ddMMyyyy",
|
||||
"dd/MM/yyyy",
|
||||
"dd-MM-yyyy",
|
||||
"d.M.yyyy",
|
||||
];
|
||||
|
||||
const ALLOWED_INPUT_FORMATS_MONTH = [
|
||||
@@ -28,20 +22,35 @@ const ALLOWED_INPUT_FORMATS_MONTH = [
|
||||
...ALLOWED_INPUT_FORMATS_DATE,
|
||||
];
|
||||
|
||||
const isTwoDigitYear = (dateString, today, locale, formats) => {
|
||||
let parsed;
|
||||
const newFormat = formats.map((x) => x.replace("yyyy", "yy"));
|
||||
for (const format of newFormat) {
|
||||
parsed = parse(dateString, format, today, { locale });
|
||||
if (isValidDate(parsed)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const parseDate = (
|
||||
date: string,
|
||||
today: Date,
|
||||
locale: Locale,
|
||||
type: "date" | "month"
|
||||
) => {
|
||||
): Date => {
|
||||
let parsed;
|
||||
const ALLOWED_FORMATS =
|
||||
type === "date" ? ALLOWED_INPUT_FORMATS_DATE : ALLOWED_INPUT_FORMATS_MONTH;
|
||||
for (const format of ALLOWED_FORMATS) {
|
||||
parsed = parse(date, format, today, { locale });
|
||||
if (isValidDate(parsed)) {
|
||||
if (
|
||||
isValidDate(parsed) &&
|
||||
!isTwoDigitYear(date, today, locale, ALLOWED_FORMATS)
|
||||
) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
return new Date("Invalid date");
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useContext } from "react";
|
||||
import React, { useContext } from "react";
|
||||
import cl from "clsx";
|
||||
import { FieldsetContext } from "./index";
|
||||
import { useId } from "../index";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from "./accordion";
|
||||
export * from "./alert";
|
||||
export * from "./provider";
|
||||
export * from "./button";
|
||||
export * from "./date";
|
||||
export * from "./form";
|
||||
@@ -15,6 +16,7 @@ export * from "./panel";
|
||||
export * from "./popover";
|
||||
export * from "./read-more";
|
||||
export * from "./chat";
|
||||
export * from "./chips";
|
||||
export * from "./stepper";
|
||||
export * from "./tag";
|
||||
export * from "./toggle-group";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { forwardRef, useMemo, useRef } from "react";
|
||||
import React, { forwardRef, useEffect, useMemo, useRef } from "react";
|
||||
import cl from "clsx";
|
||||
import ReactModal from "react-modal";
|
||||
import { Close } from "@navikt/ds-icons";
|
||||
import { Button, mergeRefs } from "..";
|
||||
import { Button, mergeRefs, useProvider } from "..";
|
||||
import ModalContent, { ModalContentType } from "./ModalContent";
|
||||
|
||||
export interface ModalProps {
|
||||
@@ -63,7 +63,7 @@ interface ModalComponent
|
||||
}
|
||||
|
||||
type ModalLifecycle = {
|
||||
setAppElement?: (element: any) => void;
|
||||
setAppElement: (element: any) => void;
|
||||
};
|
||||
|
||||
export const Modal = forwardRef<ReactModal, ModalProps>(
|
||||
@@ -81,6 +81,7 @@ export const Modal = forwardRef<ReactModal, ModalProps>(
|
||||
"aria-modal": ariaModal,
|
||||
"aria-label": contentLabel,
|
||||
style,
|
||||
parentSelector,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
@@ -88,6 +89,12 @@ export const Modal = forwardRef<ReactModal, ModalProps>(
|
||||
const modalRef = useRef<ReactModal | null>(null);
|
||||
const mergedRef = useMemo(() => mergeRefs([modalRef, ref]), [ref]);
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const rootElement = useProvider()?.rootElement;
|
||||
const appElement = useProvider()?.appElement;
|
||||
|
||||
useEffect(() => {
|
||||
appElement && Modal.setAppElement(appElement);
|
||||
}, [appElement]);
|
||||
|
||||
const onModalCloseRequest = (e) => {
|
||||
if (shouldCloseOnOverlayClick || e.type === "keydown") {
|
||||
@@ -100,6 +107,11 @@ export const Modal = forwardRef<ReactModal, ModalProps>(
|
||||
return (
|
||||
<ReactModal
|
||||
{...rest}
|
||||
parentSelector={
|
||||
parentSelector ?? rootElement !== undefined
|
||||
? () => rootElement as HTMLElement
|
||||
: undefined
|
||||
}
|
||||
style={style}
|
||||
isOpen={open}
|
||||
ref={mergedRef}
|
||||
|
||||
@@ -114,7 +114,7 @@ describe("Popover", () => {
|
||||
expect(fn).toHaveBeenCalledTimes(0);
|
||||
|
||||
cleanup();
|
||||
});
|
||||
}, 20000);
|
||||
|
||||
it("keep open on popover-click", async () => {
|
||||
const fn = jest.fn();
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import React, { createContext, useContext } from "react";
|
||||
|
||||
export interface ProviderContextType {
|
||||
/**
|
||||
* Global root-element to attach portals to (Modal, Tooltip)
|
||||
*/
|
||||
rootElement?: HTMLElement;
|
||||
/**
|
||||
* Global config for appElement (Modal)
|
||||
*/
|
||||
appElement?: HTMLElement;
|
||||
}
|
||||
|
||||
export const ProviderContext = createContext<ProviderContextType | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
export interface ProviderProps {
|
||||
children?: React.ReactNode;
|
||||
rootElement?: HTMLElement;
|
||||
appElement?: HTMLElement;
|
||||
}
|
||||
|
||||
export const useProvider = () => useContext(ProviderContext);
|
||||
|
||||
export const Provider = ({ children, ...rest }: ProviderProps) => {
|
||||
return (
|
||||
<ProviderContext.Provider value={rest}>{children}</ProviderContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Provider;
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Provider, useProvider } from "./Provider";
|
||||
@@ -37,7 +37,7 @@ export const TabList = forwardRef<HTMLDivElement, TabListProps>(
|
||||
() =>
|
||||
debounce(() => {
|
||||
if (!listRef?.current) return;
|
||||
const { scrollWidth, clientWidth } = listRef?.current;
|
||||
const { scrollWidth, clientWidth } = listRef.current;
|
||||
let showStartScroll;
|
||||
let showEndScroll;
|
||||
|
||||
|
||||
@@ -3,19 +3,31 @@ import cl from "clsx";
|
||||
import { BodyShort, Detail } from "..";
|
||||
|
||||
export interface TagProps extends HTMLAttributes<HTMLSpanElement> {
|
||||
/**
|
||||
* Tag label
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Changes background and border color
|
||||
* Changes visual profile of tag
|
||||
*/
|
||||
variant: "warning" | "error" | "info" | "success";
|
||||
variant:
|
||||
| "warning"
|
||||
| "warning-filled"
|
||||
| "error"
|
||||
| "error-filled"
|
||||
| "info"
|
||||
| "info-filled"
|
||||
| "success"
|
||||
| "success-filled"
|
||||
| "neutral"
|
||||
| "neutral-filled"
|
||||
| "alt1"
|
||||
| "alt1-filled"
|
||||
| "alt2"
|
||||
| "alt2-filled"
|
||||
| "alt3"
|
||||
| "alt3-filled";
|
||||
/**
|
||||
* Changes padding and font-sizes
|
||||
* @default "medium"
|
||||
*/
|
||||
size?: "medium" | "small";
|
||||
size?: "medium" | "small" | "xsmall";
|
||||
}
|
||||
|
||||
export const Tag = forwardRef<HTMLSpanElement, TagProps>(
|
||||
@@ -27,7 +39,7 @@ export const Tag = forwardRef<HTMLSpanElement, TagProps>(
|
||||
{...rest}
|
||||
ref={ref}
|
||||
as="span"
|
||||
size={size}
|
||||
size={size === "medium" ? "medium" : "small"}
|
||||
className={cl(
|
||||
"navds-tag",
|
||||
className,
|
||||
|
||||
@@ -9,17 +9,70 @@ export default {
|
||||
defaultValue: "info",
|
||||
control: {
|
||||
type: "radio",
|
||||
options: ["error", "warning", "info", "success"],
|
||||
options: [
|
||||
"warning",
|
||||
"error",
|
||||
"info",
|
||||
"success",
|
||||
"neutral",
|
||||
"alt1",
|
||||
"alt2",
|
||||
"alt3",
|
||||
"warning-filled",
|
||||
"error-filled",
|
||||
"info-filled",
|
||||
"success-filled",
|
||||
"neutral-filled",
|
||||
"alt1-filled",
|
||||
"alt2-filled",
|
||||
"alt3-filled",
|
||||
],
|
||||
},
|
||||
},
|
||||
size: {
|
||||
defaultValue: "medium",
|
||||
control: {
|
||||
type: "radio",
|
||||
options: ["xsmall", "small", "medium"],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const variants: Array<"error" | "warning" | "info" | "success"> = [
|
||||
"error",
|
||||
const variants: Array<
|
||||
| "warning"
|
||||
| "warning-filled"
|
||||
| "error"
|
||||
| "error-filled"
|
||||
| "info"
|
||||
| "info-filled"
|
||||
| "success"
|
||||
| "success-filled"
|
||||
| "neutral"
|
||||
| "neutral-filled"
|
||||
| "alt1"
|
||||
| "alt1-filled"
|
||||
| "alt2"
|
||||
| "alt2-filled"
|
||||
| "alt3"
|
||||
| "alt3-filled"
|
||||
> = [
|
||||
"warning",
|
||||
"error",
|
||||
"info",
|
||||
"success",
|
||||
"neutral",
|
||||
"alt1",
|
||||
"alt2",
|
||||
"alt3",
|
||||
"warning-filled",
|
||||
"error-filled",
|
||||
"info-filled",
|
||||
"success-filled",
|
||||
"neutral-filled",
|
||||
"alt1-filled",
|
||||
"alt2-filled",
|
||||
"alt3-filled",
|
||||
];
|
||||
|
||||
export const Default = (props) => (
|
||||
@@ -34,10 +87,34 @@ Default.args = {
|
||||
|
||||
export const Small = () => {
|
||||
return (
|
||||
<div className="rowgap">
|
||||
<div className="rowgap rowgap-wrap">
|
||||
{variants.map((variant, i) => (
|
||||
<Tag key={variant} variant={variant} size="small">
|
||||
{new Array(i + 1).fill("Id elit esse")}
|
||||
{variant}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const xSmall = () => {
|
||||
return (
|
||||
<div className="rowgap rowgap-wrap">
|
||||
{variants.map((variant, i) => (
|
||||
<Tag key={variant} variant={variant} size="xsmall">
|
||||
{variant}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Variants = () => {
|
||||
return (
|
||||
<div className="rowgap rowgap-wrap">
|
||||
{variants.map((variant, i) => (
|
||||
<Tag key={variant} variant={variant}>
|
||||
{variant}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Detail } from "..";
|
||||
import { Detail, useProvider } from "..";
|
||||
import { mergeRefs, useId } from "../util";
|
||||
|
||||
export interface TooltipProps extends HTMLAttributes<HTMLDivElement> {
|
||||
@@ -96,6 +96,7 @@ export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
|
||||
) => {
|
||||
const [open, setOpen] = useState(defaultOpen);
|
||||
const arrowRef = useRef<HTMLDivElement | null>(null);
|
||||
const rootElement = useProvider()?.rootElement;
|
||||
|
||||
const {
|
||||
x,
|
||||
@@ -170,7 +171,7 @@ export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
|
||||
: children?.props["aria-describedby"],
|
||||
})
|
||||
)}
|
||||
<FloatingPortal>
|
||||
<FloatingPortal root={rootElement}>
|
||||
{(userOpen ?? open) && (
|
||||
<div
|
||||
{...getFloatingProps({
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
/* https://github.com/radix-ui/primitives/blob/main/packages/react/portal/src/Portal.tsx */
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
const Portal = ({ children }) => {
|
||||
const hostElement = globalThis?.document?.body;
|
||||
|
||||
if (hostElement) {
|
||||
return ReactDOM.createPortal(children, hostElement);
|
||||
}
|
||||
|
||||
// bail out of ssr
|
||||
return null;
|
||||
};
|
||||
|
||||
export default Portal;
|
||||
@@ -1,3 +1,5 @@
|
||||
import React from "react";
|
||||
|
||||
export interface OverridableComponent<Component, Element extends HTMLElement> {
|
||||
(props: Component & React.RefAttributes<Element>): ReturnType<React.FC>;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react";
|
||||
// https://github.com/gregberge/react-merge-refs
|
||||
export default function mergeRefs<T = any>(
|
||||
refs: Array<React.MutableRefObject<T> | React.LegacyRef<T>>
|
||||
|
||||
@@ -1,3 +1,25 @@
|
||||
# @navikt/ds-tailwind
|
||||
|
||||
## 1.5.2
|
||||
|
||||
## 1.5.1
|
||||
|
||||
## 1.5.0
|
||||
|
||||
## 1.4.4
|
||||
|
||||
## 1.4.3
|
||||
|
||||
## 1.4.2
|
||||
|
||||
## 1.4.1
|
||||
|
||||
## 1.4.0
|
||||
|
||||
## 1.3.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [`40f0b3340`](https://github.com/navikt/Designsystemet/commit/40f0b3340d01bf85fecc04f2f3a8e8b2acf996f1) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Bump for latest-tag
|
||||
|
||||
## 1.3.38
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tailwind",
|
||||
"version": "1.3.38",
|
||||
"version": "1.5.2",
|
||||
"description": "Auto-generated tailwind config based on @navikt/ds-tokens",
|
||||
"author": "NAV Designsystem team",
|
||||
"keywords": [
|
||||
@@ -22,13 +22,13 @@
|
||||
"directory": "@navikt/core/tailwind"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-tokens": "^1.3.38",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@navikt/ds-tokens": "^1.5.2",
|
||||
"@types/jest": "^29.0.0",
|
||||
"@types/node": "^17.0.35",
|
||||
"jest": "^27.2.0",
|
||||
"jest": "^29.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"ts-jest": "^27.0.5",
|
||||
"ts-node": "^10.3.0",
|
||||
"typescript": "^4.7.2"
|
||||
"ts-jest": "^29.0.0",
|
||||
"ts-node": "10.9.0",
|
||||
"typescript": "^4.8.0"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,25 @@
|
||||
# @navikt/ds-tokens
|
||||
|
||||
## 1.5.2
|
||||
|
||||
## 1.5.1
|
||||
|
||||
## 1.5.0
|
||||
|
||||
## 1.4.4
|
||||
|
||||
## 1.4.3
|
||||
|
||||
## 1.4.2
|
||||
|
||||
## 1.4.1
|
||||
|
||||
## 1.4.0
|
||||
|
||||
## 1.3.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [`40f0b3340`](https://github.com/navikt/Designsystemet/commit/40f0b3340d01bf85fecc04f2f3a8e8b2acf996f1) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Bump for latest-tag
|
||||
|
||||
## 1.3.38
|
||||
@@ -13,7 +13,7 @@ const styleFieldNames = {
|
||||
|
||||
const findNodeWithStyle = (node: Node<any>, styleId: StyleId): Node<any> => {
|
||||
if (
|
||||
node.hasOwnProperty("styles") &&
|
||||
"styles" in node &&
|
||||
styleFieldNames[styleId.styleType]?.some(
|
||||
(name) => node.styles[name] === styleId.id
|
||||
)
|
||||
@@ -31,8 +31,6 @@ const findNodeWithStyle = (node: Node<any>, styleId: StyleId): Node<any> => {
|
||||
|
||||
const findStyleValue = (document: DOCUMENT, styleId: StyleId) => {
|
||||
const node = findNodeWithStyle(document, styleId);
|
||||
if (styleId.id === "5249:11258") {
|
||||
}
|
||||
|
||||
switch (styleId.styleType) {
|
||||
case "FILL":
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tokens",
|
||||
"version": "1.3.38",
|
||||
"version": "1.5.2",
|
||||
"description": "Auto-generated design-tokens for NAV design-systems framework",
|
||||
"author": "NAV Designsystem team",
|
||||
"keywords": [
|
||||
@@ -29,18 +29,18 @@
|
||||
"directory": "@navikt/core/tokens"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/jest": "^29.0.0",
|
||||
"@types/node": "^17.0.35",
|
||||
"axios": "^0.21.3",
|
||||
"color": "4.2.3",
|
||||
"dotenv": "^10.0.0",
|
||||
"figma-api": "^1.7.0",
|
||||
"jest": "^27.2.0",
|
||||
"jest": "^29.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"nodemon": "^2.0.12",
|
||||
"style-dictionary": "^3.0.2",
|
||||
"ts-jest": "^27.0.5",
|
||||
"ts-node": "^10.3.0",
|
||||
"typescript": "^4.7.2"
|
||||
"ts-jest": "^29.0.0",
|
||||
"ts-node": "10.9.0",
|
||||
"typescript": "^4.8.0"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,27 @@
|
||||
# @navikt/ds-icons
|
||||
|
||||
## 1.5.2
|
||||
|
||||
## 1.5.1
|
||||
|
||||
## 1.5.0
|
||||
|
||||
## 1.4.4
|
||||
|
||||
## 1.4.3
|
||||
|
||||
## 1.4.2
|
||||
|
||||
## 1.4.1
|
||||
|
||||
## 1.4.0
|
||||
|
||||
## 1.3.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [`40f0b3340`](https://github.com/navikt/Designsystemet/commit/40f0b3340d01bf85fecc04f2f3a8e8b2acf996f1) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Bump for latest-tag
|
||||
|
||||
## 1.3.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -78,8 +78,4 @@ const main = async () => {
|
||||
console.log("\nDownloaded all icons from Figma!\n");
|
||||
};
|
||||
|
||||
try {
|
||||
main();
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
main();
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-icons",
|
||||
"version": "1.3.38",
|
||||
"version": "1.5.2",
|
||||
"description": "Aksel icons",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -25,13 +25,14 @@
|
||||
"/esm",
|
||||
"/src",
|
||||
"/svg",
|
||||
"/meta.json"
|
||||
"/meta.json",
|
||||
"!**/*.tsbuildinfo"
|
||||
],
|
||||
"scripts": {
|
||||
"create-package-json-pointers-to-esm": "node ../../scripts/createPackageJsonsWithESMPointers.js",
|
||||
"create-icons": "svgr --out-dir src svg",
|
||||
"create-icons": "svgr --silent --out-dir src svg",
|
||||
"clean": "rimraf src cjs esm",
|
||||
"build": "yarn clean && copyfiles util/* src && yarn create-icons && concurrently \"tsc\" \"tsc -p tsconfig.esm.json\" && yarn create-package-json-pointers-to-esm",
|
||||
"build": "copyfiles util/* src && yarn create-icons && concurrently \"tsc\" \"tsc -p tsconfig.esm.json\" && yarn create-package-json-pointers-to-esm",
|
||||
"update": "ts-node figma/index.ts dotenv/config",
|
||||
"test": "jest"
|
||||
},
|
||||
@@ -46,7 +47,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@svgr/cli": "6.2.1",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/jest": "^29.0.0",
|
||||
"@types/lodash.startcase": "^4.4.6",
|
||||
"@types/node": "^17.0.35",
|
||||
"@types/rimraf": "3.0.2",
|
||||
@@ -54,13 +55,13 @@
|
||||
"concurrently": "7.2.1",
|
||||
"copyfiles": "^2.4.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"jest": "^27.5.0",
|
||||
"jest": "^29.0.0",
|
||||
"lodash.kebabcase": "^4.1.1",
|
||||
"lodash.startcase": "4.4.0",
|
||||
"react": "^18.0.0",
|
||||
"rimraf": "3.0.2",
|
||||
"ts-jest": "^27.1.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"typescript": "^4.7.2"
|
||||
"ts-jest": "^29.0.0",
|
||||
"ts-node": "10.9.0",
|
||||
"typescript": "^4.8.0"
|
||||
}
|
||||
}
|
||||
@@ -4,5 +4,6 @@
|
||||
"outDir": "./cjs",
|
||||
"module": "commonjs"
|
||||
},
|
||||
"exclude": ["./figma", "./util", "./tests", "./scripts", "**/*.stories.*"]
|
||||
"exclude": ["./figma", "./util", "./tests", "./scripts", "**/*.stories.*"],
|
||||
"include": ["./src"]
|
||||
}
|
||||
@@ -1,3 +1,29 @@
|
||||
# @navikt/ds-css-internal
|
||||
|
||||
## 1.5.2
|
||||
|
||||
## 1.5.1
|
||||
|
||||
## 1.5.0
|
||||
|
||||
## 1.4.4
|
||||
|
||||
## 1.4.3
|
||||
|
||||
## 1.4.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- :bug: Dropdown-link ikke i synk med Figma
|
||||
|
||||
## 1.4.1
|
||||
|
||||
## 1.4.0
|
||||
|
||||
## 1.3.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [`40f0b3340`](https://github.com/navikt/Designsystemet/commit/40f0b3340d01bf85fecc04f2f3a8e8b2acf996f1) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Bump for latest-tag
|
||||
|
||||
## 1.3.38
|
||||
@@ -3,7 +3,7 @@
|
||||
--navds-dropdown-menu-color: var(--navds-semantic-color-text);
|
||||
--navdsi-dropdown-item-color-text: var(--navds-semantic-color-link);
|
||||
--navdsi-dropdown-item-color-background-hover: var(--navds-semantic-color-interaction-primary-hover-subtle);
|
||||
--navdsi-dropdown-item-color-background-active: var(--navds-global-color-blue-200);
|
||||
--navdsi-dropdown-item-color-background-active: var(--navds-global-color-deepblue-500);
|
||||
--navds-dropdown-item-color-focus: var(--navds-semantic-color-focus);
|
||||
}
|
||||
|
||||
@@ -64,9 +64,10 @@
|
||||
|
||||
.navdsi-dropdown__item:active {
|
||||
background-color: var(--navdsi-dropdown-item-color-background-active);
|
||||
color: var(--navds-global-color-white);
|
||||
}
|
||||
|
||||
.navdsi-dropdown__item:focus {
|
||||
.navdsi-dropdown__item:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: inset 0 0 0 2px var(--navds-dropdown-item-color-focus);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-css-internal",
|
||||
"version": "1.3.38",
|
||||
"version": "1.5.2",
|
||||
"description": "Css for NAV internal Designsystem components",
|
||||
"author": "NAV Designsystem team",
|
||||
"keywords": [
|
||||
@@ -20,9 +20,9 @@
|
||||
"watch": "yarn build --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"postcss": "^8.3.8",
|
||||
"postcss": "^8.4.0",
|
||||
"postcss-cli": "^9.0.0",
|
||||
"postcss-combine-duplicated-selectors": "10.0.3",
|
||||
"postcss-import": "^14.0.2"
|
||||
"postcss-import": "^14.1.0"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,79 @@
|
||||
# @navikt/ds-react-internal
|
||||
|
||||
## 1.5.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-react@1.5.2
|
||||
- @navikt/ds-icons@1.5.2
|
||||
|
||||
## 1.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-react@1.5.1
|
||||
- @navikt/ds-icons@1.5.1
|
||||
|
||||
## 1.5.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`e19bf67b3`](https://github.com/navikt/Designsystemet/commit/e19bf67b337dea39989c68b5e9c2591cf0d5b40f), [`97c5f60e9`](https://github.com/navikt/Designsystemet/commit/97c5f60e9111da7e08f55c8d0aa29581f0a9b1ca)]:
|
||||
- @navikt/ds-react@1.5.0
|
||||
- @navikt/ds-icons@1.5.0
|
||||
|
||||
## 1.4.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-react@1.4.4
|
||||
- @navikt/ds-icons@1.4.4
|
||||
|
||||
## 1.4.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-react@1.4.3
|
||||
- @navikt/ds-icons@1.4.3
|
||||
|
||||
## 1.4.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-react@1.4.2
|
||||
- @navikt/ds-icons@1.4.2
|
||||
|
||||
## 1.4.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`3c08651df`](https://github.com/navikt/Designsystemet/commit/3c08651df28c3e19dd8c8a7a1d0032200bec473d)]:
|
||||
- @navikt/ds-react@1.4.1
|
||||
- @navikt/ds-icons@1.4.1
|
||||
|
||||
## 1.4.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`1bdb7e377`](https://github.com/navikt/Designsystemet/commit/1bdb7e3777ece28d153991b78dbdd289366fca57)]:
|
||||
- @navikt/ds-react@1.4.0
|
||||
- @navikt/ds-icons@1.4.0
|
||||
|
||||
## 1.3.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [`40f0b3340`](https://github.com/navikt/Designsystemet/commit/40f0b3340d01bf85fecc04f2f3a8e8b2acf996f1) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Bump for latest-tag
|
||||
|
||||
- Updated dependencies [[`40f0b3340`](https://github.com/navikt/Designsystemet/commit/40f0b3340d01bf85fecc04f2f3a8e8b2acf996f1)]:
|
||||
- @navikt/ds-react@1.3.39
|
||||
- @navikt/ds-icons@1.3.39
|
||||
|
||||
## 1.3.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-react-internal",
|
||||
"version": "1.3.38",
|
||||
"version": "1.5.2",
|
||||
"description": "NAV internal designsystem react components",
|
||||
"author": "NAV Designsystem team",
|
||||
"license": "MIT",
|
||||
@@ -24,18 +24,19 @@
|
||||
"/cjs",
|
||||
"/esm",
|
||||
"/src",
|
||||
"_docs.json"
|
||||
"_docs.json",
|
||||
"!**/*.tsbuildinfo"
|
||||
],
|
||||
"scripts": {
|
||||
"docgen": "yarn ts-node ../../../scripts/docgen.ts",
|
||||
"create-package-json-pointers-to-esm": "node ../../../scripts/createPackageJsonsWithESMPointers.js",
|
||||
"clean": "rimraf cjs esm",
|
||||
"build": "yarn run clean && concurrently \"tsc\" \"tsc -p tsconfig.esm.json\" && yarn create-package-json-pointers-to-esm",
|
||||
"build": "concurrently \"tsc\" \"tsc -p tsconfig.esm.json\" && yarn create-package-json-pointers-to-esm",
|
||||
"watch": "tsc --watch -p tsconfig.esm.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@navikt/ds-icons": "^1.3.38",
|
||||
"@navikt/ds-react": "^1.3.38",
|
||||
"@navikt/ds-icons": "^1.5.2",
|
||||
"@navikt/ds-react": "^1.5.2",
|
||||
"clsx": "^1.1.1",
|
||||
"copy-to-clipboard": "^3.3.1"
|
||||
},
|
||||
@@ -43,8 +44,8 @@
|
||||
"@types/node": "^17.0.35",
|
||||
"concurrently": "7.2.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-node": "10.7.0",
|
||||
"typescript": "^4.7.2"
|
||||
"ts-node": "10.9.0",
|
||||
"typescript": "^4.8.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.30 || ^18.0.0",
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"compilerOptions": {
|
||||
"outDir": "./cjs",
|
||||
"module": "commonjs"
|
||||
}
|
||||
},
|
||||
"include": ["./src"]
|
||||
}
|
||||
@@ -1,16 +1,10 @@
|
||||
Font License
|
||||
|
||||
The Source Sans Pro font files in packages/node_modules/nav-frontend-typografi-style/assets are a subset of Source Sans Pro, licensed under the SIL Open Font License, and copyright Adobe Systems Incorporated, with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
|
||||
|
||||
Icon License
|
||||
|
||||
This project uses Streamline Icons. If you use nav-frontend-moduler in your project please adhere to the Streamline Icons license agreement found here: https://streamlineicons.com/ux/extended-license.html
|
||||
|
||||
The rest of the codebase (excluding 3rd party dependencies):
|
||||
SIL Open Font License (OFL)
|
||||
|
||||
# The MIT License
|
||||
|
||||
Copyright 2019 NAV (Arbeids- og velferdsdirektoratet) - The Norwegian Labour and Welfare Administration
|
||||
Copyright 2022 NAV (Arbeids- og velferdsdirektoratet) - The Norwegian Labour and Welfare Administration
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the "Software"),
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
├── internal/css # @navikt/ds-css-internal, css interne flater
|
||||
├── icons # @navikt/ds-icons, ikonpakke
|
||||
└── codemod # @navikt/ds-codemod, migrerings-scripts
|
||||
|
||||
aksel.nav.no/
|
||||
├── /website # NEXT.JS app (v12)
|
||||
└── /sanity # Sanity CMS-app (v2)
|
||||
```
|
||||
|
||||
## Commands
|
||||
@@ -54,12 +58,6 @@ yarn && yarn boot
|
||||
| `yarn clean` | Fjerner genererte filer |
|
||||
| `yarn docgen` | Generer ts-docs |
|
||||
|
||||
## Dokumentasjon
|
||||
|
||||
[Dokumentasjon forvaltes i navikt/aksel-website](https://github.com/navikt/aksel-website)
|
||||
|
||||
[Migreringer](https://aksel.nav.no/designsystem/side/migrering#beta-til-v1)
|
||||
|
||||
## Bidra
|
||||
|
||||
Vi tar gjerne i mot PR-er [CONTRIBUTING.md](https://github.com/navikt/Designsystemet/blob/master/CONTRIBUTING.md)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
FROM node:16.4-alpine3.14
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
||||
ENV NODE_ENV production
|
||||
|
||||
WORKDIR /app
|
||||
COPY aksel.nav.no/website/package.json .
|
||||
COPY aksel.nav.no/website/.next/ .next/
|
||||
COPY aksel.nav.no/website/public/ public
|
||||
COPY aksel.nav.no/website/.env .
|
||||
COPY aksel.nav.no/website/redirects.json .
|
||||
COPY aksel.nav.no/website/next.config.js .
|
||||
COPY node_modules/ node_modules/
|
||||
COPY @navikt/ @navikt/
|
||||
|
||||
EXPOSE 3000
|
||||
CMD yarn start
|
||||
@@ -0,0 +1,92 @@
|
||||
CMS og nettside for [aksel.nav.no](https://aksel.nav.no/)
|
||||
|
||||
```sh
|
||||
├── /website # NEXT.JS app (v12)
|
||||
└── /sanity # Sanity CMS-app (v2)
|
||||
```
|
||||
|
||||
### /website
|
||||
|
||||
[Nextjs](https://nextjs.org/) applikasjon som henter data fra et [sanity-datasett](https://www.sanity.io/).
|
||||
|
||||
Siden leverer to løsninger:
|
||||
|
||||
- Innhold på toppnivå i Aksel: artikler, prinsipper og blogger
|
||||
- Designsystem-dokumentasjon under https://aksel.nav.no/designsystem
|
||||
|
||||
### /sanity
|
||||
|
||||
Det publiserte studioet finner man på [aksel.nav.no/studio](https://aksel.nav.no/studio)(krever NAV SSO eller invite).
|
||||
|
||||
`production`-datasettet er privat, så man må ha riktige tilganger for å kunne lese/jobbe med dataen der.
|
||||
|
||||
## Kom i gang
|
||||
|
||||
### Bidrag
|
||||
|
||||
Branch `master` blir pusher rett til prod -> aksel.nav.no. For å utvikle nye features brukes branch `next` som ved push publiseres til devmiljø aksel.dev.nav.no (krever naisdevice).
|
||||
|
||||
### Nettside (localhost:3000)
|
||||
|
||||
```
|
||||
yarn install
|
||||
yarn workspace aksel.nav.no dev
|
||||
```
|
||||
|
||||
**Rettigheter**
|
||||
|
||||
Sanity-datasettet er privat, noe som betyr du må ha tilgang til sanity applikasjonen + følgende token for å få data lokalt:
|
||||
|
||||
```
|
||||
SANITY_PREVIEW_TOKEN
|
||||
SANITY_PRIVATE_NO_DRAFTS
|
||||
```
|
||||
|
||||
### Sanity (localhost:3333)
|
||||
|
||||
> !! Må være developer i https://verktoykasse.sanity.studio, Ta kontakt med team Aksel om du trenger tilgang !!
|
||||
|
||||
Husk at alle endringer i datasettet lokalt også gjøres i prod. Bytt derfor til `development`-dataset hvis man vil teste destruktive handlinger. Dette kan endres i `/sanity/schema.json`
|
||||
|
||||
```
|
||||
yarn install
|
||||
yarn workspace aksel.nav.no sanity
|
||||
```
|
||||
|
||||
**Deploy av ny sanity-oppdatering**
|
||||
|
||||
```
|
||||
yarn workspace aksel.nav.no sanity:deploy
|
||||
```
|
||||
|
||||
### .env
|
||||
|
||||
Blir brukt flere keys i .env under `./website`, men kun `SANITY_PRIVATE_NO_DRAFTS` trengs for å teste lokalt
|
||||
|
||||
- SANITY_WRITE_KEY:
|
||||
For å sende dokumenter til sanity, brukt til å oppdatere sandboxes/examples/farger. Trengs bare hvis kode skal synkes fra lokal branch
|
||||
- ALGOLIA_ADMIN:
|
||||
For å oppdatere records i søkemotoren. Trengs bare hvis man oppdaterer søkeindexen lokalt.
|
||||
- ALGOLIA_ID:
|
||||
Søkemotor index-id for å redigere riktig Index. Trengs bare for at søk skal fungere lokalt
|
||||
- TRELLO_TOKEN: Sender feedback til et egent trello-board. Ikke nødvendig lokalt hvis man ikke vil teste feedback-modulen.
|
||||
- SANITY_PREVIEW_TOKEN: Gir appen tilgang til å lese draft innhold fra sanity i "preview"-mode. Trengs for å teste preview-sider
|
||||
- SANITY_PRIVATE_NO_DRAFTS: Gir appen tilgang til å lese innhold fra Sanity da datasettet er privat
|
||||
|
||||
### Søk
|
||||
|
||||
Søk-index blir generert av [algolia-scraper](https://github.com/algolia/docsearch-scraper). Kjøres daglig i en github-action, men kan kjøres lokalt også med docker.
|
||||
|
||||
```
|
||||
docker run -it --env-file=.env -e "CONFIG=$(cat crawler.json | jq -r tostring)" algolia/docsearch-scraper
|
||||
```
|
||||
|
||||
## Backups
|
||||
|
||||
Kjøres backups hver 3 dag til GCP-bucket under `designsystem-prod`
|
||||
|
||||
## Henvendelser
|
||||
|
||||
Spørsmål og kontakt kan rettes til team Aksel
|
||||
|
||||
Aksel har også en egen slack-kanal #aksel
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"index_name": "aksel_docsearch",
|
||||
"start_urls": [
|
||||
"https://aksel.nav.no/",
|
||||
{
|
||||
"url": "https://aksel.nav.no/designsystem",
|
||||
"tags": ["designsystem", "docs"]
|
||||
},
|
||||
{
|
||||
"url": "https://aksel.nav.no/artikkel/",
|
||||
"tags": ["aksel"],
|
||||
"selectors_key": "aksel"
|
||||
},
|
||||
{
|
||||
"url": "https://aksel.nav.no/blogg/",
|
||||
"tags": ["aksel"],
|
||||
"selectors_key": "aksel"
|
||||
}
|
||||
],
|
||||
"selectors": {
|
||||
"default": {
|
||||
"lvl0": {
|
||||
"selector": "h1.algolia-index-lvl1",
|
||||
"global": true
|
||||
},
|
||||
"lvl1": ".algolia-index-tabs--active",
|
||||
"lvl2": "h2.algolia-index-lvl2",
|
||||
"lvl3": "h3.algolia-index-lvl3",
|
||||
"text": "p.algolia-index-body, p.algolia-index-ingress"
|
||||
},
|
||||
"aksel": {
|
||||
"lvl0": {
|
||||
"selector": "h1.algolia-index-lvl1",
|
||||
"global": true
|
||||
},
|
||||
"lvl1": "h2.algolia-index-lvl2",
|
||||
"lvl2": "h3.algolia-index-lvl3",
|
||||
"text": "p.algolia-index-body, p.algolia-index-ingress"
|
||||
}
|
||||
},
|
||||
"selectors_exclude": [".algolia-ignore-index"],
|
||||
"stop_urls": [
|
||||
"https://aksel.nav.no/preview",
|
||||
"https://old-design-nav.vercel.app"
|
||||
],
|
||||
"sitemap_urls": ["https://aksel.nav.no/api/sitemap"],
|
||||
"js_render": true
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
AZURE_APP_CLIENT_ID=notfound
|
||||
AZURE_OPENID_CONFIG_TOKEN_ENDPOINT=https://fakedings.dev-gcp.nais.io/fake/aad?NAVIdent=A123456&aud=testbruker-aksel" >> website/.env
|
||||
AZURE_APP_CLIENT_SECRET=secret
|
||||
AZURE_OPENID_CONFIG_ISSUER=https://fakedings.dev-gcp.nais.io/fake
|
||||
AZURE_OPENID_CONFIG_JWKS_URI=https://fakedings.dev-gcp.nais.io/fake/jwks
|
||||
AZURE_APP_JWK=https://fakedings.dev-gcp.nais.io/fake/jwk
|
||||
@@ -0,0 +1,49 @@
|
||||
apiVersion: "nais.io/v1alpha1"
|
||||
kind: "Application"
|
||||
metadata:
|
||||
name: verktoykasse
|
||||
namespace: designsystem
|
||||
labels:
|
||||
team: designsystem
|
||||
spec:
|
||||
image: {{ image }}
|
||||
port: 3000
|
||||
ingresses:
|
||||
- "https://aksel.dev.nav.no"
|
||||
liveness:
|
||||
path: /api/isAlive
|
||||
initialDelay: 20
|
||||
timeout: 1
|
||||
periodSeconds: 5
|
||||
failureThreshold: 10
|
||||
readiness:
|
||||
path: /api/isAlive
|
||||
initialDelay: 20
|
||||
timeout: 1
|
||||
replicas:
|
||||
min: 1
|
||||
max: 1
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 200m
|
||||
memory: 256Mi
|
||||
accessPolicy:
|
||||
outbound:
|
||||
external:
|
||||
- host: api.sanity.io
|
||||
- host: hnbe3yhs.api.sanity.io
|
||||
- host: apicdn.sanity.io
|
||||
- host: hnbe3yhs.apicdn.sanity.io
|
||||
- host: cdn.sanity.io
|
||||
rules: []
|
||||
env:
|
||||
azure:
|
||||
application:
|
||||
enabled: true
|
||||
allowAllUsers: true
|
||||
tenant: nav.no
|
||||
sidecar:
|
||||
enabled: true
|
||||
@@ -0,0 +1,55 @@
|
||||
apiVersion: "nais.io/v1alpha1"
|
||||
kind: "Application"
|
||||
metadata:
|
||||
name: verktoykasse
|
||||
namespace: designsystem
|
||||
labels:
|
||||
team: designsystem
|
||||
spec:
|
||||
image: {{ image }}
|
||||
port: 3000
|
||||
ingresses:
|
||||
- "https://aksel.nav.no"
|
||||
- "https://design.nav.no"
|
||||
liveness:
|
||||
path: /api/isAlive
|
||||
initialDelay: 20
|
||||
timeout: 1
|
||||
periodSeconds: 5
|
||||
failureThreshold: 10
|
||||
readiness:
|
||||
path: /api/isAlive
|
||||
initialDelay: 20
|
||||
timeout: 1
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 200m
|
||||
memory: 256Mi
|
||||
accessPolicy:
|
||||
outbound:
|
||||
external:
|
||||
- host: api.sanity.io
|
||||
- host: hnbe3yhs.api.sanity.io
|
||||
- host: apicdn.sanity.io
|
||||
- host: hnbe3yhs.apicdn.sanity.io
|
||||
- host: cdn.sanity.io
|
||||
rules: []
|
||||
env:
|
||||
azure:
|
||||
application:
|
||||
enabled: true
|
||||
allowAllUsers: true
|
||||
tenant: nav.no
|
||||
sidecar:
|
||||
enabled: true
|
||||
gcp:
|
||||
buckets:
|
||||
- name: aksel-website-prod
|
||||
retentionPeriodDays: 30
|
||||
lifecycleCondition:
|
||||
age: 7
|
||||
numNewerVersions: 3
|
||||
withState: ANY
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "aksel.nav.no",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Nettside for Aksel",
|
||||
"scripts": {
|
||||
"dev": "yarn workspace website dev",
|
||||
"build:next": "yarn workspace website build",
|
||||
"serve:next": "yarn workspace website start",
|
||||
"sanity": "yarn workspace sanity start",
|
||||
"sanity:deploy": "yarn workspace sanity deploy",
|
||||
"sanity:backup": "yarn workspace sanity backup",
|
||||
"sanity:update": "yarn workspace website update:sanity",
|
||||
"test": "yarn workspace website test"
|
||||
},
|
||||
"workspaces": [
|
||||
"sanity",
|
||||
"website"
|
||||
],
|
||||
"dependencies": {
|
||||
"@navikt/ds-codemod": "^1.5.2",
|
||||
"@navikt/ds-css": "^1.5.2",
|
||||
"@navikt/ds-css-internal": "^1.5.2",
|
||||
"@navikt/ds-icons": "^1.5.2",
|
||||
"@navikt/ds-react": "^1.5.2",
|
||||
"@navikt/ds-react-internal": "^1.5.2",
|
||||
"@navikt/ds-tailwind": "^1.5.2",
|
||||
"@navikt/ds-tokens": "^1.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier-plugin-tailwindcss": "^0.1.13"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// https://www.sanity.io/schemas/make-a-field-read-only-after-publishing-once-d87bf5f6
|
||||
import { useState, useEffect } from "react";
|
||||
import { useDocumentOperation, useValidationStatus } from "@sanity/react-hooks";
|
||||
import PublishIcon from "part:@sanity/base/publish-icon";
|
||||
|
||||
export default function SetAndPublishAction(props) {
|
||||
const { patch, publish }: any = useDocumentOperation(props.id, props.type);
|
||||
const { isValidating, markers }: any = useValidationStatus(
|
||||
props.id,
|
||||
props.type
|
||||
);
|
||||
const [isPublishing, setIsPublishing] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// if the isPublishing state was set to true and the draft has changed
|
||||
// to become `null` the document has been published
|
||||
if (isPublishing && !props.draft) {
|
||||
setIsPublishing(false);
|
||||
}
|
||||
}, [props.draft]);
|
||||
|
||||
console.log(markers);
|
||||
|
||||
return {
|
||||
disabled:
|
||||
publish.disabled ||
|
||||
isValidating ||
|
||||
markers.some((x) => x?.level === "error"),
|
||||
label: isPublishing ? "Publishing…" : "Publish",
|
||||
color: "success",
|
||||
icon: PublishIcon,
|
||||
|
||||
onHandle: () => {
|
||||
// This will update the button text
|
||||
setIsPublishing(true);
|
||||
|
||||
// Set publishedAt to current date and time
|
||||
!props.published &&
|
||||
patch.execute([{ set: { publishedAt: new Date().toISOString() } }]);
|
||||
|
||||
// Perform the publish
|
||||
publish.execute();
|
||||
|
||||
// Signal that the action is completed
|
||||
props.onComplete();
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import { EyeScreened } from "@navikt/ds-icons";
|
||||
import { BodyLong, Button } from "@navikt/ds-react";
|
||||
import React from "react";
|
||||
const sanityClient = require("@sanity/client");
|
||||
const SanityConfig = require("../../sanity.json");
|
||||
|
||||
const client = sanityClient({
|
||||
projectId: SanityConfig.api.projectId,
|
||||
dataset: SanityConfig.api.dataset,
|
||||
apiVersion: "2021-08-21",
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
export default function SetPrivateAction(props) {
|
||||
const [isDialogOpen, setDialogOpen] = React.useState(false);
|
||||
|
||||
const isPrivate =
|
||||
props?.published?.tilgang?.privat ?? props?.draft?.tilgang?.privat;
|
||||
|
||||
const handlePatch = async (dir: boolean) => {
|
||||
props?.published &&
|
||||
(await client
|
||||
.patch(props.id)
|
||||
.set({ tilgang: { privat: dir } })
|
||||
.commit()
|
||||
.catch((err) => {
|
||||
console.error(err.message);
|
||||
}));
|
||||
props?.draft &&
|
||||
(await client
|
||||
.patch(props.draft._id)
|
||||
.set({ tilgang: { privat: dir } })
|
||||
.commit()
|
||||
.catch((err) => {
|
||||
console.error(err.message);
|
||||
}));
|
||||
props.onComplete();
|
||||
};
|
||||
|
||||
return {
|
||||
label: isPrivate ? "Åpne side for alle" : "Skjul side bak innlogging",
|
||||
/* color: "success",
|
||||
icon: PublishIcon, */
|
||||
icon: EyeScreened,
|
||||
|
||||
onHandle: async () => {
|
||||
setDialogOpen(true);
|
||||
},
|
||||
dialog: isDialogOpen && {
|
||||
type: "modal",
|
||||
onClose: props.onComplete,
|
||||
header: isPrivate
|
||||
? "Åpne innhold for alle"
|
||||
: "Gjemme innhold bak innlogging",
|
||||
content: (
|
||||
<div>
|
||||
{isPrivate ? (
|
||||
<BodyLong spacing>
|
||||
Innholdet gjøres nå tilgjengelig for alle utenfor NAV! Dette kan
|
||||
medføre konsekvenser siden innholdet i artikkelen blir
|
||||
tilgjengelig for alle. Innhold som NAV potensielt ikke ønsker å
|
||||
dele offentlig kan medføre eks omdømmetap og uønsket oppmerksomhet
|
||||
utenfra. Dobbelsjekk med temaansvarlig og andre som har skrevet
|
||||
artikkelen før du velger å åpne innholdet for alle. Er du usikker
|
||||
er det bare å sende en melding på #aksel eller #aksel-redaktører
|
||||
på slack.
|
||||
</BodyLong>
|
||||
) : (
|
||||
<>
|
||||
<BodyLong spacing>
|
||||
Innholdet gjøres nå bare tilgjengelig for NAV-ansatte. Alt
|
||||
utenom "innhold" vil fortsatt være tilgjengelig for alle. Dette
|
||||
inkluderer alle bilder som er lastet opp til sanity også! Husk
|
||||
at ingen "sikker" data skal skrives i Aksel, og at
|
||||
innloggings-barrieren er ment for å kunne dele innhold som vi
|
||||
ikke er helt trygg på å dele med alle.
|
||||
</BodyLong>
|
||||
Dette vil fortsatt være åpent for alle
|
||||
<ul>
|
||||
<li>Sidetittel</li>
|
||||
<li>Ingress</li>
|
||||
<li>Redaktører</li>
|
||||
<li>Bilder/filer</li>
|
||||
<li>SEO</li>
|
||||
</ul>
|
||||
Dette vil bare være tilgjengelig for NAV-ansatte
|
||||
<ul>
|
||||
<li>"Innhold"-blokken</li>
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
<div style={{ display: "flex", gap: "6px", marginTop: 8 }}>
|
||||
{isPrivate ? (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setDialogOpen(false);
|
||||
handlePatch(false);
|
||||
}}
|
||||
>
|
||||
Åpne innholdet for alle
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setDialogOpen(false);
|
||||
props.onComplete();
|
||||
}}
|
||||
>
|
||||
Avbryt
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setDialogOpen(false);
|
||||
handlePatch(true);
|
||||
}}
|
||||
>
|
||||
Skjul innholdet bak innlogging
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setDialogOpen(false);
|
||||
props.onComplete();
|
||||
}}
|
||||
>
|
||||
Avbryt
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import MediaAssetSource from "part:sanity-plugin-media/asset-source";
|
||||
import Unsplash from "part:sanity-plugin-asset-source-unsplash/image-asset-source";
|
||||
export default [MediaAssetSource, Unsplash];
|
||||
@@ -0,0 +1,52 @@
|
||||
import { useEffect } from "react";
|
||||
import config from "config:sanity";
|
||||
|
||||
const BUNDLE_CHECK_INTERVAL = 60 * 1000;
|
||||
const BUNDLE_CHECK_INTERVAL_DECLINED = 60 * 10000;
|
||||
const CHANGES_AVAILABLE_MESSAGE =
|
||||
"Studioet er har nye endringer! For å ta i bruk disse vil siden nå oppdateres for den nyeste versjonen.";
|
||||
|
||||
async function getCurrentHash() {
|
||||
const basePath = (config.project && config.project.basePath) || "/";
|
||||
const html = await window.fetch(basePath).then((res) => res.text());
|
||||
const [, hash] = html.match(/app\.bundle\.js\?(\w+)/) || [];
|
||||
return hash;
|
||||
}
|
||||
|
||||
let hash = null;
|
||||
let interval = null;
|
||||
|
||||
const BundleChecker = () => {
|
||||
useEffect(() => {
|
||||
getCurrentHash().then((newHash) => {
|
||||
hash = newHash;
|
||||
});
|
||||
|
||||
interval = createInterval();
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
// We're a react component, in theory, so return null to not render anything
|
||||
return null;
|
||||
};
|
||||
|
||||
export default BundleChecker;
|
||||
|
||||
const createInterval = (declined?: boolean) =>
|
||||
setInterval(
|
||||
async () => {
|
||||
const newHash = await getCurrentHash();
|
||||
|
||||
if (hash && newHash !== hash) {
|
||||
clearInterval(interval);
|
||||
|
||||
if (window.confirm(CHANGES_AVAILABLE_MESSAGE)) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
interval = createInterval(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
declined ? BUNDLE_CHECK_INTERVAL_DECLINED : BUNDLE_CHECK_INTERVAL
|
||||
);
|
||||
@@ -0,0 +1,44 @@
|
||||
import { allDocumentTypes } from "../config";
|
||||
|
||||
export default {
|
||||
widgets: [
|
||||
{
|
||||
name: "document-list",
|
||||
options: {
|
||||
title: "Drafts for God Praksis",
|
||||
query: '*[_type in $types && (_id in path("drafts.**"))]',
|
||||
queryParams: {
|
||||
types: allDocumentTypes.filter((x) => x.startsWith("gp")),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "document-list",
|
||||
options: {
|
||||
title: "Drafts for Designsystemet",
|
||||
query: '*[_type in $types && (_id in path("drafts.**"))]',
|
||||
queryParams: {
|
||||
types: allDocumentTypes.filter((x) => x.startsWith("ds")),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "outdated-documents-widget",
|
||||
options: {
|
||||
type: "warning",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "outdated-documents-widget",
|
||||
options: {
|
||||
type: "error",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "project-info",
|
||||
},
|
||||
{
|
||||
name: "project-users",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Picture } from "@navikt/ds-icons";
|
||||
import S from "@sanity/desk-tool/structure-builder";
|
||||
|
||||
import React from "react";
|
||||
|
||||
export const adminPanel = async (roles) =>
|
||||
roles.find((x) => x.name === "administrator") &&
|
||||
S.listItem()
|
||||
.title("Admin")
|
||||
.child(
|
||||
S.list()
|
||||
.title("Admin")
|
||||
.items([
|
||||
S.documentListItem()
|
||||
.title(`Forside Aksel`)
|
||||
.schemaType(`vk_frontpage`)
|
||||
.icon(() => <Picture />)
|
||||
.id(`frontpage_vk_praksis`),
|
||||
S.listItem()
|
||||
.title("Standalone-sider")
|
||||
.child(S.documentTypeList("aksel_standalone")),
|
||||
])
|
||||
);
|
||||
@@ -0,0 +1,102 @@
|
||||
import { prinsipper } from "@/lib";
|
||||
import { Facilitet } from "@navikt/ds-icons";
|
||||
import S from "@sanity/desk-tool/structure-builder";
|
||||
import documentStore from "part:@sanity/base/datastore/document";
|
||||
import React from "react";
|
||||
import { map } from "rxjs/operators";
|
||||
|
||||
const temaQuery = `*[_type == "aksel_tema" && count(*[references(^._id)]) > 0]`;
|
||||
|
||||
const AkselLogo = () => (
|
||||
<svg
|
||||
width="192px"
|
||||
height="192px"
|
||||
viewBox="0 0 192 192"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
strokeLinecap="round"
|
||||
strokeWidth="10"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path d="m41.63 150.3 47.39-47.38" />
|
||||
<path d="M40.92 80.3 77.7 43.53c4.25-4.24 9.9-4.24 14.15 0l56.56 56.57c4.25 4.25 4.25 9.9 0 14.14l-36.77 36.77" />
|
||||
<path d="m40.92 80.3 38.19-38.18c3.53-3.54 6.72-3.18 13.79-3.18h48.79s0 0 0 0c6.36 0 11.31 4.95 11.31 11.31 0 0 0 0 0 0v48.8c0 7.06.35 10.24-3.18 13.78l-38.19 38.18" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const akselInnhold = async () => {
|
||||
return S.listItem()
|
||||
.title("Aksel")
|
||||
.icon(() => <AkselLogo />)
|
||||
.child(
|
||||
S.list()
|
||||
.title("Innhold")
|
||||
.items([
|
||||
S.listItem()
|
||||
.title("Artikler")
|
||||
.child(() =>
|
||||
documentStore.listenQuery(temaQuery).pipe(
|
||||
map((tema: any) => {
|
||||
return S.list()
|
||||
.title("Artikler")
|
||||
.items([
|
||||
S.listItem()
|
||||
.title("Alle artikler")
|
||||
.child(S.documentTypeList("aksel_artikkel")),
|
||||
S.listItem()
|
||||
.title("Uten tema")
|
||||
.child(
|
||||
S.documentTypeList("aksel_artikkel").filter(
|
||||
`_type == "aksel_artikkel" && !defined(tema)`
|
||||
)
|
||||
),
|
||||
...tema.map((tag) => {
|
||||
return S.listItem()
|
||||
.title(tag.title)
|
||||
.child(
|
||||
S.documentList()
|
||||
.title(tag.title)
|
||||
.filter(
|
||||
`_type == 'aksel_artikkel' && $tag in tema[]->title`
|
||||
)
|
||||
.params({ tag: tag.title })
|
||||
);
|
||||
}),
|
||||
]);
|
||||
})
|
||||
)
|
||||
),
|
||||
S.listItem().title("Blogg").child(S.documentTypeList("aksel_blogg")),
|
||||
S.listItem()
|
||||
.title("Prinsipper")
|
||||
.child(
|
||||
S.list()
|
||||
.title("Prinsipper")
|
||||
.items([
|
||||
S.listItem()
|
||||
.title("Alle artikler")
|
||||
.child(S.documentTypeList("aksel_prinsipp")),
|
||||
...prinsipper.map(({ value, title }) =>
|
||||
S.listItem()
|
||||
.title(title)
|
||||
.child(
|
||||
S.documentList()
|
||||
.title(title)
|
||||
.filter(
|
||||
`_type == 'aksel_prinsipp' && $value == prinsipp.prinsippvalg`
|
||||
)
|
||||
.params({ value })
|
||||
)
|
||||
),
|
||||
])
|
||||
),
|
||||
S.divider(),
|
||||
S.listItem()
|
||||
.title("Temasider")
|
||||
.icon(() => <Facilitet />)
|
||||
.child(S.documentTypeList("aksel_tema")),
|
||||
])
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
import { CoApplicant, Facilitet, Information, People } from "@navikt/ds-icons";
|
||||
import S from "@sanity/desk-tool/structure-builder";
|
||||
import userStore from "part:@sanity/base/user";
|
||||
import React from "react";
|
||||
import { createSuperPane } from "sanity-super-pane";
|
||||
import { ComponentPageWebPreview } from "../../web-previews/ComponentWebPreview";
|
||||
import { KomponentPreview } from "../../web-previews/KomponentPreview";
|
||||
import { PageWebPreview } from "../../web-previews/PageWebPreview";
|
||||
import { adminPanel } from "./admin";
|
||||
import { akselInnhold } from "./aksel";
|
||||
import { dsPanel } from "./ds";
|
||||
import { profilePanel } from "./profile";
|
||||
|
||||
export const getDefaultDocumentNode = ({ schemaType }) => {
|
||||
switch (schemaType) {
|
||||
case "komponent_artikkel":
|
||||
return S.document().views([
|
||||
S.view.form(),
|
||||
S.view.component(ComponentPageWebPreview).title("Preview"),
|
||||
S.view.component(KomponentPreview).title("Preview-dev"),
|
||||
]);
|
||||
case "ds_artikkel":
|
||||
return S.document().views([
|
||||
S.view.form(),
|
||||
S.view.component(PageWebPreview).title("Preview"),
|
||||
S.view.component(KomponentPreview).title("Preview-dev"),
|
||||
]);
|
||||
case "aksel_artikkel":
|
||||
return S.document().views([
|
||||
S.view.form(),
|
||||
S.view.component(PageWebPreview).title("Preview"),
|
||||
S.view.component(KomponentPreview).title("Preview-dev"),
|
||||
]);
|
||||
case "aksel_blogg":
|
||||
return S.document().views([
|
||||
S.view.form(),
|
||||
S.view.component(PageWebPreview).title("Preview"),
|
||||
S.view.component(KomponentPreview).title("Preview-dev"),
|
||||
]);
|
||||
case "aksel_prinsipp":
|
||||
return S.document().views([
|
||||
S.view.form(),
|
||||
S.view.component(PageWebPreview).title("Preview"),
|
||||
S.view.component(KomponentPreview).title("Preview-dev"),
|
||||
]);
|
||||
case "aksel_standalone":
|
||||
return S.document().views([
|
||||
S.view.form(),
|
||||
S.view.component(PageWebPreview).title("Preview"),
|
||||
S.view.component(KomponentPreview).title("Preview-dev"),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
const items = [
|
||||
S.divider(),
|
||||
S.listItem()
|
||||
.title("Redaktører")
|
||||
.icon(() => <People />)
|
||||
.child(createSuperPane("editor")),
|
||||
|
||||
S.divider(),
|
||||
];
|
||||
|
||||
export default () => {
|
||||
return userStore.getCurrentUser().then(async ({ roles, id }) => {
|
||||
const panels = [];
|
||||
const admin = await adminPanel(roles);
|
||||
|
||||
const profile = await profilePanel(id);
|
||||
const ds = await dsPanel(roles);
|
||||
const aksel = await akselInnhold();
|
||||
const intro = [];
|
||||
|
||||
profile && intro.push(profile);
|
||||
profile && intro.push(S.divider());
|
||||
|
||||
let struct = items;
|
||||
|
||||
if (ds) {
|
||||
struct = [ds, ...struct];
|
||||
}
|
||||
struct = [aksel, ...struct];
|
||||
if (admin) {
|
||||
struct = [...struct, admin];
|
||||
}
|
||||
|
||||
return S.list()
|
||||
.title("Aksel")
|
||||
.items([...intro, ...struct, ...panels]);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,119 @@
|
||||
import {
|
||||
Facilitet,
|
||||
FileContent,
|
||||
Findout,
|
||||
Folder,
|
||||
Picture,
|
||||
Place,
|
||||
Ruler,
|
||||
Task,
|
||||
} from "@navikt/ds-icons";
|
||||
import S from "@sanity/desk-tool/structure-builder";
|
||||
import client from "part:@sanity/base/client";
|
||||
import React from "react";
|
||||
import { createSuperPane } from "sanity-super-pane";
|
||||
|
||||
export const dsPanel = async (roles) => {
|
||||
const role = roles.some(({ name }) => "aksel-editor" === name);
|
||||
|
||||
if (role) {
|
||||
return null;
|
||||
} else {
|
||||
return S.listItem()
|
||||
.title("Designsystemet")
|
||||
.icon(() => <Facilitet />)
|
||||
.child(
|
||||
S.list()
|
||||
.title("Designsystem")
|
||||
.items([
|
||||
S.listItem()
|
||||
.title("Komponentsider")
|
||||
.icon(() => <Facilitet />)
|
||||
.child(createSuperPane("komponent_artikkel")),
|
||||
S.listItem()
|
||||
.title("Artikler")
|
||||
.icon(() => <FileContent />)
|
||||
.child(createSuperPane("ds_artikkel")),
|
||||
S.divider(),
|
||||
S.documentListItem()
|
||||
.title(`Navigasjon`)
|
||||
.schemaType(`ds_navigation`)
|
||||
.icon(() => <Place />)
|
||||
.id(`ds_navigationid`),
|
||||
|
||||
S.listItem()
|
||||
.title("Publiserte sider ikke i navigasjon")
|
||||
.child(async (): Promise<any> => {
|
||||
const doc = await client.fetch(
|
||||
`*[_id == 'ds_navigationid'][0]{
|
||||
headings[]{
|
||||
title,
|
||||
menu
|
||||
}
|
||||
}`
|
||||
);
|
||||
if (!doc.headings) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let allIds = "";
|
||||
try {
|
||||
allIds = doc.headings
|
||||
.map((heading) =>
|
||||
heading.menu
|
||||
.filter((item) => item._type === "item")
|
||||
.map((item) => item.link._ref)
|
||||
.map((x) => `"${x}"`)
|
||||
.join(",")
|
||||
)
|
||||
.join(",");
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
return S.documentList()
|
||||
.title("Sider ikke i navigasjon")
|
||||
.filter(
|
||||
`!(_id in [${allIds}]) && !(_id in path('drafts.**')) && _type in ["komponent_artikkel", "ds_artikkel"]`
|
||||
);
|
||||
}),
|
||||
S.documentListItem()
|
||||
.title(`Forside`)
|
||||
.schemaType(`ds_frontpage`)
|
||||
.icon(() => <Picture />)
|
||||
.id(`frontpage_designsystem`),
|
||||
S.divider(),
|
||||
S.listItem()
|
||||
.title("Autogenerert data")
|
||||
.child(
|
||||
S.list()
|
||||
.title("Autogenerert data")
|
||||
.items([
|
||||
S.listItem()
|
||||
.title("Eksempler")
|
||||
.icon(() => <span>{`EX`}</span>)
|
||||
.child(S.documentTypeList("kode_eksempler_fil")),
|
||||
S.listItem()
|
||||
.title("Fargekategorier")
|
||||
.icon(() => <Folder />)
|
||||
.child(S.documentTypeList("ds_color_categories")),
|
||||
S.listItem()
|
||||
.title("Props")
|
||||
.icon(() => <Folder />)
|
||||
.child(S.documentTypeList("ds_props")),
|
||||
S.listItem()
|
||||
.title("Tokens")
|
||||
.icon(() => <Folder />)
|
||||
.child(S.documentTypeList("ds_tokens")),
|
||||
])
|
||||
),
|
||||
S.divider(),
|
||||
S.documentListItem()
|
||||
.title(`Komponent-template`)
|
||||
.schemaType(`ds_component_template`)
|
||||
.icon(() => <Task />)
|
||||
.id(`ds_component_templateid`),
|
||||
])
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Picture } from "@navikt/ds-icons";
|
||||
import S from "@sanity/desk-tool/structure-builder";
|
||||
import userStore from "part:@sanity/base/user";
|
||||
import client from "part:@sanity/base/client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
export const profilePanel = async (id) => {
|
||||
const ids = await client.withConfig({ apiVersion: "2021-05-31" })
|
||||
.fetch(`*[_type == "editor"]{
|
||||
_id,
|
||||
user_id
|
||||
}`);
|
||||
|
||||
const editor = ids.find(({ user_id }) => user_id?.current === id);
|
||||
|
||||
if (editor) {
|
||||
return S.documentListItem()
|
||||
.title(`Profil`)
|
||||
.schemaType(`editor`)
|
||||
.icon(() => <Picture />)
|
||||
.id(editor._id);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
import React from "react";
|
||||
import { People } from "@navikt/ds-icons";
|
||||
import T from "@sanity/base/initial-value-template-builder";
|
||||
import userStore from "part:@sanity/base/user";
|
||||
|
||||
const sanityClient = require("@sanity/client");
|
||||
const sanityToken = process.env.SANITY_TOKEN;
|
||||
const SanityConfig = require("../sanity.json");
|
||||
|
||||
const client = sanityClient({
|
||||
projectId: SanityConfig.api.projectId,
|
||||
dataset: SanityConfig.api.dataset,
|
||||
apiVersion: "2020-06-19",
|
||||
/* Token er safe og har bare tillatelse til å lese template doc */
|
||||
token:
|
||||
"skt6kqEcXmRxUTGDzdEcw11g7HJi9DaHX2fyn8Z0Dearr1k67ykW2N64KTP9GxAPNN6P8TlzPjUdkfGsvbJkfmwfq0UNudKfMYOfyz9OeA666y15pPLRr6D7xnrJGEEBYwSvG6baLLiJI478YowWpjqp1Mz2U1PbAPnIMW4W5btTpdQbwPSD",
|
||||
useCdn: false,
|
||||
});
|
||||
|
||||
export default [
|
||||
...T.defaults().filter((x) => !["komponent_artikkel"].includes(x.spec.id)),
|
||||
|
||||
T.template({
|
||||
id: "komponent_artikkel_template",
|
||||
title: "Mal for Komponentsider",
|
||||
schemaType: "komponent_artikkel",
|
||||
value: async () => {
|
||||
const doc = await client.fetch(`*[_type == "ds_component_template"][0]`);
|
||||
return doc
|
||||
? Object.entries(doc).reduce((old, [key, val]) => {
|
||||
return key.startsWith("_") ? { ...old } : { ...old, [key]: val };
|
||||
}, {})
|
||||
: {};
|
||||
},
|
||||
}),
|
||||
T.template({
|
||||
id: "newEditor",
|
||||
title: "Editor",
|
||||
schemaType: "editor",
|
||||
icon: () => <People />,
|
||||
value: async () => {
|
||||
const { id, displayName } = await userStore.getUser("me");
|
||||
|
||||
return {
|
||||
title: displayName,
|
||||
user_id: {
|
||||
current: id,
|
||||
},
|
||||
_type: "editor",
|
||||
};
|
||||
},
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,38 @@
|
||||
import S from "@sanity/base/structure-builder";
|
||||
|
||||
const docTypes = [
|
||||
{
|
||||
name: "aksel_artikkel",
|
||||
title: "Aksel Artikkel",
|
||||
},
|
||||
{
|
||||
name: "newEditor",
|
||||
title: "Bruker/redaktør",
|
||||
desc: "Lager en psuedo-bruker for deg som kan koble artikler til brukeren",
|
||||
},
|
||||
{
|
||||
name: "aksel_blogg",
|
||||
title: "Bloggpost",
|
||||
},
|
||||
{
|
||||
name: "ds_artikkel",
|
||||
title: "Designsystem Artikkel",
|
||||
desc: "Atikkel som legges under designsystemet",
|
||||
},
|
||||
{
|
||||
name: "komponent_artikkel_template",
|
||||
title: "Designsystem Komponentside",
|
||||
desc: "Atikkel som legges under designsystemet",
|
||||
},
|
||||
];
|
||||
|
||||
export default [
|
||||
...docTypes.map((doct) =>
|
||||
S.initialValueTemplateItem(doct.name)
|
||||
.id(`doc-${doct.name}`)
|
||||
.title(doct.title)
|
||||
.templateId(doct.name)
|
||||
.description(doct?.desc ?? "")
|
||||
),
|
||||
/* ...S.defaultInitialValueTemplateItems() */
|
||||
];
|
||||
@@ -0,0 +1,15 @@
|
||||
import defaultResolve, {
|
||||
PublishAction,
|
||||
} from "part:@sanity/base/document-actions";
|
||||
import SetAndPublishAction from "./actions/publishedAt";
|
||||
/* import SetPrivateAction from "./actions/set-private"; */
|
||||
import { allDocumentTypes } from "../config";
|
||||
|
||||
export default function resolveDocumentActions(props) {
|
||||
const [publish, ...rest] = defaultResolve(props).map((Action) => {
|
||||
return allDocumentTypes.includes(props.type) && Action === PublishAction
|
||||
? SetAndPublishAction
|
||||
: Action;
|
||||
});
|
||||
return [publish /* SetPrivateAction */, ...rest];
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Button, Card, Inline, Stack, Text } from "@sanity/ui";
|
||||
import moment from "moment";
|
||||
import PatchEvent, { set } from "part:@sanity/form-builder/patch-event";
|
||||
import React from "react";
|
||||
|
||||
const CustomDisplay = React.forwardRef((props, ref) => {
|
||||
const click = () => {
|
||||
props.onChange &&
|
||||
props.onChange(
|
||||
PatchEvent.from(set(new Date().toISOString().split("T")[0]))
|
||||
);
|
||||
};
|
||||
|
||||
const lastUpdate = moment(props.value);
|
||||
const daysSince = moment().diff(lastUpdate, "days");
|
||||
|
||||
return (
|
||||
<Stack tabIndex={0} space={4} ref={ref}>
|
||||
{daysSince > 90 && (
|
||||
<Card
|
||||
padding={[3, 3, 4]}
|
||||
radius={2}
|
||||
shadow={1}
|
||||
tone={daysSince > 180 ? "critical" : "caution"}
|
||||
>
|
||||
<Text align="center" size={[2, 2, 3]}>
|
||||
{daysSince > 180
|
||||
? "Innholdet er utdatert!"
|
||||
: "Innholdet er stagnert!"}
|
||||
</Text>
|
||||
</Card>
|
||||
)}
|
||||
<Stack space={4}>
|
||||
<Text size={1} weight="semibold">
|
||||
Sist oppdatert
|
||||
</Text>
|
||||
<Text size={2}>{props.value?.split("-").reverse().join("/")}</Text>
|
||||
</Stack>
|
||||
|
||||
<Inline space={[3, 3, 4]}>
|
||||
<Button
|
||||
onClick={() => click()}
|
||||
fontSize={[2]}
|
||||
mode="ghost"
|
||||
padding={[2, 2]}
|
||||
text="Oppdater"
|
||||
/>
|
||||
</Inline>
|
||||
</Stack>
|
||||
);
|
||||
});
|
||||
|
||||
export default CustomDisplay;
|
||||
@@ -0,0 +1,30 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { withDocument } from "part:@sanity/form-builder";
|
||||
import {
|
||||
TextInput,
|
||||
Stack,
|
||||
Label,
|
||||
Checkbox,
|
||||
Radio,
|
||||
Flex,
|
||||
Text,
|
||||
Box,
|
||||
Card,
|
||||
} from "@sanity/ui";
|
||||
import styled from "styled-components";
|
||||
/* TODO: Implement this. Issue now is that document returns null in props */
|
||||
const CustomDisplay = React.forwardRef((props, ref) => {
|
||||
const [doc, setDoc] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
doc && setDoc(props.document);
|
||||
}, [props]);
|
||||
|
||||
return (
|
||||
<Stack space={0} ref={ref}>
|
||||
<Card padding={0}>T</Card>
|
||||
</Stack>
|
||||
);
|
||||
});
|
||||
|
||||
export default withDocument(CustomDisplay);
|
||||
@@ -0,0 +1,49 @@
|
||||
import React from "react";
|
||||
import Preview from "part:@sanity/base/preview";
|
||||
import { IntentLink } from "part:@sanity/base/router";
|
||||
import schema from "part:@sanity/base/schema";
|
||||
import {
|
||||
Item as DefaultItem,
|
||||
List as DefaultList,
|
||||
} from "part:@sanity/components/lists/default";
|
||||
import styles from "./profile.css";
|
||||
|
||||
const ReferringDocumentsList = (props) => {
|
||||
const { documents } = props;
|
||||
return (
|
||||
<DefaultList className={styles.root}>
|
||||
{documents
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(b._updatedAt).getTime() - new Date(a._updatedAt).getTime()
|
||||
)
|
||||
.map((document) => {
|
||||
const schemaType = schema.get(document._type);
|
||||
return (
|
||||
<DefaultItem key={document._id} className={styles.item}>
|
||||
{schemaType ? (
|
||||
<IntentLink
|
||||
className={styles.link}
|
||||
intent="edit"
|
||||
params={{ id: document._id, type: document._type }}
|
||||
>
|
||||
<Preview value={document} type={schemaType} />
|
||||
{document._updatedAt
|
||||
.split("T")[0]
|
||||
.split("-")
|
||||
.reverse()
|
||||
.join("-")}
|
||||
</IntentLink>
|
||||
) : (
|
||||
<div>
|
||||
A document of the unknown type <em>{document._type}</em>
|
||||
</div>
|
||||
)}
|
||||
</DefaultItem>
|
||||
);
|
||||
})}
|
||||
</DefaultList>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReferringDocumentsList;
|
||||
@@ -0,0 +1,18 @@
|
||||
export const getUnpublished = (docs: Record<string, any>[]) =>
|
||||
docs.filter(
|
||||
(doc, i, arr) =>
|
||||
doc._id.startsWith("drafts.") &&
|
||||
!arr.find((x, y) => i !== y && doc._id.includes(x._id))
|
||||
);
|
||||
|
||||
export const getDrafts = (docs: Record<string, any>[]) =>
|
||||
docs.filter((doc, i, arr) =>
|
||||
arr.find((x, y) => i !== y && x._id.includes(doc._id))
|
||||
);
|
||||
|
||||
export const getPublished = (docs: Record<string, any>[]) =>
|
||||
docs
|
||||
.filter(
|
||||
(doc, i, arr) => !arr.find((x, y) => i !== y && x._id.includes(doc._id))
|
||||
)
|
||||
.filter((doc) => !doc._id.includes("drafts"));
|
||||
@@ -0,0 +1,171 @@
|
||||
import { Caseworker, Sandglass, Upload, Wrench } from "@navikt/ds-icons";
|
||||
import {
|
||||
BodyShort,
|
||||
Detail,
|
||||
Heading,
|
||||
HelpText,
|
||||
Label,
|
||||
Link,
|
||||
ReadMore,
|
||||
} from "@navikt/ds-react";
|
||||
import { SanityDocument } from "@sanity/client";
|
||||
import { Stack } from "@sanity/ui";
|
||||
import userStore from "part:@sanity/base/user";
|
||||
import { WithReferringDocuments } from "part:@sanity/base/with-referring-documents";
|
||||
import Spinner from "part:@sanity/components/loading/spinner";
|
||||
import { withDocument } from "part:@sanity/form-builder";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import ReferringDocumentsList from "./document-list";
|
||||
import styles from "../styles.css";
|
||||
import { getDrafts, getPublished, getUnpublished } from "./filter-docs";
|
||||
|
||||
type Props = {
|
||||
referringDocuments: Record<string, any>[];
|
||||
isLoading: boolean;
|
||||
published?: SanityDocument | null;
|
||||
type: any;
|
||||
document: any;
|
||||
};
|
||||
|
||||
const IntroPage = React.forwardRef<HTMLDivElement, Props>((props, ref) => {
|
||||
const { document } = props;
|
||||
const [user, setUser] = useState<any>({});
|
||||
|
||||
useEffect(() => {
|
||||
userStore.getCurrentUser().then((user) => setUser(user));
|
||||
}, []);
|
||||
|
||||
const EmptyState = () => (
|
||||
<Detail className={styles.detailsContent} size="small">
|
||||
Ingen sider her...
|
||||
</Detail>
|
||||
);
|
||||
if (
|
||||
!document._id ||
|
||||
(document?.user_id?.current !== user.id &&
|
||||
!user?.roles?.find((x) => x.name === "administrator"))
|
||||
)
|
||||
return null;
|
||||
|
||||
return (
|
||||
<Stack ref={ref} space={[3, 1, 2, 4]}>
|
||||
<Heading level="2" size="medium" spacing style={{ display: "flex" }}>
|
||||
Profil <Caseworker style={{ marginLeft: "0.5rem", fontSize: "2rem" }} />
|
||||
</Heading>
|
||||
{user?.roles && (
|
||||
<div>
|
||||
<Heading level="3" size="small" spacing>
|
||||
Roller/tilganger:
|
||||
</Heading>
|
||||
{user?.roles.map((x, i) => (
|
||||
<div key={i}>
|
||||
<Label size="small" spacing as="p">
|
||||
{x?.title}
|
||||
</Label>
|
||||
<BodyShort size="small">{x?.description}</BodyShort>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<BodyShort
|
||||
size="small"
|
||||
spacing
|
||||
as="span"
|
||||
style={{ display: "flex", gap: "0.5rem" }}
|
||||
>
|
||||
<ReadMore header="Har du ikke de rollene du trenger?">
|
||||
Send en melding til oss på{" "}
|
||||
<Link href="https://nav-it.slack.com/archives/C0370ADS0HX">
|
||||
#Aksel
|
||||
</Link>{" "}
|
||||
slack så fikser vi det.
|
||||
</ReadMore>
|
||||
</BodyShort>
|
||||
<div style={{ borderTop: "1px solid #bbb" }} />
|
||||
|
||||
<WithReferringDocuments id={document._id?.replace("drafts.", "")}>
|
||||
{({ referringDocuments, isLoading }) => {
|
||||
if (isLoading) {
|
||||
return <Spinner message="Looking for referring documents…" />;
|
||||
}
|
||||
if (!referringDocuments.length) return null;
|
||||
|
||||
const drafts = getDrafts(referringDocuments);
|
||||
const published = getPublished(referringDocuments);
|
||||
const unpublished = getUnpublished(referringDocuments);
|
||||
|
||||
return (
|
||||
<Stack space={2} className={styles.profileDetails}>
|
||||
<Heading level="3" size="xsmall" style={{ display: "flex" }}>
|
||||
<Upload />
|
||||
Publiserte sider
|
||||
</Heading>
|
||||
<details>
|
||||
<Label size="small" as="summary">
|
||||
<BodyShort size="small">
|
||||
Oppdaterte: {published.length}
|
||||
</BodyShort>
|
||||
</Label>
|
||||
{published ? (
|
||||
<ReferringDocumentsList documents={published} />
|
||||
) : (
|
||||
<EmptyState />
|
||||
)}
|
||||
</details>
|
||||
<details>
|
||||
<Label size="small" as="summary">
|
||||
<BodyShort size="small">
|
||||
Med upubliserte endringer: {drafts.length}
|
||||
</BodyShort>
|
||||
</Label>
|
||||
{drafts ? (
|
||||
<ReferringDocumentsList documents={drafts} />
|
||||
) : (
|
||||
<EmptyState />
|
||||
)}
|
||||
</details>
|
||||
<Heading level="3" size="xsmall" className={styles.headings}>
|
||||
<Wrench />
|
||||
Under arbeid
|
||||
</Heading>
|
||||
<details>
|
||||
<Label size="small" as="summary">
|
||||
<BodyShort size="small">
|
||||
Sider: {unpublished.length}
|
||||
</BodyShort>
|
||||
</Label>
|
||||
{unpublished ? (
|
||||
<ReferringDocumentsList documents={unpublished} />
|
||||
) : (
|
||||
<EmptyState />
|
||||
)}
|
||||
</details>
|
||||
<Heading level="3" size="xsmall" className={styles.headings}>
|
||||
<Sandglass />
|
||||
Utdaterte sider
|
||||
</Heading>
|
||||
<details>
|
||||
<Label size="small" as="summary">
|
||||
<BodyShort size="small">
|
||||
Ikke oppdatert på over 90 dager: 0
|
||||
</BodyShort>
|
||||
</Label>
|
||||
<EmptyState />
|
||||
</details>
|
||||
<details>
|
||||
<Label size="small" as="summary">
|
||||
<BodyShort size="small">
|
||||
Ikke oppdatert på over 180 dager: 0
|
||||
</BodyShort>
|
||||
</Label>
|
||||
<EmptyState />
|
||||
</details>
|
||||
</Stack>
|
||||
);
|
||||
}}
|
||||
</WithReferringDocuments>
|
||||
</Stack>
|
||||
);
|
||||
});
|
||||
|
||||
export default withDocument(IntroPage);
|
||||
Loaded 100 of 559 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user