Skip to content

Commit

Permalink
Merge pull request #2 from Altinn/chore/setup_project
Browse files Browse the repository at this point in the history
feat: setup project with components: Layout, Header, Toolbar, Menu and Avatar
  • Loading branch information
seanes authored Oct 29, 2024
2 parents 4e23cfa + f789c56 commit 82e9779
Show file tree
Hide file tree
Showing 125 changed files with 11,674 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,9 @@ FodyWeavers.xsd

# JetBrains Rider
*.sln.iml

*storybook.log

.idea/
dist/
**/.DS_Store
22 changes: 22 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {StorybookConfig} from "@storybook/react-vite";

const config: StorybookConfig = {
stories: [
"../lib/components/**/*.stories.@(ts|tsx)",
],
addons: [
"@storybook/addon-onboarding",
"@storybook/addon-links",
"@storybook/addon-essentials",
"@chromatic-com/storybook",
"@storybook/addon-interactions",
],
framework: {
name: "@storybook/react-vite",
options: {},
},
docs: {
autodocs: "tag",
},
};
export default config;
15 changes: 15 additions & 0 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import "../lib/css/global.css";

/** @type { import('@storybook/react').Preview } */
const preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};

export default preview;
65 changes: 65 additions & 0 deletions biome.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.3/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"defaultBranch": "main"
},
"organizeImports": {
"enabled": true
},
"formatter": {
"formatWithErrors": true,
"enabled": true,
"lineWidth": 120,
"lineEnding": "lf",
"ignore": [],
"indentStyle": "space",
"indentWidth": 2
},
"javascript": {
"formatter": {
"arrowParentheses": "always",
"quoteStyle": "single",
"jsxQuoteStyle": "double",
"semicolons": "always",
"trailingCommas": "all",
"quoteProperties": "asNeeded",
"bracketSpacing": true,
"bracketSameLine": false
}
},
"css": {
"parser": {
"cssModules": true
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
},
"linter": {
"enabled": true
}
},
"linter": {
"enabled": true,
"rules": {
"suspicious": {
"noArrayIndexKey": "off"
},
"recommended": true,
"style": {
"noUnusedTemplateLiteral": "off",
"noNonNullAssertion": "off",
"useTemplate": "off"
},
"correctness": {
"useExhaustiveDependencies": "warn"
}
}
},
"files": {
"ignore": [".github", "node_modules", "dist", "build", ".storybook"]
}
}
Binary file added lib/components/.DS_Store
Binary file not shown.
Binary file added lib/components/Avatar/.DS_Store
Binary file not shown.
100 changes: 100 additions & 0 deletions lib/components/Avatar/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"use client";
import cx from "classnames";
import { useState } from "react";
import { fromStringToColor } from "./color";
import styles from "./avatar.module.css";

export type AvatarType = "company" | "person" | "custom";
export type AvatarSize = "xs" | "sm" | "md" | "lg" | "xl";
export type AvatarVariant = "square" | "circle";
export type AvatarColor = "dark" | "light";

/**
* Props for the Avatar component.
*/
export interface AvatarProps {
/** The name to display in the avatar. */
name: string;
/** The type of avatar. */
type?: AvatarType;
/** The size of the avatar. */
size?: AvatarSize;
/** The variant of the avatar shape. */
variant?: AvatarVariant;
/** The color theme of the avatar. */
color?: AvatarColor;
/** Additional class names to apply to the avatar. */
className?: string;
/** URL of the image to display in the avatar. */
imageUrl?: string;
/** Alt text for the image. */
imageUrlAlt?: string;
/** Whether to display an outline around the avatar. */
outline?: boolean;
/** Custom label to display inside the avatar. */
customLabel?: string;
}

