Compare commits

...
Author SHA1 Message Date
Ken 34d41ecc78 📝 Patch-version 2022-11-02 20:42:21 +01:00
Ken 7e62365a60 🔥 Fjernet flaky e2e test 2022-11-02 19:29:52 +01:00
Ken 8565f410a1 💄 Nye tags i sidebar 2022-11-02 18:16:42 +01:00
Ken 46d70820df 📝 Version-bump 2022-11-02 18:09:27 +01:00
github-actions[bot]andgithub-actions[bot] 67e66ffe02 Version Packages (#1707)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-11-02 16:29:26 +01:00
Ken e19bf67b33 [FEATURE] Tag varianter (#1684) 2022-11-02 16:25:31 +01:00
Ken 97c5f60e91 Chips (#1668) 2022-11-02 16:20:42 +01:00
Ken ea5293c28d ⬆️ Patch-versjon 2022-11-02 10:57:45 +01:00
Ken 6a58efa9a9 changeset 2022-11-02 10:55:34 +01:00
Ken c95f33679e 💚 Fikset timezones for jest-tester
✨ Timekeeper-demo

✨ timezone for jest-tester
2022-11-02 10:53:19 +01:00
Ken b52a5e02a0 ✨ Patch-versjoner 2022-11-01 23:57:38 +01:00
Ken ab3ef0d34b ✨ Validering i Date-komponenter
🐛 Date-formats skaper problemer for ranged-input

🐛 UX tweaks for inputs

🐛 Fikset weekendValidation

🎨 Custom input-format

🎨 validation api

🎉 range-validation

🎨 Export validation-types

🎨 Datepicker-validation

🎨 monthpicker validation

🎨 Småbugs med disabled datepicker

💄 reversert capitalization i månedsnavn
2022-11-01 23:54:27 +01:00
Ken 69798f6ee2 🔥 Fjernet flaky e2e test 2022-11-01 11:00:35 +01:00
Ken ee14586beb ⬆️ yarn-lock 2022-11-01 10:40:34 +01:00
Ken 82470ba669 🐛 Synket dropdown med Figma 2022-11-01 10:18:40 +01:00
github-actions[bot]andgithub-actions[bot] 6286f0e8e6 Version Packages (#1705)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-31 13:34:55 +01:00
Ken 3c08651df2 ✨ Changeset 2022-10-31 13:26:02 +01:00
Ken e6d55fdcd6 changeset 2022-10-31 13:24:57 +01:00
Ken 8c445f020c 💚 Flere workers for e2e testing 2022-10-31 12:07:11 +01:00
Ken 78a76bac5d 🎉 Migrert aksel-website repo (#1704) 2022-10-31 11:44:38 +01:00
Ken 75b334af61 Merge branch 'master' of https://github.com/navikt/Designsystemet 2022-10-28 17:29:58 +02:00
Ken 5d98c50b80 🐛 Fikset eksempel 2022-10-28 17:28:30 +02:00
github-actions[bot]andgithub-actions[bot] 27803fd37c Version Packages (#1703)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-28 17:27:39 +02:00
Ken 1bdb7e3777 📝 changeset 2022-10-28 17:23:09 +02:00
Andreas NordahlandKen 20bcc28cb0 provider with modal config (#1702)
Co-authored-by: Ken <ken.aleksander@gmail.com>
2022-10-28 17:14:39 +02:00
github-actions[bot]andgithub-actions[bot] f43ec5c48c Version Packages (#1701)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-28 16:13:41 +02:00
Ken 40f0b3340d changeset 2022-10-28 15:58:32 +02:00
Ken 1f023a84be Repo-cleanup (#1700) 2022-10-28 10:59:24 +02:00
Ken f170a2201c 💚 ci-build 2022-10-27 14:32:50 +02:00
559 changed files with 42168 additions and 2027 deletions

No files matched your search

+1 -1
View File
@@ -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"
+71
View File
@@ -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 }}
+81
View File
@@ -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 }}
+41
View File
@@ -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 }}
+39
View File
@@ -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"
+1 -2
View File
@@ -4,11 +4,10 @@ on:
push:
branches:
- "master"
- "next"
paths:
- "@navikt/**"
pull_request:
branches: [master]
branches: [master, next]
paths:
- "@navikt/**"
-1
View File
@@ -6,7 +6,6 @@ on:
- master
- next
pull_request:
types: [opened, edited]
workflow_dispatch:
jobs:
+71
View File
@@ -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
+56
View File
@@ -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
+2 -2
View File
@@ -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
+25
View File
@@ -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
View File
@@ -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/
+2
View File
@@ -0,0 +1,2 @@
**/nais-dev.yaml
**/nais-prod.yaml
+4
View File
@@ -15,3 +15,7 @@
.rowgap[data-theme="dark"] {
background-color: #262626;
}
.rowgap-wrap {
flex-wrap: wrap;
}
-1
View File
@@ -17,7 +17,6 @@ module.exports = {
},
stories: () => [...getStories(), "./*.stories.mdx"],
addons: [
"@storybook/addon-docs",
"@storybook/addon-a11y",
{
name: "@storybook/addon-storysource",
+22
View File
@@ -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
+5 -3
View File
@@ -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": {
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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`,
+44
View File
@@ -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
+125
View File
@@ -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;
}
+1
View File
@@ -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";
+5 -5
View File
@@ -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
View File
@@ -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));
}
+85
View File
@@ -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
+12 -10
View File
@@ -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",
-1
View File
@@ -32,7 +32,6 @@ const Bubble: BubbleType = forwardRef(
ref={ref}
className={cl("navds-chat__bubble", className)}
style={{ backgroundColor: backgroundColor }}
tabIndex={0}
{...rest}
>
{(timestamp || name) && (
+45
View File
@@ -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;
+41
View File
@@ -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>
);
};
+1
View File
@@ -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,
};
+3
View File
@@ -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 -1
View File
@@ -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";
+2
View File
@@ -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";
+15 -3
View File
@@ -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;
+1
View File
@@ -0,0 +1 @@
export { default as Provider, useProvider } from "./Provider";
+1 -1
View File
@@ -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;
+20 -8
View File
@@ -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,
+82 -5
View File
@@ -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>
+3 -2
View File
@@ -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({
-15
View File
@@ -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>>
+22
View File
@@ -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
+7 -7
View File
@@ -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"
}
}
+22
View File
@@ -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
+1 -3
View File
@@ -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":
+6 -6
View File
@@ -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"
}
}
+22
View File
@@ -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
+1 -5
View File
@@ -78,8 +78,4 @@ const main = async () => {
console.log("\nDownloaded all icons from Figma!\n");
};
try {
main();
} catch (e) {
throw e;
}
main();
+10 -9
View File
@@ -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"
}
}
+2 -1
View File
@@ -4,5 +4,6 @@
"outDir": "./cjs",
"module": "commonjs"
},
"exclude": ["./figma", "./util", "./tests", "./scripts", "**/*.stories.*"]
"exclude": ["./figma", "./util", "./tests", "./scripts", "**/*.stories.*"],
"include": ["./src"]
}
+26
View File
@@ -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 -2
View File
@@ -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);
}
+3 -3
View File
@@ -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"
}
}
+74
View File
@@ -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
+8 -7
View File
@@ -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",
+2 -1
View File
@@ -3,5 +3,6 @@
"compilerOptions": {
"outDir": "./cjs",
"module": "commonjs"
}
},
"include": ["./src"]
}
+2 -8
View File
@@ -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"),
+4 -6
View File
@@ -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)
+17
View File
@@ -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
+92
View File
@@ -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
+48
View File
@@ -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
}
+6
View File
@@ -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
+49
View File
@@ -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
+55
View File
@@ -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
+33
View File
@@ -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