Compare commits

..
Author SHA1 Message Date
github-actions[bot] ddc0db1905 Lerna versionbump [ci skip]
- @navikt/ds-css@0.16.4
 - @navikt/ds-react@0.17.4
 - @navikt/ds-react-internal@0.12.4
2022-03-14 12:29:03 +00:00
211bd751cd [fix] Helptext uu fix (#1407)
* storybook no div in p fix

* move title inside svg

* extra helptext in story

* remove duplicate title

* remove aria-props and refactor

* remove unused style

* simplify styling some

* default help text content to body-short

* cleanup

* tabindex 0 på popover

* flyttet story

* fjernet tabindex

* Update @navikt/core/react/src/help-text/HelpText.tsx

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

Co-authored-by: Ken <ken.aleksander@gmail.com>
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2022-03-14 13:18:43 +01:00
github-actions[bot] 3e334453e3 Lerna versionbump [ci skip]
- @navikt/ds-css@0.16.3
2022-03-14 12:00:11 +00:00
Ken 70819de284 [FIX] Bedre width for search (#1455) 2022-03-14 12:51:53 +01:00
github-actions[bot] 4baebb4340 Lerna versionbump [ci skip]
- @navikt/ds-react@0.17.3
 - @navikt/ds-react-internal@0.12.3
2022-03-08 10:50:29 +00:00
Ken 7866333ae2 [FIX] Search: form -> div (#1454) 2022-03-08 11:40:25 +01:00
github-actions[bot] 2f13905d61 Lerna versionbump [ci skip]
- @navikt/ds-css@0.16.2
2022-03-07 15:23:32 +00:00
KenandAndreas Nordahl 0160383ff0 [FIX] Synket ConfirmationPanel med Figma (#1437)
Co-authored-by: Andreas Nordahl <andnorda@gmail.com>
2022-03-07 16:14:04 +01:00
github-actions[bot] dd1c128aac Lerna versionbump [ci skip]
- @navikt/ds-react@0.17.2
 - @navikt/ds-react-internal@0.12.2
2022-03-07 11:25:20 +00:00
Ken f1a6d768fc [FIX] onSubmit kjører onSearch callback i Search (#1452) 2022-03-07 12:16:56 +01:00
github-actions[bot] c47967589a Lerna versionbump [ci skip]
- @navikt/ds-react@0.17.1
 - @navikt/ds-react-internal@0.12.1
2022-03-07 10:13:39 +00:00
Ken 51654edbfb [FIX] parentElement prop på Modal (#1450) 2022-03-07 11:05:02 +01:00
Ken 5c3683a89e Search clearbutton label comment 2022-03-04 17:19:59 +01:00
github-actions[bot] 9d9bf898e9 Lerna versionbump [ci skip]
- @navikt/ds-css@0.16.1
2022-03-04 16:06:56 +00:00
Ken e8e530b764 [FIX] Search label layout hotfix 2022-03-04 16:57:09 +01:00
github-actions[bot] d2d08f58ce Lerna versionbump [ci skip]
- @navikt/ds-css@0.16.0
 - @navikt/ds-react@0.17.0
 - @navikt/ds-react-internal@0.12.0
2022-03-04 15:18:02 +00:00
KenandAndreas Nordahl f75abaff4b [FEATURE] Nytt søkefelt (#1382)
Co-authored-by: Andreas Nordahl <andnorda@gmail.com>
2022-03-04 16:08:26 +01:00
github-actions[bot] 7e8899df34 Lerna versionbump [ci skip]
- @navikt/ds-react@0.16.20
 - @navikt/ds-react-internal@0.11.20
2022-03-02 10:10:05 +00:00
Ken 3926b24350 [FIX] Modal aria-label (#1449) 2022-03-02 11:01:45 +01:00
github-actions[bot] f1c2c718e5 Lerna versionbump [ci skip]
- @navikt/ds-css@0.15.12
 - @navikt/ds-react@0.16.19
 - @navikt/ds-react-internal@0.11.19
2022-03-01 10:13:40 +00:00
Ken ddcc6b8bd7 [FIX] Ny versjon av Radix ToggleGroup (#1448) 2022-03-01 11:03:45 +01:00
github-actions[bot] 6f5fee9628 Lerna versionbump [ci skip]
- @navikt/ds-css@0.15.11
 - @navikt/ds-react@0.16.18
 - @navikt/ds-react-internal@0.11.18
2022-03-01 08:33:43 +00:00
ad191179ab [fix] Table sort (#1445)
* story baseline

* pagination first take

* sort first take

* fix sort logic

* sort styling

* fix widths

* scope row

* steps

* pagination styles

* sortable birth year

* simplify comparator

* async table story

* HeaderCell allows sorting

* remove redundant story. fix some styling

* fix chromatic

* refactor column header

* overflow auto

* refactor pagination

* fix pagination steps test

* separate pagination comp

* xsmall button in table

* fikset type-error i ds-react-internal

* Update @navikt/core/css/button.css

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Update @navikt/core/css/pagination.css

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Update @navikt/core/css/pagination.css

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Update @navikt/core/css/table.css

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Update @navikt/core/react/src/pagination/Pagination.tsx

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Update @navikt/core/react/src/pagination/Pagination.tsx

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Update @navikt/core/react/src/table/ColumnHeader.tsx

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* move storybook static conf

* sort arrow fix

* new, adjustable step logic

* fix ellipsis key bug

* pagination styling

* some a11y things

* lint

* some sizing

* next prev focus style

* add pagination component and styles

* pagination css custom props

* 1px #fff linje mellom bg og shadow

* fix page logic on story

* user defined sort change logic

* fix table padding

* fix focus bug

* fix tilbake title bug

* digihot table story

* no active style, selected blue

* yarn.lock

* remove duplicate pagination stories

* fix storybook

* bump

* table cell alignment

* Update @navikt/core/react/src/table/Table.tsx

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Update @navikt/core/react/src/table/stories/table-async.stories.tsx

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Update @navikt/core/css/table.css

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

Co-authored-by: Ken <ken.aleksander@gmail.com>
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2022-03-01 09:23:54 +01:00
github-actions[bot] 9c86becab0 Lerna versionbump [ci skip]
- @navikt/ds-react@0.16.17
 - @navikt/ds-react-internal@0.11.17
2022-02-28 13:12:38 +00:00
Ken 200d420723 [FIX] readme 2022-02-28 14:05:20 +01:00
Ken 44ab348ecf force v0.1.4 for ToggleGroup 2022-02-28 14:03:31 +01:00
54 changed files with 4938 additions and 3049 deletions

No files matched your search

+1
View File
@@ -33,4 +33,5 @@ module.exports = {
core: {
builder: "webpack5",
},
staticDirs: ["./public"],
};
+5
View File
@@ -1,5 +1,10 @@
import "@navikt/ds-css/index.css";
import "@navikt/ds-css-internal/index.css";
import { initialize, mswDecorator } from "msw-storybook-addon";
initialize();
export const decorators = [mswDecorator];
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
+338
View File
@@ -0,0 +1,338 @@
/* eslint-disable */
/* tslint:disable */
/**
* Mock Service Worker (0.36.5).
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/
const INTEGRITY_CHECKSUM = "02f4ad4a2797f85668baf196e553d929";
const bypassHeaderName = "x-msw-bypass";
const activeClientIds = new Set();
self.addEventListener("install", function () {
return self.skipWaiting();
});
self.addEventListener("activate", async function (event) {
return self.clients.claim();
});
self.addEventListener("message", async function (event) {
const clientId = event.source.id;
if (!clientId || !self.clients) {
return;
}
const client = await self.clients.get(clientId);
if (!client) {
return;
}
const allClients = await self.clients.matchAll();
switch (event.data) {
case "KEEPALIVE_REQUEST": {
sendToClient(client, {
type: "KEEPALIVE_RESPONSE",
});
break;
}
case "INTEGRITY_CHECK_REQUEST": {
sendToClient(client, {
type: "INTEGRITY_CHECK_RESPONSE",
payload: INTEGRITY_CHECKSUM,
});
break;
}
case "MOCK_ACTIVATE": {
activeClientIds.add(clientId);
sendToClient(client, {
type: "MOCKING_ENABLED",
payload: true,
});
break;
}
case "MOCK_DEACTIVATE": {
activeClientIds.delete(clientId);
break;
}
case "CLIENT_CLOSED": {
activeClientIds.delete(clientId);
const remainingClients = allClients.filter((client) => {
return client.id !== clientId;
});
// Unregister itself when there are no more clients
if (remainingClients.length === 0) {
self.registration.unregister();
}
break;
}
}
});
// Resolve the "main" client for the given event.
// Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase.
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId);
if (client.frameType === "top-level") {
return client;
}
const allClients = await self.clients.matchAll();
return allClients
.filter((client) => {
// Get only those clients that are currently visible.
return client.visibilityState === "visible";
})
.find((client) => {
// Find the client ID that's recorded in the
// set of clients that have registered the worker.
return activeClientIds.has(client.id);
});
}
async function handleRequest(event, requestId) {
const client = await resolveMainClient(event);
const response = await getResponse(event, client, requestId);
// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
(async function () {
const clonedResponse = response.clone();
sendToClient(client, {
type: "RESPONSE",
payload: {
requestId,
type: clonedResponse.type,
ok: clonedResponse.ok,
status: clonedResponse.status,
statusText: clonedResponse.statusText,
body:
clonedResponse.body === null ? null : await clonedResponse.text(),
headers: serializeHeaders(clonedResponse.headers),
redirected: clonedResponse.redirected,
},
});
})();
}
return response;
}
async function getResponse(event, client, requestId) {
const { request } = event;
const requestClone = request.clone();
const getOriginalResponse = () => fetch(requestClone);
// Bypass mocking when the request client is not active.
if (!client) {
return getOriginalResponse();
}
// Bypass initial page load requests (i.e. static assets).
// The absence of the immediate/parent client in the map of the active clients
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests.
if (!activeClientIds.has(client.id)) {
return await getOriginalResponse();
}
// Bypass requests with the explicit bypass header
if (requestClone.headers.get(bypassHeaderName) === "true") {
const cleanRequestHeaders = serializeHeaders(requestClone.headers);
// Remove the bypass header to comply with the CORS preflight check.
delete cleanRequestHeaders[bypassHeaderName];
const originalRequest = new Request(requestClone, {
headers: new Headers(cleanRequestHeaders),
});
return fetch(originalRequest);
}
// Send the request to the client-side MSW.
const reqHeaders = serializeHeaders(request.headers);
const body = await request.text();
const clientMessage = await sendToClient(client, {
type: "REQUEST",
payload: {
id: requestId,
url: request.url,
method: request.method,
headers: reqHeaders,
cache: request.cache,
mode: request.mode,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body,
bodyUsed: request.bodyUsed,
keepalive: request.keepalive,
},
});
switch (clientMessage.type) {
case "MOCK_SUCCESS": {
return delayPromise(
() => respondWithMock(clientMessage),
clientMessage.payload.delay
);
}
case "MOCK_NOT_FOUND": {
return getOriginalResponse();
}
case "NETWORK_ERROR": {
const { name, message } = clientMessage.payload;
const networkError = new Error(message);
networkError.name = name;
// Rejecting a request Promise emulates a network error.
throw networkError;
}
case "INTERNAL_ERROR": {
const parsedBody = JSON.parse(clientMessage.payload.body);
console.error(
`\
[MSW] Uncaught exception in the request handler for "%s %s":
${parsedBody.location}
This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error, as it indicates a mistake in your code. If you wish to mock an error response, please see this guide: https://mswjs.io/docs/recipes/mocking-error-responses\
`,
request.method,
request.url
);
return respondWithMock(clientMessage);
}
}
return getOriginalResponse();
}
self.addEventListener("fetch", function (event) {
const { request } = event;
const accept = request.headers.get("accept") || "";
// Bypass server-sent events.
if (accept.includes("text/event-stream")) {
return;
}
// Bypass navigation requests.
if (request.mode === "navigate") {
return;
}
// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (request.cache === "only-if-cached" && request.mode !== "same-origin") {
return;
}
// Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests
// after it's been deleted (still remains active until the next reload).
if (activeClientIds.size === 0) {
return;
}
const requestId = uuidv4();
return event.respondWith(
handleRequest(event, requestId).catch((error) => {
if (error.name === "NetworkError") {
console.warn(
'[MSW] Successfully emulated a network error for the "%s %s" request.',
request.method,
request.url
);
return;
}
// At this point, any exception indicates an issue with the original request/response.
console.error(
`\
[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`,
request.method,
request.url,
`${error.name}: ${error.message}`
);
})
);
});
function serializeHeaders(headers) {
const reqHeaders = {};
headers.forEach((value, name) => {
reqHeaders[name] = reqHeaders[name]
? [].concat(reqHeaders[name]).concat(value)
: value;
});
return reqHeaders;
}
function sendToClient(client, message) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel();
channel.port1.onmessage = (event) => {
if (event.data && event.data.error) {
return reject(event.data.error);
}
resolve(event.data);
};
client.postMessage(JSON.stringify(message), [channel.port2]);
});
}
function delayPromise(cb, duration) {
return new Promise((resolve) => {
setTimeout(() => resolve(cb()), duration);
});
}
function respondWithMock(clientMessage) {
return new Response(clientMessage.payload.body, {
...clientMessage.payload,
headers: clientMessage.payload.headers,
});
}
function uuidv4() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
const r = (Math.random() * 16) | 0;
const v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
+7 -1
View File
@@ -99,6 +99,11 @@
min-width: 32px;
}
.navds-button--xsmall {
padding: 0.125rem 0.375rem;
min-width: 24px;
}
.navds-button:focus {
outline: none;
box-shadow: var(--navds-shadow-focus);
@@ -115,7 +120,8 @@
font-size: 1.5rem;
}
.navds-button--small svg {
.navds-button--small svg,
.navds-button--xsmall svg {
font-size: 1.25rem;
}
+11 -4
View File
@@ -1,15 +1,21 @@
:root {
--navds-confirmation-panel-color-background: var(
--navds-semantic-color-feedback-warning-background
--navds-global-color-orange-50
);
--navds-confirmation-panel-color-background-checked: var(
--navds-semantic-color-feedback-success-background
--navds-global-color-green-50
);
--navds-confirmation-panel-color-border: var(
--navds-semantic-color-feedback-warning-border
);
--navds-confirmation-panel-color-border-checked: var(
--navds-semantic-color-feedback-success-background
--navds-semantic-color-feedback-success-border
);
--navds-confirmation-panel-color-border-error: var(
--navds-semantic-color-feedback-danger-background
);
--navds-confirmation-panel-color-background-error: var(
--navds-semantic-color-feedback-danger-background
--navds-global-color-red-50
);
--navds-confirmation-panel-color-checkbox-background-checked-hover: var(
--navds-global-color-green-700
@@ -34,6 +40,7 @@
gap: var(--navds-spacing-4);
padding: var(--navds-spacing-4);
border-radius: 4px;
border: 1px solid var(--navds-confirmation-panel-color-border);
background-color: var(--navds-confirmation-panel-color-background);
transition: background-color linear 100ms;
}
+1 -1
View File
@@ -11,4 +11,4 @@
@import "switch.css";
@import "text-field.css";
@import "textarea.css";
@import "search-field.css";
@import "search.css";
-59
View File
@@ -1,59 +0,0 @@
:root {
--navds-search-field-color-shadow-error: var(
--navds-semantic-color-interaction-danger
);
--navds-search-field-color-border-error: var(
--navds-semantic-color-interaction-danger
);
--navds-search-field-color-clearbutton-border: var(
--navds-semantic-color-border
);
}
.navds-search-field__input-wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.navds-form-field--small .navds-search-field__input {
padding: 0.15rem;
min-height: 32px;
}
.navds-search-field__input-wrapper > *:not(:first-child):not(:last-child) {
border-radius: 0;
}
.navds-search-field__input-wrapper > :first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.navds-search-field__input-wrapper > :last-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.navds-search-field__input-wrapper > :last-child:first-child {
border-radius: 4px;
}
.navds-search-field__input-wrapper :focus {
z-index: var(--navds-z-index-focus);
}
.navds-search-field__button,
.navds-search-field__clear-button {
flex-shrink: 0;
}
.navds-search-field--error
.navds-search-field__input:not(:hover):not(:focus):not(:disabled) {
box-shadow: 0 0 0 1px var(--navds-search-field-color-shadow-error) inset;
border-color: var(--navds-search-field-color-border-error);
}
.navds-search-field--disabled {
opacity: 0.3;
}
+125
View File
@@ -0,0 +1,125 @@
:root {
--navds-search-color-border: var(--navds-semantic-color-border);
--navds-search-color-border-hover: var(
--navds-semantic-color-interaction-primary
);
}
.navds-search {
display: flex;
flex-direction: column;
width: 100%;
}
.navds-search__wrapper-inner {
position: relative;
width: 100%;
}
.navds-search__wrapper {
display: inline-flex;
align-items: center;
}
.navds-search__input {
padding-right: var(--navds-spacing-12);
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.navds-form-field--small .navds-search__input {
min-height: 32px;
padding: 0.15rem;
padding-right: var(--navds-spacing-8);
}
.navds-search__button-clear {
position: absolute;
right: 0.75rem;
border-radius: var(--navds-border-radius-medium);
color: var(--navds-semantic-color-text);
height: 32px;
width: 32px;
top: 50%;
transform: translateY(-50%);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
background: none;
border: none;
font-size: 1rem;
padding: 0;
}
.navds-form-field--small .navds-search__button-clear {
right: 0.5rem;
height: 24px;
width: 24px;
}
.navds-search__button-clear:hover {
color: var(--navds-global-color-blue-500);
}
.navds-search__button-clear:focus {
box-shadow: var(--navds-shadow-focus);
outline: none;
}
.navds-search__button-search {
flex-shrink: 0;
min-width: 64px;
min-height: 32px;
border-radius: 0;
border-top-right-radius: var(--navds-border-radius-medium);
border-bottom-right-radius: var(--navds-border-radius-medium);
}
.navds-form-field--small .navds-search__button-search svg {
font-size: 1rem;
}
.navds-form-field--small .navds-search__button-search {
min-width: 40px;
}
.navds-search__button-search.navds-button--tertiary {
box-shadow: -1px 0 0 0 var(--navds-search-color-border) inset,
0 1px 0 0 var(--navds-search-color-border) inset,
0 -1px 0 0 var(--navds-search-color-border) inset;
}
.navds-search__button-search.navds-button--tertiary:hover {
box-shadow: -1px 0 0 0 var(--navds-search-color-border-hover) inset,
0 1px 0 0 var(--navds-search-color-border-hover) inset,
0 -1px 0 0 var(--navds-search-color-border-hover) inset,
-1px 0 0 0 var(--navds-search-color-border-hover);
z-index: 1;
}
.navds-search__wrapper-inner:focus-within
+ .navds-search__button-search.navds-button--tertiary:hover {
z-index: auto;
}
.navds-search__button-search.navds-button--tertiary:focus {
box-shadow: 0 0 0 1px var(--navds-search-color-border) inset,
var(--navds-shadow-focus);
}
.navds-search__button-search.navds-button--tertiary:focus:hover {
box-shadow: 0 0 0 1px var(--navds-search-color-border-hover) inset,
var(--navds-shadow-focus);
}
.navds-search__button-search.navds-button--tertiary:focus:active {
box-shadow: 0 0 0 1px white inset, var(--navds-shadow-focus);
}
/* Focus layering */
.navds-search__input:focus,
.navds-search__button-clear,
.navds-search__button-search:focus {
z-index: 1;
}
+2
View File
@@ -1,4 +1,5 @@
:root {
--navds-text-field-color-text: var(--navds-semantic-color-text);
--navds-text-field-color-background: var(
--navds-semantic-color-component-background-light
);
@@ -27,6 +28,7 @@
border: 1px solid var(--navds-text-field-color-border);
min-height: 48px;
width: 100%;
color: var(--navds-text-field-color-text);
}
.navds-form-field--small > .navds-text-field__input {
+23 -28
View File
@@ -1,22 +1,21 @@
:root {
--navds-help-text-color-button-background: transparent;
--navds-help-text-color-icon: var(--navds-semantic-color-interaction-primary);
--navds-help-text-color-icon-hover: var(
--navds-help-text-color: var(--navds-semantic-color-interaction-primary);
--navds-help-text-color-hover: var(
--navds-semantic-color-component-background-light
);
--navds-help-text-color-icon-background-hover: var(
--navds-semantic-color-interaction-primary
);
--navds-help-text-color-icon-shadow-hover: var(
--navds-semantic-color-interaction-primary
);
--navds-help-text-color-icon-focus: var(
--navds-help-text-color-focus: var(
--navds-semantic-color-component-background-light
);
--navds-help-text-color-icon-background-focus: var(
--navds-help-text-color-background-hover: var(
--navds-semantic-color-interaction-primary
);
--navds-help-text-color-icon-shadow-focus: var(--navds-semantic-color-focus);
--navds-help-text-color-background-focus: var(
--navds-semantic-color-interaction-primary
);
--navds-help-text-color-shadow-hover: var(
--navds-semantic-color-interaction-primary
);
--navds-help-text-color-shadow-focus: var(--navds-semantic-color-focus);
--navds-help-text-color-popover-background: var(
--navds-semantic-color-feedback-info-background
);
@@ -27,34 +26,30 @@
padding: 0;
border: 0;
cursor: pointer;
background-color: var(--navds-help-text-color-button-background);
background-color: transparent;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
color: var(--navds-help-text-color);
font-size: 1.5rem;
}
.navds-help-text__button:hover {
color: var(--navds-help-text-color-hover);
background: var(--navds-help-text-color-background-hover);
box-shadow: 0 0 0 2px var(--navds-help-text-color-shadow-hover);
}
.navds-help-text__button:focus {
outline: none;
color: var(--navds-help-text-color-focus);
background: var(--navds-help-text-color-background-focus);
box-shadow: 0 0 0 3px var(--navds-help-text-color-shadow-focus);
}
.navds-help-text__icon {
border-radius: 50%;
font-size: 1.5rem;
color: var(--navds-help-text-color-icon);
}
.navds-help-text__button:hover > .navds-help-text__icon {
color: var(--navds-help-text-color-icon-hover);
background: var(--navds-help-text-color-icon-background-hover);
box-shadow: 0 0 0 2px var(--navds-help-text-color-icon-shadow-hover);
}
.navds-help-text__button:focus > .navds-help-text__icon {
outline: none;
color: var(--navds-help-text-color-icon-focus);
background: var(--navds-help-text-color-icon-background-focus);
box-shadow: 0 0 0 3px var(--navds-help-text-color-icon-shadow-focus);
}
.navds-help-text__popover > .navds-popover__arrow::before {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "0.15.10",
"version": "0.16.4",
"private": false,
"description": "Css for NAV Designsystem components",
"author": "NAV Designsystem team",
+64 -3
View File
@@ -16,8 +16,8 @@
}
.navds-table {
border-collapse: collapse;
width: 100%;
border-collapse: collapse;
display: table;
}
@@ -54,11 +54,21 @@
.navds-table__header-cell,
.navds-table__data-cell {
display: table-cell;
padding: var(--navds-spacing-4);
padding: var(--navds-spacing-4) var(--navds-spacing-3);
border-bottom: 1px solid var(--navds-table-cell-color-border);
text-align: left;
}
.navds-table__header-cell--align-right,
.navds-table__data-cell--align-right {
text-align: right;
}
.navds-table__header-cell--align-center,
.navds-table__data-cell--align-center {
text-align: center;
}
.navds-table__body .navds-table__row:hover .navds-table__header-cell,
.navds-table__body .navds-table__row:hover .navds-table__data-cell {
border-color: var(--navds-table-cell-color-border-hover);
@@ -66,7 +76,7 @@
.navds-table--small .navds-table__header-cell,
.navds-table--small .navds-table__data-cell {
padding: var(--navds-spacing-2);
padding: var(--navds-spacing-2) var(--navds-spacing-3);
}
.navds-table .navds-checkbox .navds-checkbox__input {
@@ -80,3 +90,54 @@
.navds-table .navds-checkbox .navds-checkbox__label {
padding: 0;
}
.navds-table__header-cell[aria-sort] {
padding: 0;
}
.navds-table__sort-button {
appearance: none;
background: none;
color: var(--navds-semantic-color-link);
border: none;
cursor: pointer;
margin: 0;
padding: var(--navds-spacing-4) var(--navds-spacing-3);
width: 100%;
display: flex;
gap: 0.5rem;
align-items: center;
line-height: inherit;
font-weight: inherit;
}
.navds-table--small .navds-table__sort-button {
padding: var(--navds-spacing-2) var(--navds-spacing-3);
}
.navds-table__sort-button:hover {
background-color: var(--navds-semantic-color-canvas-background);
}
.navds-table__sort-button:focus {
outline: none;
box-shadow: inset 0 0 0 2px var(--navds-semantic-color-focus);
}
.navds-table__header-cell[aria-sort="ascending"] .navds-table__sort-button,
.navds-table__header-cell[aria-sort="descending"] .navds-table__sort-button {
background-color: var(--navds-global-color-blue-50);
}
.navds-table__header-cell--align-right .navds-table__sort-button {
justify-content: end;
}
.navds-table__header-cell--align-center .navds-table__sort-button {
justify-content: center;
}
.navds-table__sort-button svg {
font-size: 1rem;
flex-shrink: 0;
}
+2 -1
View File
@@ -69,7 +69,8 @@
flex-shrink: 0;
}
.navds-toggle-group__button[aria-pressed="true"] {
.navds-toggle-group__button[aria-pressed="true"],
.navds-toggle-group__button[aria-checked="true"] {
background-color: var(--navds-toggle-group-color-background-pressed);
color: var(--navds-toggle-group-color-text-pressed);
}
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "0.16.16",
"version": "0.17.4",
"private": false,
"description": "NAV designsystem react components",
"author": "NAV Designsystem team",
@@ -37,7 +37,7 @@
"@material-ui/core": "^4.12.3",
"@navikt/ds-icons": "^0.8.4",
"@popperjs/core": "^2.10.1",
"@radix-ui/react-toggle-group": "^0.1.4",
"@radix-ui/react-toggle-group": "0.1.5",
"classnames": "^2.2.6",
"react-collapse": "^5.1.0",
"react-merge-refs": "^1.1.0",
@@ -59,6 +59,7 @@
"react-router-dom": "^6.1.0",
"rimraf": "3.0.2",
"styled-components": "^5.3.1",
"swr": "^1.1.2",
"ts-jest": "^27.0.5"
},
"peerDependencies": {
+7 -3
View File
@@ -19,7 +19,7 @@ export interface ButtonProps
* Changes padding, height and font-size
* @default "medium"
*/
size?: "medium" | "small";
size?: "medium" | "small" | "xsmall";
/**
* Prevent the user from interacting with the button: it cannot be pressed or focused.
* @note Avoid using if possible for accessibility purposes
@@ -43,6 +43,7 @@ const Button: OverridableComponent<ButtonProps, HTMLButtonElement> = forwardRef(
size = "medium",
loading = false,
disabled,
style,
...rest
},
ref
@@ -76,13 +77,16 @@ const Button: OverridableComponent<ButtonProps, HTMLButtonElement> = forwardRef(
"navds-button--loading": widthOverride,
}
)}
style={{ width: widthOverride }}
style={{
...style,
width: widthOverride,
}}
disabled={disabled ?? widthOverride ? true : undefined}
>
<BodyShort
as="span"
className="navds-button__inner"
size={size}
size={size === "medium" ? "medium" : "small"}
aria-live="polite"
>
{widthOverride ? <Loader size={size} /> : children}
@@ -81,6 +81,14 @@ export const All = () => {
</Button>
))}
</Section>
<h2>Xsmall</h2>
<Section>
{variants.map((variant) => (
<Button key={variant} variant={variant} size="xsmall">
{varSwitch[variant]}
</Button>
))}
</Section>
<h2>Button w/icon</h2>
<Section>
{variants.map((variant) => (
+1 -1
View File
@@ -10,7 +10,7 @@ export {
FieldsetProps,
} from "./Fieldset";
export { Radio, RadioGroup, RadioGroupProps, RadioProps } from "./radio";
export { SearchField, SearchFieldProps } from "./search-field";
export { Search, SearchProps, SearchClearEvent } from "./search";
export { default as Select, SelectProps } from "./Select";
export { default as Switch, SwitchProps } from "./Switch";
export { ErrorSummary, ErrorSummaryProps } from "./error-summary";
@@ -1,132 +0,0 @@
import cl from "classnames";
import React, { forwardRef } from "react";
import { BodyShort, Label, omit } from "../..";
import ErrorMessage from "../ErrorMessage";
import { FormFieldProps, useFormField } from "../useFormField";
import SearchFieldButton, { SearchFieldButtonType } from "./SearchFieldButton";
import SearchFieldClearButton, {
SearchFieldClearButtonType,
} from "./SearchFieldClearButton";
import SearchFieldInput, { SearchFieldInputType } from "./SearchFieldInput";
export interface SearchFieldContextProps {
inputProps: {
id: string;
"aria-invalid": boolean;
"aria-describedby"?: string;
disabled?: boolean;
};
size?: "medium" | "small";
}
export const SearchFieldContext = React.createContext<SearchFieldContextProps | null>(
null
);
export interface SearchFieldProps
extends FormFieldProps,
React.HTMLAttributes<HTMLDivElement> {
/**
* SearchFieldInput & SearchFieldButton
*/
children: React.ReactNode;
/**
* If enabled shows the label and description for screenreaders only
*/
hideLabel?: boolean;
/**
* SearchField label
*/
label: React.ReactNode;
}
interface SearchFieldComponent
extends React.ForwardRefExoticComponent<
SearchFieldProps & React.RefAttributes<HTMLDivElement>
> {
Button: SearchFieldButtonType;
Clear: SearchFieldClearButtonType;
Input: SearchFieldInputType;
}
const SearchField = forwardRef<HTMLDivElement, SearchFieldProps>(
(props, ref) => {
const {
inputProps,
errorId,
showErrorMsg,
hasError,
size,
inputDescriptionId,
} = useFormField(props, "searchfield");
const {
className,
hideLabel,
children,
label,
description,
error,
...rest
} = props;
return (
<div
ref={ref}
{...omit(rest, ["id", "error", "errorId", "size", "disabled"])}
className={cl(
className,
"navds-form-field",
`navds-form-field--${size ?? "medium"}`,
"navds-search-field",
{
"navds-search-field--error": hasError,
"navds-search-field--disabled": !!inputProps.disabled,
}
)}
>
<Label
htmlFor={inputProps.id}
size={size}
as="label"
className={cl("navds-text-field__label", {
"navds-sr-only": hideLabel,
})}
>
{label}
</Label>
{!!description && (
<BodyShort
as="div"
className={cl("navds-text-field__description", {
"navds-sr-only": hideLabel,
})}
id={inputDescriptionId}
size={size}
>
{description}
</BodyShort>
)}
<div className="navds-search-field__input-wrapper">
<SearchFieldContext.Provider
value={{
inputProps,
size,
}}
>
{children}
</SearchFieldContext.Provider>
</div>
<div id={errorId} aria-relevant="additions removals" aria-live="polite">
{showErrorMsg && <ErrorMessage size={size}>{error}</ErrorMessage>}
</div>
</div>
);
}
) as SearchFieldComponent;
SearchField.Button = SearchFieldButton;
SearchField.Clear = SearchFieldClearButton;
SearchField.Input = SearchFieldInput;
export default SearchField;
@@ -1,47 +0,0 @@
import React, { forwardRef, useContext } from "react";
import cl from "classnames";
import { SearchFieldContext } from "./SearchField";
import { Button, ButtonProps } from "../..";
export interface SearchFieldButtonProps extends Omit<ButtonProps, "size"> {
/**
* Button text
* @example <Search /> <span>Søk</span>
*/
children: React.ReactNode;
/**
* Changes design and interactions
* @default "primary"
*/
variant?: "primary" | "secondary";
}
export type SearchFieldButtonType = React.ForwardRefExoticComponent<
SearchFieldButtonProps & React.RefAttributes<HTMLButtonElement>
>;
const SearchFieldButton: SearchFieldButtonType = forwardRef(
({ className, variant = "primary", disabled, ...rest }, ref) => {
const searchField = useContext(SearchFieldContext);
if (searchField === null) {
console.warn("SearchFieldButton has to be wrapped in <SearchField />");
return null;
}
const { size, inputProps } = searchField;
return (
<Button
ref={ref}
className={cl(className, "navds-search-field__button")}
{...rest}
size={size}
variant={variant}
disabled={disabled ?? inputProps?.disabled}
/>
);
}
);
export default SearchFieldButton;
@@ -1,49 +0,0 @@
import React, { forwardRef, useContext } from "react";
import cl from "classnames";
import { SearchFieldContext } from "./SearchField";
import { Button, ButtonProps } from "../..";
export interface SearchFieldClearButtonProps extends Omit<ButtonProps, "size"> {
/**
* Button text
* @example <Close /> <span>Søk</span>
*/
children: React.ReactNode;
/**
* Changes design and interactions
* @default "secondary"
*/
variant?: "primary" | "secondary";
}
export type SearchFieldClearButtonType = React.ForwardRefExoticComponent<
SearchFieldClearButtonProps & React.RefAttributes<HTMLButtonElement>
>;
const SearchFieldClearButton: SearchFieldClearButtonType = forwardRef(
({ className, variant = "secondary", disabled, ...rest }, ref) => {
const searchField = useContext(SearchFieldContext);
if (searchField === null) {
console.warn(
"SearchFieldClearButton has to be wrapped in <SearchField />"
);
return null;
}
const { size, inputProps } = searchField;
return (
<Button
{...rest}
ref={ref}
className={cl(className, "navds-search-field__clear-button")}
size={size}
variant={variant}
disabled={disabled ?? inputProps?.disabled}
/>
);
}
);
export default SearchFieldClearButton;
@@ -1,42 +0,0 @@
import React, { forwardRef, InputHTMLAttributes, useContext } from "react";
import cl from "classnames";
import { SearchFieldContext } from "./SearchField";
export interface SearchFieldInputProps
extends InputHTMLAttributes<HTMLInputElement> {}
export type SearchFieldInputType = React.ForwardRefExoticComponent<
SearchFieldInputProps & React.RefAttributes<HTMLInputElement>
>;
const SearchFieldInput: SearchFieldInputType = forwardRef(
({ className, ...rest }, ref) => {
const searchField = useContext(SearchFieldContext);
if (searchField === null) {
console.warn("SearchFieldInput has to be wrapped in <SearchField />");
return null;
}
const { size, inputProps } = searchField;
return (
<input
{...rest}
{...inputProps}
type="search"
role="searchbox"
ref={ref}
className={cl(
className,
"navds-search-field__input",
"navds-text-field__input",
"navds-body-short",
`navds-body-${size ?? "medium"}`
)}
/>
);
}
);
export default SearchFieldInput;
@@ -1,2 +0,0 @@
export { default as SearchField, SearchFieldProps } from "./SearchField";
export * from "./SearchField";
@@ -1,25 +0,0 @@
import React, { useState } from "react";
import { Close, Search } from "@navikt/ds-icons";
import { SearchField } from "../index";
export const Example = ({ size = "medium" }: { size: "medium" | "small" }) => {
const [value, setValue] = useState("");
return (
<SearchField size={size} label="Skriv i søkefeltet for å vise clearbutton">
<SearchField.Input
value={value}
onChange={(e) => setValue(e.target.value)}
/>
{!!value && (
<SearchField.Clear onClick={() => setValue("")}>
<Close />
Tøm
</SearchField.Clear>
)}
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
);
};
@@ -1,156 +0,0 @@
import { Meta, Canvas } from "@storybook/addon-docs";
import { SearchField } from "../index";
import { Example } from "./search-field-example.tsx";
import { Search } from "@navikt/ds-icons";
<Meta title="ds-react/form/search-field/intro" />
# Hvordan ta i bruk SearchField
## Viktig
Komponenten er ikke testet eller gått gjennom et design-review, så anbefalses og vente med å ta den i bruk.
## Bruk
```jsx
<SearchField label="Mollit eiusmod" description="Ea cupidatat eu sunt commodo">
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
```
<Canvas>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
</Canvas>
## hideLabel
`hideLabel` proppen kan brukes for å vise label + description for bare skjermlesere.
Vi har satt `label` som required siden man vil bruke den i sammenheng med denne proppen for bedre UU.
```jsx
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
```
<Canvas>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
</Canvas>
## Sizing
```jsx
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
size="small"
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
```
<Canvas>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
size="small"
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
</Canvas>
## Errors
```jsx
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
error="Error message"
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
```
<Canvas>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
error="Error message"
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
</Canvas>
## ClearButton
Man kan bruke `SearchField.Clear` for å håndtere clearing av value i `<input />`. For å bruke denne
kreves det da at `SearchField.Input` er controlled slik at man styrer state selv slik som eksemplet under.
Hvis man bare ønsker at den bare skal vises når bruker har begynt å skrive, anbefaler vi bare å rendre
den når inputvalue ikke er empty.
```jsx
<SearchField label="Mollit eiusmod" description="Ea cupidatat eu sunt commodo">
<SearchField.Input value={value} onChange={(e) => setValue(e.target.value)} />
{!!value && (
<SearchField.Clear onClick={() => setValue("")}>
<Close /> Tøm
</SearchField.Clear>
)}
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
```
<Canvas>
<div>
<Example />
<Example size="small" />
</div>
</Canvas>
@@ -1,199 +0,0 @@
import React, { useState } from "react";
import { Meta } from "@storybook/react/types-6-0";
import { Close, Search } from "@navikt/ds-icons";
import { SearchField } from "../index";
import { Fieldset } from "../..";
export default {
title: "ds-react/form/search-field",
component: SearchField,
} as Meta;
export const All = () => {
const [show, setShow] = useState(false);
return (
<>
<h1>SearchField</h1>
<SearchField
label="Mollit eiusmod"
description={<div>Ea cupidatat eu sunt commodo</div>}
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
>
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
<SearchField.Input />
</SearchField>
<h1>SearchField w clearsearch</h1>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
>
<SearchField.Input />
<SearchField.Clear>
<Close />
</SearchField.Clear>
<SearchField.Button onClick={() => setShow(!show)}>
<Search /> Søk
</SearchField.Button>
</SearchField>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
>
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
<SearchField.Clear>
<Close /> Tøm
</SearchField.Clear>
<SearchField.Input />
</SearchField>
<h2>Hidelabel</h2>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
>
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
<SearchField.Input />
</SearchField>
<h2>SearchField small</h2>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
size="small"
>
<SearchField.Button>
<Search />
<span className="navds-sr-only">Søk</span>
</SearchField.Button>
<SearchField.Input />
</SearchField>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
size="small"
>
<SearchField.Input />
<SearchField.Button>
<Search />
<span className="navds-sr-only">Søk</span>
</SearchField.Button>
</SearchField>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
size="small"
>
<SearchField.Button>
<Search />
<span className="navds-sr-only">Søk</span>
</SearchField.Button>
<SearchField.Clear>
<Close /> Tøm
</SearchField.Clear>
<SearchField.Input />
</SearchField>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
size="small"
>
<SearchField.Input />
<SearchField.Clear>
<Close /> Tøm
</SearchField.Clear>
<SearchField.Button>
<Search />
<span className="navds-sr-only">Søk</span>
</SearchField.Button>
</SearchField>
<h2>SearchField w error</h2>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
error="Errormsg"
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
error="Errormsg"
>
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
<SearchField.Input />
</SearchField>
<h2>SearchField in Fieldset</h2>
<Fieldset legend="Filter" error="Fieldset-error-msg">
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
error="Errormsg"
hideLabel
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
>
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
<SearchField.Input />
</SearchField>
</Fieldset>
<h2>Disabled </h2>
<SearchField
disabled
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
<Fieldset legend="Filter" disabled>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
error="Errormsg"
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
</Fieldset>
</>
);
};
@@ -0,0 +1,233 @@
import { Close } from "@navikt/ds-icons";
import cl from "classnames";
import React, {
forwardRef,
InputHTMLAttributes,
useCallback,
useEffect,
useRef,
useState,
} from "react";
import mergeRefs from "react-merge-refs";
import { BodyShort, Label, omit, useEventListener } from "../..";
import { FormFieldProps } from "../useFormField";
import SearchButton, { SearchButtonType } from "./SearchButton";
import { useSearch } from "./useSearch";
export type SearchClearEvent =
| {
trigger: "Click";
event: React.MouseEvent<HTMLButtonElement, MouseEvent>;
}
| { trigger: "Escape"; event: React.KeyboardEvent<HTMLDivElement> };
export interface SearchProps
extends Omit<FormFieldProps, "error" | "errorId">,
Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "onChange"> {
children?: React.ReactNode;
/**
* Search label
* @info Will be hidden by default, is required for accessibility reasons.
*/
label: React.ReactNode;
/**
* Shows label and description for screenreaders-only
* @default true
*/
hideLabel?: boolean;
/**
* Callback for value-change in input
*/
onChange?: (value: string) => void;
/**
* Callback for <Search.Button/> click or onSubmit in form
*/
onSearch?: (value: string | number | readonly string[]) => void;
/**
* Callback for click on clear-button or Escape keydown
*/
onClear?: (e: SearchClearEvent) => void;
/**
* aria-label on clear button
* @default "Tøm"
*/
clearButtonLabel?: string;
/**
* If false, removes clear-button option from input.
* @default true
*/
clearButton?: boolean;
/**
* Changes button-variant
* @default "tertiary"
*/
variant?: "tertiary" | "primary";
}
interface SearchComponent
extends React.ForwardRefExoticComponent<
SearchProps & React.RefAttributes<HTMLDivElement>
> {
Button: SearchButtonType;
}
export interface SearchContextProps {
disabled?: boolean;
size: "medium" | "small";
variant?: "tertiary" | "primary";
onSearch?: () => void;
}
export const SearchContext = React.createContext<SearchContextProps | null>(
null
);
const Search = forwardRef<HTMLInputElement, SearchProps>((props, ref) => {
const { inputProps, size = "medium", inputDescriptionId } = useSearch(
props,
"searchfield"
);
const {
className,
hideLabel = true,
label,
description,
value,
clearButtonLabel,
onClear,
clearButton = true,
children,
onSearch,
variant = "tertiary",
...rest
} = props;
const searchRef = useRef<HTMLInputElement | null>(null);
const mergedRef = mergeRefs([searchRef, ref]);
const [wrapperRef, setWrapperRef] = useState<HTMLDivElement | null>(null);
const [controlledValue, setControlledValue] = useState(value ?? "");
const handleChange = useCallback(
(v: string) => {
searchRef.current && value === undefined && setControlledValue(v);
props?.onChange?.(v);
},
[props, value]
);
const handleClear = useCallback(
(event: SearchClearEvent) => {
onClear?.(event);
handleChange("");
if (searchRef.current && value === undefined) {
searchRef.current.value = "";
}
searchRef.current && searchRef.current?.focus?.();
},
[handleChange, onClear, value]
);
useEventListener(
"keydown",
useCallback(
(e) => {
if (e.key === "Escape") {
e.preventDefault();
handleClear({ trigger: "Escape", event: e });
}
},
[handleClear]
),
wrapperRef
);
useEffect(() => {
value !== undefined && setControlledValue(value);
}, [value]);
return (
<div
ref={setWrapperRef}
className={cl(
className,
"navds-form-field",
`navds-form-field--${size}`,
"navds-search",
{
"navds-search--disabled": !!inputProps.disabled,
}
)}
>
<Label
htmlFor={inputProps.id}
size={size}
as="label"
className={cl("navds-text-field__label", {
"navds-sr-only": hideLabel,
})}
>
{label}
</Label>
{!!description && (
<BodyShort
as="div"
className={cl("navds-text-field__description", {
"navds-sr-only": hideLabel,
})}
id={inputDescriptionId}
size={size}
>
{description}
</BodyShort>
)}
<div className="navds-search__wrapper">
<div className="navds-search__wrapper-inner">
<input
ref={mergedRef}
{...omit(rest, ["size"])}
{...inputProps}
{...(props.value !== undefined && { value: props.value })}
onChange={(e) => handleChange(e.target.value)}
type="search"
role="searchbox"
className={cl(
className,
"navds-search__input",
"navds-text-field__input",
"navds-body-short",
`navds-body-${size ?? "medium"}`
)}
/>
{controlledValue && clearButton && (
<button
type="button"
onClick={(e) => handleClear({ trigger: "Click", event: e })}
className="navds-search__button-clear"
>
<span className="navds-sr-only">
{clearButtonLabel ? clearButtonLabel : "Tøm"}
</span>
<Close aria-hidden />
</button>
)}
</div>
<SearchContext.Provider
value={{
size,
disabled: inputProps.disabled,
variant,
onSearch: () => onSearch?.(controlledValue),
}}
>
{children ? children : <SearchButton />}
</SearchContext.Provider>
</div>
</div>
);
}) as SearchComponent;
Search.Button = SearchButton;
export default Search;
@@ -0,0 +1,51 @@
import { Search } from "@navikt/ds-icons";
import cl from "classnames";
import React, { forwardRef, useContext } from "react";
import { Button, ButtonProps } from "../..";
import { SearchContext } from "./Search";
export interface SearchButtonProps
extends Omit<ButtonProps, "size" | "children" | "variant"> {
/**
* Text set before <Search/> icon
*/
children?: React.ReactNode;
}
export type SearchButtonType = React.ForwardRefExoticComponent<
SearchButtonProps & React.RefAttributes<HTMLButtonElement>
>;
const SearchButton: SearchButtonType = forwardRef(
({ className, children, disabled, onClick, ...rest }, ref) => {
const context = useContext(SearchContext);
if (context === null) {
console.warn("<Search.Button> has to be wrapped in <Search />");
return null;
}
const { size, variant, onSearch } = context;
return (
<Button
type="submit"
{...rest}
ref={ref}
size={size}
variant={variant}
className={cl("navds-search__button-search", className)}
disabled={context?.disabled ?? disabled}
onClick={(e) => {
onSearch?.();
onClick?.(e);
}}
>
<Search aria-hidden />
{children ? children : <span className="navds-sr-only">Søk</span>}
</Button>
);
}
);
export default SearchButton;
@@ -0,0 +1 @@
export { default as Search, SearchProps, SearchClearEvent } from "./Search";
@@ -0,0 +1,94 @@
import { Meta } from "@storybook/react/types-6-0";
import React, { useState } from "react";
import { Search } from "../index";
export default {
title: "ds-react/form/search",
component: Search,
} as Meta;
export const All = () => {
const [value, setValue] = useState("");
return (
<div
style={{
display: "flex",
alignItems: "center",
flexDirection: "column",
maxWidth: 400,
}}
>
<h1>Search</h1>
<Search label="Søk alle sider om X og Y" onSearch={console.log}></Search>
<h2>Primary search</h2>
<Search
label="Søk alle sider om X og Y"
onSearch={console.log}
variant="primary"
></Search>
<h2>Search small</h2>
<Search
label="Søk alle sider om X og Y"
description="Beskrivelse av søket"
size="small"
>
<Search.Button />
</Search>
<br />
<Search
label="Søk alle sider om X og Y"
description="Beskrivelse av søket"
size="small"
hideLabel
variant="primary"
>
<Search.Button />
</Search>
<h2>Med knappe-tekst</h2>
<Search label="Søk alle sider om X og Y" hideLabel={false}>
<Search.Button>Søk</Search.Button>
</Search>
<Search
label="Søk alle sider om X og Y"
hideLabel={false}
variant="primary"
>
<Search.Button>Søk</Search.Button>
</Search>
<h2>Hidelabel false</h2>
<Search label="Søk alle sider om X og Y" hideLabel={false}>
<Search.Button />
</Search>
<h2>Controlled state </h2>
<Search
value={value}
label="Søk alle sider om X og Y"
description="Beskrivelse av søket"
onChange={(e) => setValue(e)}
onClear={() => setValue("")}
>
<Search.Button />
</Search>
<h2>No clear button</h2>
<Search
hideLabel
label="Søk alle sider om X og Y"
description="Beskrivelse av søket"
clearButton={false}
>
<Search.Button />
</Search>
</div>
);
};
export const UUDemo = () => {
return (
<Search label="Søk på nav.no" onSearch={console.log} placeholder="Søk">
<Search.Button />
</Search>
);
};
@@ -0,0 +1,31 @@
import cl from "classnames";
import { useContext } from "react";
import { useId } from "../../index";
import { FieldsetContext } from "../index";
import { FormFieldProps } from "../useFormField";
export const useSearch = (props: FormFieldProps, prefix: string) => {
const { size } = props;
const fieldset = useContext(FieldsetContext);
const genId = useId();
const id = props.id ?? `${prefix}-${genId}`;
const inputDescriptionId = `${prefix}-description-${genId}`;
const disabled = fieldset?.disabled || props.disabled;
return {
inputDescriptionId,
size: size ?? fieldset?.size ?? "medium",
inputProps: {
id,
"aria-describedby":
cl(props["aria-describedby"], {
[inputDescriptionId]: !!props?.description,
}) || undefined,
disabled,
},
};
};
+11 -22
View File
@@ -1,6 +1,6 @@
import { Helptext as HelpTextIcon } from "@navikt/ds-icons";
import cl from "classnames";
import React, { forwardRef, useEffect, useRef, useState } from "react";
import React, { forwardRef, useRef, useState } from "react";
import mergeRefs from "react-merge-refs";
import { Popover, PopoverProps } from "..";
@@ -21,53 +21,42 @@ const HelpText = forwardRef<HTMLButtonElement, HelpTextProps>(
placement = "top",
strategy = "absolute",
title = "hjelp",
onClick,
...rest
},
ref
) => {
const buttonRef = useRef<HTMLButtonElement | null>(null);
const mergedRef = mergeRefs([buttonRef, ref]);
const [popoverRef, setPopoverRef] = useState<HTMLDivElement | null>(null);
const wrapperRef = useRef<HTMLDivElement | null>(null);
const [open, setOpen] = useState(false);
useEffect(() => {
open && popoverRef?.focus?.();
}, [open, popoverRef]);
const handleClick = (
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
setOpen((x) => !x);
rest.onClick && rest.onClick(e);
};
return (
<div className="navds-help-text" ref={wrapperRef}>
<div className="navds-help-text">
<button
{...rest}
ref={mergedRef}
onClick={(e) => handleClick(e)}
onClick={(e) => {
setOpen((x) => !x);
onClick?.(e);
}}
className={cl(className, "navds-help-text__button")}
type="button"
aria-expanded={open}
aria-haspopup="dialog"
title={title}
>
<HelpTextIcon className="navds-help-text__icon" />
<span className="navds-sr-only">{title}</span>
<HelpTextIcon className="navds-help-text__icon" aria-hidden/>
</button>
<Popover
ref={setPopoverRef}
onClose={() => setOpen(false)}
className="navds-help-text__popover"
open={open}
role="tooltip"
anchorEl={buttonRef.current}
placement={placement}
strategy={strategy}
>
<Popover.Content>{children}</Popover.Content>
<Popover.Content className="navds-body-short">
{children}
</Popover.Content>
</Popover>
</div>
);
@@ -0,0 +1,28 @@
import React from "react";
import { TextField, HelpText } from "..";
export default {
title: "ds-react/help-text",
component: HelpText,
};
export const All = () => {
return (
<>
<h1>HelpText</h1>
<HelpText title="show tooltip">
Id ullamco excepteur elit fugiat labore.
</HelpText>
<TextField
label={
<div style={{ display: "flex", gap: 8 }}>
Text field label
<HelpText title="show tooltip">
Id ullamco excepteur elit fugiat labore.
</HelpText>
</div>
}
></TextField>
</>
);
};
@@ -1,35 +0,0 @@
import { Meta, Canvas } from "@storybook/addon-docs";
import { HelpText } from "../../index";
import { Success } from "@navikt/ds-react";
<Meta title="ds-react/help-text/intro" />
# Hvordan ta i bruk HelpText
```jsx
<HelpText>Exercitation esse minim esse mollit ex magna.</HelpText>
```
<Canvas>
<div style={{ padding: "5rem" }}>
<HelpText>Exercitation esse minim esse mollit ex magna.</HelpText>
</div>
</Canvas>
# Egen title
Husk å endre på "title" slik at skjermlesere får tydeligere informasjon
```jsx
<HelpText title="vis hjelpetekst angående søknad">
Exercitation esse minim esse mollit ex magna.
</HelpText>
```
<Canvas>
<div style={{ padding: "5rem" }}>
<HelpText title="vis hjelpetekst angående søknad">
Exercitation esse minim esse mollit ex magna.
</HelpText>
</div>
</Canvas>
@@ -1,32 +0,0 @@
import React from "react";
import { BodyLong } from "../../typography";
import { HelpText } from "../index";
export default {
title: "ds-react/help-text",
component: HelpText,
};
export const All = () => {
return (
<>
<h1>HelpText</h1>
<BodyLong style={{ display: "flex" }}>
Quis tempor incididunt sint sit veniam ad dolore cillum ut.
<HelpText title="show tooltip">
Id ullamco excepteur elit fugiat labore.
</HelpText>
</BodyLong>
<BodyLong>
Incididunt pariatur laborum dolor sint qui eiusmod exercitation non
cupidatat amet.
</BodyLong>
<h2>HelpText defaultOpen</h2>
<HelpText title="show tooltip">
Id ullamco excepteur elit fugiat labore.
</HelpText>
</>
);
};
+9 -9
View File
@@ -19,15 +19,15 @@ interface MenuComponent
const Menu = forwardRef<HTMLElement, MenuProps>(
({ children, className, ...rest }, ref) => (
<nav
{...rest}
role="navigation"
ref={ref}
className={cl("navds-menu", className)}
>
<MenuItems>{children}</MenuItems>
</nav>
)
<nav
{...rest}
role="navigation"
ref={ref}
className={cl("navds-menu", className)}
>
<MenuItems>{children}</MenuItems>
</nav>
)
) as MenuComponent;
Menu.Collapse = Collapse;
+11
View File
@@ -33,9 +33,18 @@ export interface ModalProps {
* @default true
*/
closeButton?: boolean;
/**
* Callback for getting parent element modal will attach to
*/
parentSelector?(): HTMLElement;
"aria-labelledby"?: string;
"aria-describedby"?: string;
"aria-modal"?: boolean;
/**
* Sets aria-label on modal
* @warning This should be set if not using 'aria-labelledby' or 'aria-describedby'
*/
"aria-label"?: string;
}
interface ModalComponent
@@ -62,6 +71,7 @@ const Modal = forwardRef<ReactModal, ModalProps>(
"aria-describedby": ariaDescribedBy,
"aria-labelledby": ariaLabelledBy,
"aria-modal": ariaModal,
"aria-label": contentLabel,
...rest
},
ref
@@ -92,6 +102,7 @@ const Modal = forwardRef<ReactModal, ModalProps>(
labelledby: ariaLabelledBy,
modal: ariaModal,
}}
contentLabel={contentLabel}
>
{children}
{closeButton && (
@@ -16,9 +16,13 @@ export const All = () => {
return (
<>
<button onClick={() => setOpen(true)}>Open modal</button>
<Modal open={open} onClose={() => setOpen(false)}>
<Modal
open={open}
onClose={() => setOpen(false)}
aria-labelledby="header123"
>
<Modal.Content>
<h1>Header</h1>
<h1 id="header123">Header</h1>
<h2>subheader</h2>
<p>Cupidatat irure ipsum veniam ad in esse.</p>
<p>Cillum tempor pariatur amet ut laborum Lorem enim enim.</p>
@@ -32,9 +36,10 @@ export const All = () => {
shouldCloseOnOverlayClick={false}
open={openTwo}
onClose={() => setOpenTwo(false)}
aria-labelledby="header123"
>
<Modal.Content>
<h1>Header</h1>
<h1 id="header123">Header</h1>
<h2>subheader</h2>
<p>
Cupidatat irure ipsum veniam ad in esse. Voluptate do nulla amet
@@ -54,10 +59,9 @@ export const All = () => {
open={openThree}
closeButton={false}
onClose={() => setOpenThree(false)}
aria-label="Min modal"
>
<Modal.Content>
<h1>Header</h1>
<h2>subheader</h2>
<p>Cupidatat irure ipsum veniam ad in esse.</p>
<p>Cillum tempor pariatur amet ut laborum Lorem enim enim.</p>
</Modal.Content>
@@ -1,32 +0,0 @@
import { Meta, Canvas } from "@storybook/addon-docs";
import { Modal } from "../../index";
<Meta title="ds-react/modal/intro" />
# Hvordan ta i bruk Modal
NOTE: For at modalen skal mountes riktig må man kjøre
`Modal.setAppElement("#root");` før man bruke modalen, da i eks useEffect/useLayoutEffect.
`#root` kan da her erstattes med andre elementer, men root er relativt vanlig.
Dette er nødvendig for at skjermlesere ikke skal lese opp innholdet feil i open/closed state
Modalen styres med `open`-proppen som triggrer modal-staten til å endre. Når bruker ønsker å lukke modalen
vil `onClose`-triggre og det er opp til systemet å håndtere det.
```jsx
<Modal open={open} onClose={() => setOpen(false)}>
<Modal.Content>
<h2>Heading</h2>
<p>
Cupidatat irure ipsum veniam ad in esse. Voluptate do nulla amet
laboriselit sit dolor aliquip velit esse. Excepteur sint non minim nulla
excepteur labore non magna eu.
</p>
</Modal.Content>
</Modal>
```
## shouldCloseOnOverlayClick
Proppen `shouldCloseOnOverlayClick` toggler om `onClose` skal triggres
når bruker trykker utenfor modalen (overlayet). Er default: true .
@@ -0,0 +1,70 @@
import React, { forwardRef, useContext } from "react";
import { Down, Up } from "@navikt/ds-icons";
import { TableContext } from "..";
import HeaderCell, { HeaderCellProps } from "./HeaderCell";
interface ColumnHeaderProps extends HeaderCellProps {
/**
* Key to sort by
*/
sortKey?: string;
/**
* Column is sortable
* @default false
*/
sortable?: boolean;
}
export interface ColumnHeaderType
extends React.ForwardRefExoticComponent<
ColumnHeaderProps & React.RefAttributes<HTMLTableCellElement>
> {}
const ColumnHeader: ColumnHeaderType = forwardRef(
({ className, children, sortable = false, sortKey, ...rest }, ref) => {
const context = useContext(TableContext);
if (sortable && !sortKey) {
console.warn("ColumnHeader with `sortable=true` must have a sortKey.");
}
return (
<HeaderCell
scope="col"
ref={ref}
className={className}
aria-sort={
sortable
? context?.sort?.orderBy === sortKey
? context?.sort?.direction
: "none"
: undefined
}
{...rest}
>
{sortable ? (
<button
className="navds-table__sort-button"
onClick={
sortable && sortKey
? () => context?.onSortChange?.(sortKey)
: undefined
}
>
{children}
{context?.sort?.orderBy === sortKey &&
context?.sort?.direction === "descending" ? (
<Down aria-label="sorter synkende" />
) : (
<Up aria-label="sorter stigende" />
)}
</button>
) : (
children
)}
</HeaderCell>
);
}
);
export default ColumnHeader;
+11 -3
View File
@@ -3,7 +3,13 @@ import cl from "classnames";
import { BodyShort } from "..";
import { TableContext } from ".";
interface DataCellProps extends React.TdHTMLAttributes<HTMLTableCellElement> {}
interface DataCellProps extends React.TdHTMLAttributes<HTMLTableCellElement> {
/**
* Content alignment
* @default "left"
*/
align?: "left" | "center" | "right";
}
export interface DataCellType
extends React.ForwardRefExoticComponent<
@@ -11,14 +17,16 @@ export interface DataCellType
> {}
const DataCell: DataCellType = forwardRef(
({ className, children = "", ...rest }, ref) => {
({ className, children = "", align, ...rest }, ref) => {
const context = useContext(TableContext);
return (
<BodyShort
as="td"
ref={ref}
className={cl("navds-table__data-cell", className)}
className={cl("navds-table__data-cell", className, {
[`navds-table__data-cell--align-${align}`]: align,
})}
size={context?.size}
{...rest}
>
+11 -3
View File
@@ -2,8 +2,14 @@ import React, { forwardRef, useContext } from "react";
import cl from "classnames";
import { Label, TableContext } from "..";
interface HeaderCellProps extends React.ThHTMLAttributes<HTMLTableCellElement> {
export interface HeaderCellProps
extends React.ThHTMLAttributes<HTMLTableCellElement> {
scope?: string;
/**
* Content alignment
* @default "left"
*/
align?: "left" | "center" | "right";
}
export interface HeaderCellType
@@ -12,14 +18,16 @@ export interface HeaderCellType
> {}
const HeaderCell: HeaderCellType = forwardRef(
({ className, children, ...rest }, ref) => {
({ className, children, align, ...rest }, ref) => {
const context = useContext(TableContext);
return (
<Label
as="th"
ref={ref}
className={cl("navds-table__header-cell", className)}
className={cl("navds-table__header-cell", className, {
[`navds-table__header-cell--align-${align}`]: align,
})}
size={context?.size}
{...rest}
>
+30 -2
View File
@@ -3,9 +3,15 @@ import cl from "classnames";
import Header, { HeaderType } from "./Header";
import Body, { BodyType } from "./Body";
import Row, { RowType } from "./Row";
import ColumnHeader, { ColumnHeaderType } from "./ColumnHeader";
import HeaderCell, { HeaderCellType } from "./HeaderCell";
import DataCell, { DataCellType } from "./DataCell";
export interface SortState {
orderBy: string;
direction: "ascending" | "descending";
}
export interface TableProps
extends React.TableHTMLAttributes<HTMLTableElement> {
/**
@@ -18,6 +24,14 @@ export interface TableProps
* @default false
*/
zebraStripes?: boolean;
/**
* Sort state
*/
sort?: SortState;
/**
* Callback whens sort state changes
*/
onSortChange?: (sortKey?: string) => void;
}
export interface TableType
@@ -29,17 +43,30 @@ export interface TableType
Row: RowType;
DataCell: DataCellType;
HeaderCell: HeaderCellType;
ColumnHeader: ColumnHeaderType;
}
export interface TableContextProps {
size: "medium" | "small";
onSortChange?: (sortKey: string) => void;
sort?: SortState;
}
export const TableContext = createContext<TableContextProps | null>(null);
const Table = forwardRef(
({ className, zebraStripes = false, size = "medium", ...rest }, ref) => (
<TableContext.Provider value={{ size }}>
(
{
className,
zebraStripes = false,
size = "medium",
onSortChange,
sort,
...rest
},
ref
) => (
<TableContext.Provider value={{ size, onSortChange, sort }}>
<table
{...rest}
ref={ref}
@@ -54,6 +81,7 @@ const Table = forwardRef(
Table.Header = Header;
Table.Body = Body;
Table.Row = Row;
Table.ColumnHeader = ColumnHeader;
Table.HeaderCell = HeaderCell;
Table.DataCell = DataCell;
@@ -0,0 +1,822 @@
[
{
"name": "Luke Skywalker",
"height": 172,
"mass": 77,
"hair_color": "blond",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": 19,
"gender": "male"
},
{
"name": "C-3PO",
"height": 167,
"mass": 75,
"hair_color": "n/a",
"skin_color": "gold",
"eye_color": "yellow",
"birth_year": 112,
"gender": "n/a"
},
{
"name": "R2-D2",
"height": 96,
"mass": 32,
"hair_color": "n/a",
"skin_color": "white, blue",
"eye_color": "red",
"birth_year": 33,
"gender": "n/a"
},
{
"name": "Darth Vader",
"height": 202,
"mass": 136,
"hair_color": "none",
"skin_color": "white",
"eye_color": "yellow",
"birth_year": 41.9,
"gender": "male"
},
{
"name": "Leia Organa",
"height": 150,
"mass": 49,
"hair_color": "brown",
"skin_color": "light",
"eye_color": "brown",
"birth_year": 19,
"gender": "female"
},
{
"name": "Owen Lars",
"height": 178,
"mass": 120,
"hair_color": "brown, grey",
"skin_color": "light",
"eye_color": "blue",
"birth_year": 52,
"gender": "male"
},
{
"name": "Beru Whitesun lars",
"height": 165,
"mass": 75,
"hair_color": "brown",
"skin_color": "light",
"eye_color": "blue",
"birth_year": 47,
"gender": "female"
},
{
"name": "R5-D4",
"height": 97,
"mass": 32,
"hair_color": "n/a",
"skin_color": "white, red",
"eye_color": "red",
"birth_year": null,
"gender": "n/a"
},
{
"name": "Biggs Darklighter",
"height": 183,
"mass": 84,
"hair_color": "black",
"skin_color": "light",
"eye_color": "brown",
"birth_year": 24,
"gender": "male"
},
{
"name": "Obi-Wan Kenobi",
"height": 182,
"mass": 77,
"hair_color": "auburn, white",
"skin_color": "fair",
"eye_color": "blue-gray",
"birth_year": 57,
"gender": "male"
},
{
"name": "Anakin Skywalker",
"height": 188,
"mass": 84,
"hair_color": "blond",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": 41.9,
"gender": "male"
},
{
"name": "Wilhuff Tarkin",
"height": 180,
"mass": null,
"hair_color": "auburn, grey",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": 64,
"gender": "male"
},
{
"name": "Chewbacca",
"height": 228,
"mass": 112,
"hair_color": "brown",
"skin_color": null,
"eye_color": "blue",
"birth_year": 200,
"gender": "male"
},
{
"name": "Han Solo",
"height": 180,
"mass": 80,
"hair_color": "brown",
"skin_color": "fair",
"eye_color": "brown",
"birth_year": 29,
"gender": "male"
},
{
"name": "Greedo",
"height": 173,
"mass": 74,
"hair_color": "n/a",
"skin_color": "green",
"eye_color": "black",
"birth_year": 44,
"gender": "male"
},
{
"name": "Jabba Desilijic Tiure",
"height": 175,
"mass": null,
"hair_color": "n/a",
"skin_color": "green-tan, brown",
"eye_color": "orange",
"birth_year": 600,
"gender": "hermaphrodite"
},
{
"name": "Wedge Antilles",
"height": 170,
"mass": 77,
"hair_color": "brown",
"skin_color": "fair",
"eye_color": "hazel",
"birth_year": 21,
"gender": "male"
},
{
"name": "Jek Tono Porkins",
"height": 180,
"mass": 110,
"hair_color": "brown",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": null,
"gender": "male"
},
{
"name": "Yoda",
"height": 66,
"mass": 17,
"hair_color": "white",
"skin_color": "green",
"eye_color": "brown",
"birth_year": 896,
"gender": "male"
},
{
"name": "Palpatine",
"height": 170,
"mass": 75,
"hair_color": "grey",
"skin_color": "pale",
"eye_color": "yellow",
"birth_year": 82,
"gender": "male"
},
{
"name": "Boba Fett",
"height": 183,
"mass": 78.2,
"hair_color": "black",
"skin_color": "fair",
"eye_color": "brown",
"birth_year": 31.5,
"gender": "male"
},
{
"name": "IG-88",
"height": 200,
"mass": 140,
"hair_color": "none",
"skin_color": "metal",
"eye_color": "red",
"birth_year": 15,
"gender": "none"
},
{
"name": "Bossk",
"height": 190,
"mass": 113,
"hair_color": "none",
"skin_color": "green",
"eye_color": "red",
"birth_year": 53,
"gender": "male"
},
{
"name": "Lando Calrissian",
"height": 177,
"mass": 79,
"hair_color": "black",
"skin_color": "dark",
"eye_color": "brown",
"birth_year": 31,
"gender": "male"
},
{
"name": "Lobot",
"height": 175,
"mass": 79,
"hair_color": "none",
"skin_color": "light",
"eye_color": "blue",
"birth_year": 37,
"gender": "male"
},
{
"name": "Ackbar",
"height": 180,
"mass": 83,
"hair_color": "none",
"skin_color": "brown mottle",
"eye_color": "orange",
"birth_year": 41,
"gender": "male"
},
{
"name": "Mon Mothma",
"height": 150,
"mass": null,
"hair_color": "auburn",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": 48,
"gender": "female"
},
{
"name": "Arvel Crynyd",
"height": null,
"mass": null,
"hair_color": "brown",
"skin_color": "fair",
"eye_color": "brown",
"birth_year": null,
"gender": "male"
},
{
"name": "Wicket Systri Warrick",
"height": 88,
"mass": 20,
"hair_color": "brown",
"skin_color": "brown",
"eye_color": "brown",
"birth_year": 8,
"gender": "male"
},
{
"name": "Nien Nunb",
"height": 160,
"mass": 68,
"hair_color": "none",
"skin_color": "grey",
"eye_color": "black",
"birth_year": null,
"gender": "male"
},
{
"name": "Qui-Gon Jinn",
"height": 193,
"mass": 89,
"hair_color": "brown",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": 92,
"gender": "male"
},
{
"name": "Nute Gunray",
"height": 191,
"mass": 90,
"hair_color": "none",
"skin_color": "mottled green",
"eye_color": "red",
"birth_year": null,
"gender": "male"
},
{
"name": "Finis Valorum",
"height": 170,
"mass": null,
"hair_color": "blond",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": 91,
"gender": "male"
},
{
"name": "Padmé Amidala",
"height": 185,
"mass": 45,
"hair_color": "brown",
"skin_color": "light",
"eye_color": "brown",
"birth_year": 46,
"gender": "female"
},
{
"name": "Jar Jar Binks",
"height": 196,
"mass": 66,
"hair_color": "none",
"skin_color": "orange",
"eye_color": "orange",
"birth_year": 52,
"gender": "male"
},
{
"name": "Roos Tarpals",
"height": 224,
"mass": 82,
"hair_color": "none",
"skin_color": "grey",
"eye_color": "orange",
"birth_year": null,
"gender": "male"
},
{
"name": "Rugor Nass",
"height": 206,
"mass": null,
"hair_color": "none",
"skin_color": "green",
"eye_color": "orange",
"birth_year": null,
"gender": "male"
},
{
"name": "Ric Olié",
"height": 183,
"mass": null,
"hair_color": "brown",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": null,
"gender": "male"
},
{
"name": "Watto",
"height": 137,
"mass": null,
"hair_color": "black",
"skin_color": "blue, grey",
"eye_color": "yellow",
"birth_year": null,
"gender": "male"
},
{
"name": "Sebulba",
"height": 112,
"mass": 40,
"hair_color": "none",
"skin_color": "grey, red",
"eye_color": "orange",
"birth_year": null,
"gender": "male"
},
{
"name": "Quarsh Panaka",
"height": 183,
"mass": null,
"hair_color": "black",
"skin_color": "dark",
"eye_color": "brown",
"birth_year": 62,
"gender": "male"
},
{
"name": "Shmi Skywalker",
"height": 163,
"mass": null,
"hair_color": "black",
"skin_color": "fair",
"eye_color": "brown",
"birth_year": 72,
"gender": "female"
},
{
"name": "Darth Maul",
"height": 175,
"mass": 80,
"hair_color": "none",
"skin_color": "red",
"eye_color": "yellow",
"birth_year": 54,
"gender": "male"
},
{
"name": "Bib Fortuna",
"height": 180,
"mass": null,
"hair_color": "none",
"skin_color": "pale",
"eye_color": "pink",
"birth_year": null,
"gender": "male"
},
{
"name": "Ayla Secura",
"height": 178,
"mass": 55,
"hair_color": "none",
"skin_color": "blue",
"eye_color": "hazel",
"birth_year": 48,
"gender": "female"
},
{
"name": "Ratts Tyerel",
"height": 79,
"mass": 15,
"hair_color": "none",
"skin_color": "grey, blue",
"eye_color": null,
"birth_year": null,
"gender": "male"
},
{
"name": "Dud Bolt",
"height": 94,
"mass": 45,
"hair_color": "none",
"skin_color": "blue, grey",
"eye_color": "yellow",
"birth_year": null,
"gender": "male"
},
{
"name": "Gasgano",
"height": 122,
"mass": null,
"hair_color": "none",
"skin_color": "white, blue",
"eye_color": "black",
"birth_year": null,
"gender": "male"
},
{
"name": "Ben Quadinaros",
"height": 163,
"mass": 65,
"hair_color": "none",
"skin_color": "grey, green, yellow",
"eye_color": "orange",
"birth_year": null,
"gender": "male"
},
{
"name": "Mace Windu",
"height": 188,
"mass": 84,
"hair_color": "none",
"skin_color": "dark",
"eye_color": "brown",
"birth_year": 72,
"gender": "male"
},
{
"name": "Ki-Adi-Mundi",
"height": 198,
"mass": 82,
"hair_color": "white",
"skin_color": "pale",
"eye_color": "yellow",
"birth_year": 92,
"gender": "male"
},
{
"name": "Kit Fisto",
"height": 196,
"mass": 87,
"hair_color": "none",
"skin_color": "green",
"eye_color": "black",
"birth_year": null,
"gender": "male"
},
{
"name": "Eeth Koth",
"height": 171,
"mass": null,
"hair_color": "black",
"skin_color": "brown",
"eye_color": "brown",
"birth_year": null,
"gender": "male"
},
{
"name": "Adi Gallia",
"height": 184,
"mass": 50,
"hair_color": "none",
"skin_color": "dark",
"eye_color": "blue",
"birth_year": null,
"gender": "female"
},
{
"name": "Saesee Tiin",
"height": 188,
"mass": null,
"hair_color": "none",
"skin_color": "pale",
"eye_color": "orange",
"birth_year": null,
"gender": "male"
},
{
"name": "Yarael Poof",
"height": 264,
"mass": null,
"hair_color": "none",
"skin_color": "white",
"eye_color": "yellow",
"birth_year": null,
"gender": "male"
},
{
"name": "Plo Koon",
"height": 188,
"mass": 80,
"hair_color": "none",
"skin_color": "orange",
"eye_color": "black",
"birth_year": 22,
"gender": "male"
},
{
"name": "Mas Amedda",
"height": 196,
"mass": null,
"hair_color": "none",
"skin_color": "blue",
"eye_color": "blue",
"birth_year": null,
"gender": "male"
},
{
"name": "Gregar Typho",
"height": 185,
"mass": 85,
"hair_color": "black",
"skin_color": "dark",
"eye_color": "brown",
"birth_year": null,
"gender": "male"
},
{
"name": "Cordé",
"height": 157,
"mass": null,
"hair_color": "brown",
"skin_color": "light",
"eye_color": "brown",
"birth_year": null,
"gender": "female"
},
{
"name": "Cliegg Lars",
"height": 183,
"mass": null,
"hair_color": "brown",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": 82,
"gender": "male"
},
{
"name": "Poggle the Lesser",
"height": 183,
"mass": 80,
"hair_color": "none",
"skin_color": "green",
"eye_color": "yellow",
"birth_year": null,
"gender": "male"
},
{
"name": "Luminara Unduli",
"height": 170,
"mass": 56.2,
"hair_color": "black",
"skin_color": "yellow",
"eye_color": "blue",
"birth_year": 58,
"gender": "female"
},
{
"name": "Barriss Offee",
"height": 166,
"mass": 50,
"hair_color": "black",
"skin_color": "yellow",
"eye_color": "blue",
"birth_year": 40,
"gender": "female"
},
{
"name": "Dormé",
"height": 165,
"mass": null,
"hair_color": "brown",
"skin_color": "light",
"eye_color": "brown",
"birth_year": null,
"gender": "female"
},
{
"name": "Dooku",
"height": 193,
"mass": 80,
"hair_color": "white",
"skin_color": "fair",
"eye_color": "brown",
"birth_year": 102,
"gender": "male"
},
{
"name": "Bail Prestor Organa",
"height": 191,
"mass": null,
"hair_color": "black",
"skin_color": "tan",
"eye_color": "brown",
"birth_year": 67,
"gender": "male"
},
{
"name": "Jango Fett",
"height": 183,
"mass": 79,
"hair_color": "black",
"skin_color": "tan",
"eye_color": "brown",
"birth_year": 66,
"gender": "male"
},
{
"name": "Zam Wesell",
"height": 168,
"mass": 55,
"hair_color": "blonde",
"skin_color": "fair, green, yellow",
"eye_color": "yellow",
"birth_year": null,
"gender": "female"
},
{
"name": "Dexter Jettster",
"height": 198,
"mass": 102,
"hair_color": "none",
"skin_color": "brown",
"eye_color": "yellow",
"birth_year": null,
"gender": "male"
},
{
"name": "Lama Su",
"height": 229,
"mass": 88,
"hair_color": "none",
"skin_color": "grey",
"eye_color": "black",
"birth_year": null,
"gender": "male"
},
{
"name": "Taun We",
"height": 213,
"mass": null,
"hair_color": "none",
"skin_color": "grey",
"eye_color": "black",
"birth_year": null,
"gender": "female"
},
{
"name": "Jocasta Nu",
"height": 167,
"mass": null,
"hair_color": "white",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": null,
"gender": "female"
},
{
"name": "R4-P17",
"height": 96,
"mass": null,
"hair_color": "none",
"skin_color": "silver, red",
"eye_color": "red, blue",
"birth_year": null,
"gender": "female"
},
{
"name": "Wat Tambor",
"height": 193,
"mass": 48,
"hair_color": "none",
"skin_color": "green, grey",
"eye_color": null,
"birth_year": null,
"gender": "male"
},
{
"name": "San Hill",
"height": 191,
"mass": null,
"hair_color": "none",
"skin_color": "grey",
"eye_color": "gold",
"birth_year": null,
"gender": "male"
},
{
"name": "Shaak Ti",
"height": 178,
"mass": 57,
"hair_color": "none",
"skin_color": "red, blue, white",
"eye_color": "black",
"birth_year": null,
"gender": "female"
},
{
"name": "Grievous",
"height": 216,
"mass": 159,
"hair_color": "none",
"skin_color": "brown, white",
"eye_color": "green, yellow",
"birth_year": null,
"gender": "male"
},
{
"name": "Tarfful",
"height": 234,
"mass": 136,
"hair_color": "brown",
"skin_color": "brown",
"eye_color": "blue",
"birth_year": null,
"gender": "male"
},
{
"name": "Raymus Antilles",
"height": 188,
"mass": 79,
"hair_color": "brown",
"skin_color": "light",
"eye_color": "brown",
"birth_year": null,
"gender": "male"
},
{
"name": "Sly Moore",
"height": 178,
"mass": 48,
"hair_color": "none",
"skin_color": "pale",
"eye_color": "white",
"birth_year": null,
"gender": "female"
},
{
"name": "Tion Medon",
"height": 206,
"mass": 80,
"hair_color": "none",
"skin_color": "grey",
"eye_color": "black",
"birth_year": null,
"gender": "male"
}
]
@@ -0,0 +1,169 @@
import React, { useState } from "react";
import { rest } from "msw";
import useSWR from "swr";
import people from "./people.json";
import { Table } from "../index";
import { Loader, Pagination, SortState } from "../..";
export default {
title: "ds-react/table",
component: Table,
parameters: {
chromatic: { delay: 500 },
},
};
const rowsPerPage = 10;
const queryString = (obj) =>
Object.keys(obj)
.filter((key) => obj[key] !== undefined)
.map((key) => key + "=" + obj[key])
.join("&");
export const Async = () => {
const [page, setPage] = useState(1);
const [sort, setSort] = useState<SortState>();
const { data } = useSWR(
`/people?${queryString({
page,
sort: sort ? `${sort.orderBy}:${sort.direction}` : undefined,
})}`,
(url) => fetch(url).then((res) => res.json())
);
if (!data) {
return <Loader size="2xlarge" />;
}
const { results: people, count } = data;
const columns = [
{ key: "name", name: "Name", width: 154 },
{ key: "height", name: "Height", width: 108 },
{ key: "mass", name: "Mass", width: 95 },
{
key: "birth_year",
name: "Birth year",
value: (person) =>
person.birth_year !== null ? `${person.birth_year}BBY` : undefined,
width: 133,
},
{ key: "eye_color", name: "Eye color", width: 127 },
{ key: "gender", name: "Gender", width: 113, sortable: false },
{ key: "hair_color", name: "Hair color", width: 132 },
{ key: "skin_color", name: "Skin color", width: 133 },
];
return (
<div
style={{
display: "flex",
flexDirection: "column",
gap: 16,
}}
>
<div style={{ overflow: "auto" }}>
<Table
style={{ width: "initial" }}
sort={sort}
onSortChange={(sortKey) =>
setSort(
sort?.orderBy === sortKey && sort.direction === "descending"
? undefined
: {
orderBy: sortKey,
direction:
sort?.direction === "ascending"
? "descending"
: "ascending",
}
)
}
>
<Table.Header>
<Table.Row>
{columns.map(({ key, name, width, sortable = true }) => (
<Table.ColumnHeader
style={{ width, minWidth: width, maxWidth: width }}
key={key}
sortable={sortable}
sortKey={key}
>
{name}
</Table.ColumnHeader>
))}
</Table.Row>
</Table.Header>
<Table.Body>
{people.map((person) => (
<Table.Row key={person.name}>
{columns.map(({ key, width, value }) => (
<Table.DataCell
style={{
width,
minWidth: width,
maxWidth: width,
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
}}
title={person[key]}
key={key}
>
{value ? value(person) : person[key]}
</Table.DataCell>
))}
</Table.Row>
))}
</Table.Body>
</Table>
</div>
<Pagination
page={page}
onPageChange={setPage}
count={Math.ceil(count / rowsPerPage)}
/>
</div>
);
};
Async.parameters = {
msw: {
handlers: [
rest.get("/people", (req, res, ctx) => {
const comparator = (a, b, orderBy) => {
if (b[orderBy] < a[orderBy]) {
return -1;
}
if (b[orderBy] > a[orderBy]) {
return 1;
}
return 0;
};
const page = Number(req.url.searchParams.get("page"));
const sort = req.url.searchParams.get("sort");
return res(
//ctx.delay(),
ctx.json({
count: people.length,
results: people
.slice()
.sort((a, b) => {
if (sort) {
const [orderBy, direction] = sort.split(":");
return direction === "ascending"
? comparator(b, a, orderBy)
: comparator(a, b, orderBy);
}
return 1;
})
.slice((page - 1) * rowsPerPage, page * rowsPerPage),
})
);
}),
],
},
};
@@ -0,0 +1,376 @@
import React, { useState } from "react";
import { capitalize } from "@material-ui/core";
import { Table } from "../";
import { Button } from "../..";
import { SortState } from "../..";
export default {
title: "ds-react/table",
component: Table,
};
interface Column {
key: string;
name?: string;
sortable?: boolean;
render?: (value: any) => React.ReactElement<any, any> | null;
value?: (oppgave: any) => any;
width?: number;
align?: "left" | "center" | "right";
}
export const Hot = () => {
const [sort, setSort] = useState<SortState>();
const columns: Column[] = [
{
key: "eier",
name: "Eier",
render: (value) =>
value ?? (
<Button
size="xsmall"
style={{ marginTop: -2, marginBottom: -2, marginLeft: -6 }}
variant="tertiary"
>
Start saken
</Button>
),
value: (oppgave) => oppgave.saksbehandler?.navn,
width: 152,
},
{
key: "status",
name: "Status",
value: (oppgave) =>
({
ALLE: "Alle",
VEDTAK_FATTET: "Innvilget",
AVVENTER_SAKSBEHANDLER: "Mottatt",
SENDT_GOSYS: "Sendt GOSYS",
TILDELT_SAKSBEHANDLER: "Under behandling",
}[oppgave.status]),
width: 154,
},
{
key: "område",
name: "Område",
value: (oppgave) =>
capitalize(oppgave.personinformasjon.funksjonsnedsettelse[0]),
width: 152,
},
{
key: "søknadOm",
name: "Søknad om",
width: 192,
},
{
key: "hjelpemiddelbruker",
name: "Hjelpemiddelbruker",
value: (oppgave) =>
`${oppgave.personinformasjon.etternavn}, ${oppgave.personinformasjon.fornavn}`,
width: 188,
},
{
key: "fødselsnr",
name: "Fødselsnr.",
value: (oppgave) =>
`${oppgave.personinformasjon.fnr.slice(
0,
6
)} ${oppgave.personinformasjon.fnr.slice(6)}`,
width: 124,
align: "right",
},
{
key: "bosted",
name: "Bosted",
value: (oppgave) => oppgave.personinformasjon.bosted,
width: 144,
},
{
key: "formidlerNavn",
name: "Formidler",
width: 164,
},
{
key: "mottattDato",
name: "Mottatt dato",
width: 124,
},
{
key: "menu",
sortable: false,
render: (value) => (
<span style={{ display: "flex", marginBlock: -2 }}></span>
),
value: (oppgave) => oppgave.saksbehandler,
},
];
return (
<div
style={{
display: "flex",
flexDirection: "column",
gap: 16,
}}
>
<div style={{ overflow: "auto" }}>
<Table
size="small"
style={{ width: "initial" }}
sort={sort}
onSortChange={(sortKey) =>
setSort({
orderBy: sortKey,
direction:
sort?.direction === "ascending" ? "descending" : "ascending",
})
}
>
<Table.Header>
<Table.Row>
{columns.map(({ key, name, width, sortable = true, align }) => (
<Table.ColumnHeader
style={{
width,
minWidth: width,
maxWidth: width,
}}
key={key}
sortable={sortable}
sortKey={key}
align={align}
>
<div
style={{
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
}}
>
{name}
</div>
</Table.ColumnHeader>
))}
</Table.Row>
</Table.Header>
<Table.Body>
{data
.slice()
.sort((a, b) => {
if (sort) {
const comparator = (a, b, orderBy) => {
const lookup = (oppgave) => {
const column = columns.find(({ key }) => key === orderBy);
return column.value
? column.value(oppgave)
: oppgave[orderBy];
};
if (lookup(b) < lookup(a) || lookup(b) === undefined) {
return -1;
}
if (lookup(b) > lookup(a)) {
return 1;
}
return 0;
};
return sort.direction === "ascending"
? comparator(b, a, sort.orderBy)
: comparator(a, b, sort.orderBy);
}
return 1;
})
.map((oppgave) => (
<Table.Row key={oppgave.saksid}>
{columns.map(
({
key,
value,
width,
render = (value) => value,
align,
}) => (
<Table.DataCell
style={{
width,
minWidth: width,
maxWidth: width,
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
}}
title={oppgave[key]}
key={key}
align={align}
>
{render(value ? value(oppgave) : oppgave[key])}
</Table.DataCell>
)
)}
</Table.Row>
))}
</Table.Body>
</Table>
</div>
</div>
);
};
const data = [
{
saksid: "111111",
søknadOm:
"Søknad om: Toalettforhøyer; kalender-/minnesystem; støttehåndtak; tidsur; komfyr-/elvakt",
mottattDato: "2021-09-19T13:55:45",
formidlerNavn: "Kari Johansen",
personinformasjon: {
fnr: "19044238651",
funksjonsnedsettelse: ["bevegelse"],
fornavn: "Petter Andreas",
etternavn: "Hansen",
poststed: "Trondheim",
bosted: "TRONDHEIM",
},
saksbehandler: null,
status: "AVVENTER_SAKSBEHANDLER",
enhet: [
{
enhetsnummer: "6969",
enhetsnavn: "Enhetsnavn",
},
],
},
{
saksid: "222222",
søknadOm: "Hjelpemidler",
mottattDato: "2021-09-24T13:55:45",
formidlerNavn: "Kari Johansen",
personinformasjon: {
fnr: "13044238651",
funksjonsnedsettelse: ["bevegelse"],
fornavn: "Mia Cathrine",
etternavn: "Svendsen",
poststed: "Trondheim",
bosted: "TRONDHEIM",
},
saksbehandler: null,
status: "AVVENTER_SAKSBEHANDLER",
enhet: [
{
enhetsnummer: "6969",
enhetsnavn: "Enhetsnavn",
},
],
},
{
saksid: "5878444",
søknadOm: "Hjelpemidler",
mottattDato: "2021-09-20T13:55:45",
formidlerNavn: "Kari Johansen",
personinformasjon: {
fnr: "130441386551",
funksjonsnedsettelse: ["bevegelse"],
fornavn: "Per Bjørkli",
etternavn: "Hansen",
poststed: "Trondheim",
bosted: "TRONDHEIM",
},
saksbehandler: {
epost: "a.b@nav.no",
objectId: "23ea7485-1324-4b25-a763-6969",
navn: "Geir Markusen",
},
status: "SENDT_GOSYS",
enhet: [
{
enhetsnummer: "6969",
enhetsnavn: "Enhetsnavn",
},
],
},
{
saksid: "1234567",
søknadOm: "Hjelpemidler",
mottattDato: "2021-06-25T13:55:45",
formidlerNavn: "Kari Johansen",
personinformasjon: {
fnr: "06115559891",
funksjonsnedsettelse: ["kognisjon"],
fornavn: "Iver",
etternavn: "Slettan",
poststed: "Lade",
bosted: "TRONDHEIM",
},
saksbehandler: {
epost: "a.b@nav.no",
objectId: "23ea7485-1324-4b25-a763-assdfdfa",
navn: "Silje Saksbehandler",
},
status: "TILDELT_SAKSBEHANDLER",
enhet: [
{
enhetsnummer: "6969",
enhetsnavn: "Enhetsnavn",
},
],
},
{
saksid: "999999",
søknadOm: "Hjelpemidler",
mottattDato: "2021-06-25T16:55:45",
formidlerNavn: "Kari Johansen",
personinformasjon: {
fnr: "16120101181",
funksjonsnedsettelse: ["bevegelse"],
fornavn: "Sondre",
etternavn: "Hansen",
poststed: "Lade",
bosted: "TRONDHEIM",
},
saksbehandler: {
epost: "a.b@nav.no",
objectId: "23ea7485-1324-4b25-a763-6969",
navn: "Geir Markusen",
},
status: "TILDELT_SAKSBEHANDLER",
enhet: [
{
enhetsnummer: "6969",
enhetsnavn: "Enhetsnavn",
},
],
},
{
saksid: "888888",
søknadOm: "Hjelpemidler",
mottattDato: "2021-09-19T13:55:45",
formidlerNavn: "Kari Johansen",
personinformasjon: {
fnr: "19044238651",
funksjonsnedsettelse: ["bevegelse"],
fornavn: "Geir",
etternavn: "Dalby",
poststed: "Trondheim",
bosted: "TRONDHEIM",
},
saksbehandler: {
epost: "a.b@nav.no",
objectId: "23ea7485-1324-4b25-a763-assdfdfa",
navn: "Silje Saksbehandler",
},
status: "VEDTAK_FATTET",
enhet: [
{
enhetsnummer: "6969",
enhetsnavn: "Enhetsnavn",
},
],
},
];
@@ -1,6 +1,6 @@
import React, { useState } from "react";
import { Table } from "../index";
import { Alert, Checkbox, Link } from "@navikt/ds-react";
import { Table } from "../";
import { Alert, Button, Checkbox, Link } from "../..";
export default {
title: "ds-react/table",
@@ -12,6 +12,7 @@ export const All = () => {
<Table {...props}>
<Table.Header>
<Table.Row>
{props.button && <Table.HeaderCell>Action</Table.HeaderCell>}
<Table.HeaderCell>ID</Table.HeaderCell>
<Table.HeaderCell>Fornavn</Table.HeaderCell>
<Table.HeaderCell>Etternavn</Table.HeaderCell>
@@ -20,18 +21,48 @@ export const All = () => {
</Table.Header>
<Table.Body>
<Table.Row>
{props.button && (
<Table.DataCell
style={{
paddingTop: 6,
paddingBottom: 6,
}}
>
<Button size="xsmall">Click me!</Button>
</Table.DataCell>
)}
<Table.HeaderCell>1</Table.HeaderCell>
<Table.DataCell>Jean-Luc</Table.DataCell>
<Table.DataCell>Picard</Table.DataCell>
<Table.DataCell>Kaptein</Table.DataCell>
</Table.Row>
<Table.Row>
{props.button && (
<Table.DataCell
style={{
paddingTop: 6,
paddingBottom: 6,
}}
>
<Button size="xsmall">Click me!</Button>
</Table.DataCell>
)}
<Table.HeaderCell>2</Table.HeaderCell>
<Table.DataCell>William</Table.DataCell>
<Table.DataCell>Riker</Table.DataCell>
<Table.DataCell>Kommandør</Table.DataCell>
</Table.Row>
<Table.Row>
{props.button && (
<Table.DataCell
style={{
paddingTop: 6,
paddingBottom: 6,
}}
>
<Button size="xsmall">Click me!</Button>
</Table.DataCell>
)}
<Table.HeaderCell>3</Table.HeaderCell>
<Table.DataCell>Geordi</Table.DataCell>
<Table.DataCell>La Forge</Table.DataCell>
@@ -48,6 +79,8 @@ export const All = () => {
<TableComponent zebraStripes />
<h2>Small Table</h2>
<TableComponent size="small" />
<h2>Small Table with buttons</h2>
<TableComponent size="small" button />
<h2>Table with divs</h2>
<Alert variant="warning">
Obs! Hvis man skal bygge tabeller uten å bruke vanlig {"<tabell> "}
@@ -51,6 +51,17 @@ const Items = (icon?: boolean, both?: boolean) => (
</>
);
export const UUDemo = () => {
const [activeValue, setActiveValue] = useState("ulest");
return (
<ToggleGroup value={activeValue} onChange={setActiveValue}>
<ToggleGroup.Item value="ulest">Ulest</ToggleGroup.Item>
<ToggleGroup.Item value="lest">Leste</ToggleGroup.Item>
<ToggleGroup.Item value="sendt">Sendte</ToggleGroup.Item>
</ToggleGroup>
);
};
export const All = () => {
const [activeValue, setActiveValue] = useState("first");
@@ -120,7 +120,7 @@ const ToggleGroup = forwardRef<HTMLDivElement, ToggleGroupProps>(
`navds-toggle-group--${size}`
)}
{...(describeBy && { "aria-describedby": describeBy })}
role="toolbar"
role="radiogroup"
type="single"
>
{children}
+33
View File
@@ -1,3 +1,5 @@
import { useEffect } from "react";
export * from "./OverridableComponent";
export * from "./useId";
@@ -11,3 +13,34 @@ export const omit = (obj: object, props: string[]) =>
}),
{}
);
export interface ListenerT {
addEventListener(
name: string,
handler: (event?: any) => void,
...args: any[]
);
removeEventListener(
name: string,
handler: (event?: any) => void,
...args: any[]
);
}
/* https://github.com/streamich/react-use/blob/master/src/useEvent.ts */
export const useEventListener = <T extends ListenerT>(
name: Parameters<ListenerT["addEventListener"]>[0],
handler: Parameters<ListenerT["addEventListener"]>[1],
target: null | T | Window = window
): void => {
useEffect(() => {
if (!target) {
return;
}
target?.addEventListener(name, handler);
return () => {
target?.addEventListener(name, handler);
};
}, [name, handler, target]);
};
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react-internal",
"version": "0.11.16",
"version": "0.12.4",
"private": false,
"description": "NAV internal designsystem react components",
"author": "NAV Designsystem team",
@@ -34,7 +34,7 @@
},
"dependencies": {
"@navikt/ds-icons": "^0.8.4",
"@navikt/ds-react": "^0.16.16",
"@navikt/ds-react": "^0.17.4",
"@popperjs/core": "^2.10.1",
"classnames": "^2.3.1",
"copy-to-clipboard": "^3.3.1",
@@ -83,7 +83,7 @@ const CopyToClipboard = forwardRef<HTMLButtonElement, CopyToClipboardProps>(
arrow={false}
className="navdsi-copy-to-clipboard__popover"
>
<BodyShort size={size} as="span">
<BodyShort size={size === "medium" ? size : "small"} as="span">
{popoverText}
</BodyShort>
</Popover>
+7 -7
View File
@@ -1,18 +1,18 @@
<h1 align="center">
<img src="https://avatars.githubusercontent.com/u/11848947?s=200&v=4" />
<br/>Designsystemet
<br/>Aksel
</h1>
<div align="center">
<p>
Monorepo for alle felleskomponenter i NAVs frontend-rammeverk.
Monorepo for Frontend-komponenter til Aksel.
</p>
<p>
<a href="https://github.com/navikt/nav-frontend-moduler/pulls">
<img src="https://img.shields.io/badge/PRs-welcome-green.svg" />
</a>
<img src="https://github.com/navikt/nav-frontend-moduler/actions/workflows/build-publish.yml/badge.svg" />
<img src="https://github.com/navikt/nav-frontend-moduler/actions/workflows/chromatic.yml/badge.svg" />
<img src="https://github.com/navikt/nav-frontend-moduler/actions/workflows/chromatic.yml/badge.svg" />
</p>
<img src="https://img.shields.io/endpoint?style=for-the-badge&url=https://runkit.io/fezvrasta/combined-npm-downloads/1.0.0?packages=@navikt/ds-react,@navikt/ds-css,@navikt/ds-icons,@navikt/ds-tokens,@navikt/ds-react-internal,@navikt/ds-css-internal,nav-frontend-alertstriper-style,nav-frontend-alertstriper,nav-frontend-chevron-style,nav-frontend-chevron,nav-frontend-core,nav-frontend-ekspanderbartpanel-style,nav-frontend-ekspanderbartpanel,nav-frontend-etiketter-style,nav-frontend-etiketter,nav-frontend-fullbreddeknapp-style,nav-frontend-grid-style,nav-frontend-grid,nav-frontend-hjelpetekst-style,nav-frontend-hjelpetekst,nav-frontend-ikoner-assets,nav-frontend-ikonknapper,nav-frontend-js-utils,nav-frontend-knapper-style,nav-frontend-knapper,nav-frontend-lenkepanel-style,nav-frontend-lenkepanel,nav-frontend-lenker-style,nav-frontend-lenker,nav-frontend-lesmerpanel-style,nav-frontend-lesmerpanel,nav-frontend-lukknapp-style,nav-frontend-lukknapp,nav-frontend-modal-style,nav-frontend-modal,nav-frontend-paneler-style,nav-frontend-paneler,nav-frontend-popover-style,nav-frontend-popover,nav-frontend-skjema-style,nav-frontend-skjema,nav-frontend-snakkeboble-style,nav-frontend-snakkeboble,nav-frontend-spinner-style,nav-frontend-spinner,nav-frontend-stegindikator-style,nav-frontend-stegindikator,nav-frontend-tabell-style,nav-frontend-tabs-style,nav-frontend-tabs,nav-frontend-tekstomrade,nav-frontend-toggle-style,nav-frontend-toggle,nav-frontend-typografi-style,nav-frontend-typografi,nav-frontend-veileder-style,nav-frontend-veileder,nav-frontend-veilederpanel-style,nav-frontend-veilederpanel" />
<h4><a href="https://design.nav.no">Nettside</a> | <a href="https://github.com/navikt/nav-frontend-moduler/blob/master/CONTRIBUTING.md">Bidra</a> | <a href="https://github.com/navikt/nav-frontend-moduler/issues">Bug-rapport</a>
@@ -26,8 +26,8 @@ Repoet inneholder følgende:
- [Storybook](https://5f801fb2aea7820022de2936-rognyzqcpw.chromatic.com/) for komponenter.
- [Gammel dokumentasjon](https://old-design-nav.vercel.app) for komponenter.
## Dokumentasjon
## Dokumentasjon
[Dokumentasjon side forvaltes i dette repoet](https://github.com/navikt/verktoykassen)
## Endringslogg
@@ -41,9 +41,9 @@ Gå til [CONTRIBUTING.md](https://github.com/navikt/nav-frontend-moduler/blob/ma
## Lisenser
Gå til [LICENSE](https://github.com/navikt/nav-frontend-moduler/blob/master/LICENCE)
## Contributors
<a href="https://github.com/navikt/nav-frontend-moduler/graphs/contributors">
<img src="https://contrib.rocks/image?repo=navikt/nav-frontend-moduler" />
</a>
+4 -2
View File
@@ -10,8 +10,8 @@
"storybook": "start-storybook -p 6006",
"build:packages": "yarn build:tokens && yarn run --silent lerna run build",
"build:storybook": "build-storybook",
"chromatic": "npx chromatic --project-token x3xqdfgkujg --build-script-name build:storybook",
"build:tokens": "yarn workspace @navikt/ds-tokens build",
"chromatic": "npx chromatic --project-token x3xqdfgkujg",
"lerna:watch": "yarn run lerna run watch --stream",
"update:icons": "yarn workspace @navikt/ds-icons update",
"update:tokens": "yarn workspace @navikt/ds-tokens update",
@@ -103,6 +103,8 @@
"typescript": "^4.3.5",
"webpack": "^5.4.0",
"webpack-cli": "^4.2.0",
"react-dom": "^17.0.2"
"react-dom": "^17.0.2",
"msw": "^0.36.5",
"msw-storybook-addon": "^1.6.0"
}
}
+2278 -2135
View File
File diff suppressed because it is too large. Load diff