/**
* Avatar component to display user or company avatars with various customization options.
*/
export const Avatar = ({
type,
size = "sm",
variant,
color,
name = "Avatar",
outline = false,
imageUrl,
imageUrlAlt,
customLabel,
className,
}: AvatarProps): JSX.Element => {
const [hasImageError, setHasImageError] = useState<boolean>(false);

const defaultVariant = type === "person" ? "circle" : "square";
const defaultColor = type === "person" ? "light" : "dark";
const appliedVariant = variant || defaultVariant;
const appliedColor = color || defaultColor;

const { backgroundColor, foregroundColor } = fromStringToColor(
name,
appliedColor
);
const initials = (name[0] ?? "").toUpperCase();
const usingImageUrl = imageUrl && !hasImageError;

const inlineStyles = !usingImageUrl
? {
backgroundColor,
color: foregroundColor,
}
: undefined;

return (
<div
className={cx(
styles.avatar,
styles[appliedVariant],
styles[size],
{ [styles.outline]: outline },
className
)}
style={inlineStyles}
aria-hidden
>
{usingImageUrl ? (
<img
src={imageUrl}
className={styles.image}
alt={imageUrlAlt || imageUrl}
onError={() => {
setHasImageError(true);
}}
/>
) : (
<span>{customLabel || initials}</span>
)}
</div>
);
};
67 changes: 67 additions & 0 deletions lib/components/Avatar/AvatarGroup.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { Meta, StoryObj } from "@storybook/react";
import { fn } from "@storybook/test";
import { AvatarGroup } from "./AvatarGroup";

const meta = {
title: "Avatar/AvatarGroup",
component: AvatarGroup,
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ["autodocs"],
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
// layout: 'fullscreen',
},
args: {},
} satisfies Meta<typeof AvatarGroup>;

export default meta;
type Story = StoryObj<typeof meta>;

export const People: Story = {
args: {
type: "person",
items: [
{
name: "Albert Åberg",
},
{
name: "Birger Meling",
},
{
name: "Celine Dion",
},
],
},
};

export const Companies: Story = {
args: {
type: "company",
items: [
{
name: "Albert Åberg",
},
{
name: "Birger Meling",
},
{
name: "Celine Dion",
},
],
},
};

export const CompanyAndPerson: Story = {
args: {
items: [
{
type: "company",
name: "Albert Åberg",
},
{
type: "person",
name: "Birger Meling",
},
],
},
};
42 changes: 42 additions & 0 deletions lib/components/Avatar/AvatarGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import cx from 'classnames';
import {Avatar, type AvatarProps, type AvatarSize, type AvatarType} from '.';
import styles from './avatarGroup.module.css';
import {useMemo} from 'react';

export interface AvatarGroupProps {
items?: AvatarProps[];
maxItemsCount?: number;
type?: AvatarType;
size?: AvatarSize;
className?: string;
}

export const AvatarGroup = ({ items = [], maxItemsCount = 4, type, size = 'sm', className }: AvatarGroupProps) => {
const maxItems = useMemo(() => items.slice(0, maxItemsCount), [items, maxItemsCount]);

if (items?.length === 0) {
return <div className={styles.avatarGroup} />;
}

return (
<ul className={cx(styles.reset, styles.group, styles[size], className)} data-count={maxItems?.length}>
{maxItems.map((avatar, index) => {
const lastLegalAvatarReached = index === maxItemsCount - 1;
const customLabel = avatar.customLabel || lastLegalAvatarReached ? items.length.toString() : undefined;
return (
<li className={cx(styles.reset, styles.item)} key={avatar.name}>
<Avatar
name={avatar.name}
customLabel={customLabel}
imageUrl={avatar.imageUrl}
imageUrlAlt={avatar.imageUrlAlt}
type={avatar?.type || type}
size={size}
outline
/>
</li>
);
})}
</ul>
);
};
59 changes: 59 additions & 0 deletions lib/components/Avatar/avatar.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
.avatar {
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
--avatar-font-size-xs: 0.75rem;
--avatar-font-size-sm: 0.875rem;
--avatar-font-size-md: 1.125rem;
--avatar-font-size-lg: 1.25rem;
--avatar-font-size-xl: 1.5rem;
}

.circle {
border-radius: 50%;
}

.square {
border-radius: 5%;
}

.outline {
outline: 1px solid #ffffff;
box-sizing: border-box;
}

.xs {
font-size: var(--avatar-font-size-xs);
width: 20px;
height: 20px;
}

.sm {
font-size: var(--avatar-font-size-sm);
width: 24px;
height: 24px;
}

.md {
font-size: var(--avatar-font-size-md);
width: 30px;
height: 30px;
}

.lg {
font-size: var(--avatar-font-size-lg);
width: 36px;
height: 36px;
}

.xl {
font-size: var(--avatar-font-size-xl);
width: 44px;
height: 44px;
}

.image {
width: 100%;
height: 100%;
}
Loading

0 comments on commit 82e9779

Please sign in to comment.