Add new Avatar component

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-04-14 17:09:37 +02:00
parent 335a3f1664
commit 851779d101
12 changed files with 380 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/canon': patch
---
Add new Avatar component to Canon.
+49
View File
@@ -0,0 +1,49 @@
.canon-AvatarRoot {
vertical-align: middle;
user-select: none;
color: var(--canon-fg-primary);
background-color: var(--canon-bg-surface-2);
border-radius: 100%;
justify-content: center;
align-items: center;
width: 2rem;
height: 2rem;
font-size: 1rem;
font-weight: 500;
line-height: 1;
display: inline-flex;
overflow: hidden;
}
.canon-AvatarRoot[data-size="small"] {
width: 1.5rem;
height: 1.5rem;
}
.canon-AvatarRoot[data-size="medium"] {
width: 2rem;
height: 2rem;
}
.canon-AvatarRoot[data-size="large"] {
width: 3rem;
height: 3rem;
}
.canon-AvatarImage {
object-fit: cover;
width: 100%;
height: 100%;
}
.canon-AvatarFallback {
width: 100%;
height: 100%;
font-size: var(--canon-font-size-3);
font-weight: var(--canon-font-weight-regular);
box-shadow: inset 0 0 0 1px var(--canon-border);
border-radius: var(--canon-radius-full);
justify-content: center;
align-items: center;
display: flex;
}
+50
View File
@@ -1,3 +1,53 @@
.canon-AvatarRoot {
vertical-align: middle;
user-select: none;
color: var(--canon-fg-primary);
background-color: var(--canon-bg-surface-2);
border-radius: 100%;
justify-content: center;
align-items: center;
width: 2rem;
height: 2rem;
font-size: 1rem;
font-weight: 500;
line-height: 1;
display: inline-flex;
overflow: hidden;
}
.canon-AvatarRoot[data-size="small"] {
width: 1.5rem;
height: 1.5rem;
}
.canon-AvatarRoot[data-size="medium"] {
width: 2rem;
height: 2rem;
}
.canon-AvatarRoot[data-size="large"] {
width: 3rem;
height: 3rem;
}
.canon-AvatarImage {
object-fit: cover;
width: 100%;
height: 100%;
}
.canon-AvatarFallback {
width: 100%;
height: 100%;
font-size: var(--canon-font-size-3);
font-weight: var(--canon-font-weight-regular);
box-shadow: inset 0 0 0 1px var(--canon-border);
border-radius: var(--canon-radius-full);
justify-content: center;
align-items: center;
display: flex;
}
.canon-Box {
font-family: var(--canon-font-regular);
font-weight: var(--canon-font-weight-regular);
+50
View File
@@ -9222,6 +9222,56 @@
--canon-scrollbar-thumb: #575757;
}
.canon-AvatarRoot {
vertical-align: middle;
user-select: none;
color: var(--canon-fg-primary);
background-color: var(--canon-bg-surface-2);
border-radius: 100%;
justify-content: center;
align-items: center;
width: 2rem;
height: 2rem;
font-size: 1rem;
font-weight: 500;
line-height: 1;
display: inline-flex;
overflow: hidden;
}
.canon-AvatarRoot[data-size="small"] {
width: 1.5rem;
height: 1.5rem;
}
.canon-AvatarRoot[data-size="medium"] {
width: 2rem;
height: 2rem;
}
.canon-AvatarRoot[data-size="large"] {
width: 3rem;
height: 3rem;
}
.canon-AvatarImage {
object-fit: cover;
width: 100%;
height: 100%;
}
.canon-AvatarFallback {
width: 100%;
height: 100%;
font-size: var(--canon-font-size-3);
font-weight: var(--canon-font-weight-regular);
box-shadow: inset 0 0 0 1px var(--canon-border);
border-radius: var(--canon-radius-full);
justify-content: center;
align-items: center;
display: flex;
}
.canon-Box {
font-family: var(--canon-font-regular);
font-weight: var(--canon-font-weight-regular);
+17
View File
@@ -3,6 +3,7 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { Avatar as Avatar_2 } from '@base-ui-components/react/avatar';
import { Breakpoint as Breakpoint_2 } from '@backstage/canon';
import { ChangeEvent } from 'react';
import { Context } from 'react';
@@ -52,6 +53,22 @@ export type AsProps =
| 'dl'
| 'dt';
// @public (undocumented)
export const Avatar: ForwardRefExoticComponent<
AvatarProps & RefAttributes<HTMLSpanElement>
>;
// @public (undocumented)
export interface AvatarProps
extends React.ComponentPropsWithoutRef<typeof Avatar_2.Root> {
// (undocumented)
name: string;
// (undocumented)
size?: 'small' | 'medium' | 'large';
// (undocumented)
src: string;
}
// @public (undocumented)
export type BooleanPropDef = {
type: 'boolean';
@@ -0,0 +1,54 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import { Avatar } from './index';
import { Flex } from '@backstage/canon';
const meta = {
title: 'Components/Avatar',
component: Avatar,
} satisfies Meta<typeof Avatar>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
src: 'https://avatars.githubusercontent.com/u/1540635?v=4',
name: 'Charles de Dreuille',
},
};
export const Fallback: Story = {
args: {
...Default.args,
src: 'https://avatars.githubusercontent.com/u/15406AAAAAAAAA',
},
};
export const Sizes: Story = {
args: {
...Default.args,
},
render: args => (
<Flex>
<Avatar {...args} size="small" />
<Avatar {...args} size="medium" />
<Avatar {...args} size="large" />
</Flex>
),
};
@@ -0,0 +1,65 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.canon-AvatarRoot {
display: inline-flex;
justify-content: center;
align-items: center;
vertical-align: middle;
border-radius: 100%;
user-select: none;
font-weight: 500;
color: var(--canon-fg-primary);
background-color: var(--canon-bg-surface-2);
font-size: 1rem;
line-height: 1;
overflow: hidden;
height: 2rem;
width: 2rem;
}
.canon-AvatarRoot[data-size='small'] {
height: 1.5rem;
width: 1.5rem;
}
.canon-AvatarRoot[data-size='medium'] {
height: 2rem;
width: 2rem;
}
.canon-AvatarRoot[data-size='large'] {
height: 3rem;
width: 3rem;
}
.canon-AvatarImage {
object-fit: cover;
height: 100%;
width: 100%;
}
.canon-AvatarFallback {
align-items: center;
display: flex;
justify-content: center;
height: 100%;
width: 100%;
font-size: var(--canon-font-size-3);
font-weight: var(--canon-font-weight-regular);
box-shadow: inset 0 0 0 1px var(--canon-border);
border-radius: var(--canon-radius-full);
}
@@ -0,0 +1,45 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { forwardRef, ElementRef } from 'react';
import { Avatar as AvatarPrimitive } from '@base-ui-components/react/avatar';
import clsx from 'clsx';
import { AvatarProps } from './types';
/** @public */
export const Avatar = forwardRef<
ElementRef<typeof AvatarPrimitive.Root>,
AvatarProps
>(({ className, src, name, size = 'medium', ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={clsx('canon-AvatarRoot', className)}
data-size={size}
{...props}
>
<AvatarPrimitive.Image className="canon-AvatarImage" src={src} />
<AvatarPrimitive.Fallback className="canon-AvatarFallback">
{(name || '')
.split(' ')
.map(word => word[0])
.join('')
.toLocaleUpperCase('en-US')
.slice(0, 2)}
</AvatarPrimitive.Fallback>
</AvatarPrimitive.Root>
));
Avatar.displayName = AvatarPrimitive.Root.displayName;
@@ -0,0 +1,18 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { Avatar } from './Avatar';
export type { AvatarProps } from './types';
@@ -0,0 +1,25 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Avatar } from '@base-ui-components/react/avatar';
/** @public */
export interface AvatarProps
extends React.ComponentPropsWithoutRef<typeof Avatar.Root> {
src: string;
name: string;
size?: 'small' | 'medium' | 'large';
}
+1
View File
@@ -14,6 +14,7 @@
* limitations under the License.
*/
@import '../components/Avatar/Avatar.styles.css';
@import '../components/Box/styles.css';
@import '../components/Button/styles.css';
@import '../components/DataTable/Root/DataTableRoot.styles.css';
+1
View File
@@ -32,6 +32,7 @@ export * from './components/Text';
export * from './components/Heading';
// UI components
export * from './components/Avatar';
export * from './components/Button';
export * from './components/DataTable';
export * from './components/Icon';