Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2026 Specter Ops, Inc.
//
// Licensed under the Apache License, Version 2.0
// 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.
//
// SPDX-License-Identifier: Apache-2.0

import { cn } from '../utils';

export const accordionItemClasses = 'bg-[color:var(--accordion-fill)] rounded-lg mb-4';

export const accordionHeaderClasses = cn(
'flex flex-1 gap-1 items-center rounded-md text-main font-bold transition-all',
'hover:underline',
'focus:outline-none',
'focus-visible:focus-ring focus-visible:underline',
'[&[data-state=open]>svg]:rotate-180',
'p-4'
);

export const accordionContentClasses =
'overflow-hidden text-sm text-main transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down';
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,27 @@
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

import * as AccordionPrimitive from '@radix-ui/react-accordion';
import * as React from 'react';

import { cn } from '../utils';
import { accordionContentClasses, accordionHeaderClasses, accordionItemClasses } from './Accordion.styles';

const Accordion = AccordionPrimitive.Root;

const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ ...props }, ref) => (
<AccordionPrimitive.Item ref={ref} className='bg-[color:var(--accordion-fill)] rounded-lg mb-4' {...props} />
));
>(({ ...props }, ref) => <AccordionPrimitive.Item ref={ref} className={accordionItemClasses} {...props} />);
AccordionItem.displayName = 'AccordionItem';

const AccordionHeader = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className='flex'>
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
'flex flex-1 gap-1 items-center rounded-md text-main font-bold transition-all hover:underline focus:outline-none focus-visible:focus-ring focus-visible:underline [&[data-state=open]>svg]:rotate-180 p-4',
className
)}
{...props}>
<AccordionPrimitive.Trigger ref={ref} className={cn(accordionHeaderClasses, className)} {...props}>
{children}
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
Expand All @@ -50,10 +44,7 @@ const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className='overflow-hidden text-sm text-main transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down'
{...props}>
<AccordionPrimitive.Content ref={ref} className={accordionContentClasses} {...props}>
<div className={cn('p-4', className)}>{children}</div>
</AccordionPrimitive.Content>
));
Expand Down
44 changes: 44 additions & 0 deletions packages/javascript/doodle-ui/src/components/Alert/Alert.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2026 Specter Ops, Inc.
//
// Licensed under the Apache License, Version 2.0
// 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.
//
// SPDX-License-Identifier: Apache-2.0

import { cva } from 'class-variance-authority';

export const alertVariants = cva('flex text-sm w-full rounded px-4 py-2 ', {
variants: {
variant: {
// TODO: Update in https://specterops.atlassian.net/browse/BED-8069
// default: 'bg-status-indeterminate-fill text-contrast',
default: 'bg-status-indeterminate-fill text-neutral-1',
error: 'bg-status-error-fill text-status-error-text [&>div>svg]:text-status-error-main',
info: 'bg-status-info-fill text-status-info-text [&>div>svg]:text-status-info-main',
success: 'bg-status-success-fill text-status-success-text [&>div>svg]:text-status-success-main',
warning: 'bg-status-warning-fill text-status-warning-text [&>div>svg]:text-status-warning-main',
},
},
defaultVariants: {
variant: 'default',
},
});

export const alertBodyClasses = 'flex flex-col justify-center flex-grow py-2 gap-1';

export const alertTitleClasses = 'text-base/6 font-medium tracking-tight';

export const alertActionClasses =
'h-6 rounded-sm px-1 font-medium uppercase focus:outline-none focus-visible:focus-ring';

export const alertDismissClasses =
'ml-2 inline-flex h-6 items-center justify-center rounded-sm px-1 focus:outline-none focus-visible:focus-ring';
42 changes: 12 additions & 30 deletions packages/javascript/doodle-ui/src/components/Alert/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,17 @@

import { faClose } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { cva, type VariantProps } from 'class-variance-authority';
import { type VariantProps } from 'class-variance-authority';
import { forwardRef, HTMLAttributes, ReactNode } from 'react';
import { AppIcon } from '../../styleguide/components/AppIcons/AppIcons';
import { cn } from '../utils';

const alertVariants = cva('flex text-sm w-full rounded px-4 py-2 ', {
variants: {
variant: {
// TODO: Update in https://specterops.atlassian.net/browse/BED-8069
// default: 'bg-status-indeterminate-fill text-contrast',
default: 'bg-status-indeterminate-fill text-neutral-1',
error: 'bg-status-error-fill text-status-error-text [&>div>svg]:text-status-error-main',
info: 'bg-status-info-fill text-status-info-text [&>div>svg]:text-status-info-main',
success: 'bg-status-success-fill text-status-success-text [&>div>svg]:text-status-success-main',
warning: 'bg-status-warning-fill text-status-warning-text [&>div>svg]:text-status-warning-main',
},
},
defaultVariants: {
variant: 'default',
},
});
import {
alertActionClasses,
alertBodyClasses,
alertDismissClasses,
alertTitleClasses,
alertVariants,
} from './Alert.styles';

const iconMap = {
default: <AppIcon.Info />,
Expand All @@ -59,28 +49,20 @@ const Alert = forwardRef<
<div ref={ref} role='alert' className={cn(alertVariants({ variant }), className)} {...props}>
<div className='my-3 mr-2'>{iconMap[variant ?? 'default']}</div>

<div className='flex flex-col justify-center flex-grow py-2 gap-1'>
{title && <h5 className={cn('text-base/6 font-medium tracking-tight')}>{title}</h5>}
<div className={alertBodyClasses}>
{title && <h5 className={alertTitleClasses}>{title}</h5>}
{children}
</div>

<div className='flex mt-1'>
{action && (
<button
aria-label='Alert action'
className='h-6 rounded-sm px-1 font-medium uppercase focus:outline-none focus-visible:focus-ring'
onClick={action.onClick}
type='button'>
<button aria-label='Alert action' className={alertActionClasses} onClick={action.onClick} type='button'>
{action.label}
</button>
)}

{onClose && (
<button
aria-label='Dismiss alert'
className='ml-2 inline-flex h-6 items-center justify-center rounded-sm px-1 focus:outline-none focus-visible:focus-ring'
onClick={onClose}
type='button'>
<button aria-label='Dismiss alert' className={alertDismissClasses} onClick={onClose} type='button'>
<FontAwesomeIcon icon={faClose} className='mt-1 size-4' />
</button>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,22 @@
//
// SPDX-License-Identifier: Apache-2.0
import { cva } from 'class-variance-authority';
import { cn } from '../utils';

const badgeBaseClasses = [
const badgeBaseClasses = cn(
'inline-flex min-w-16 items-center justify-center',
'rounded px-2 py-1',
'text-base text-main',
];
'text-base text-main'
);

export const BadgeVariants = cva(badgeBaseClasses, {
variants: {
variant: {
fill: 'bg-[color:var(--badge-surface)]',
outline: [
outline: cn(
'border border-[color:var(--badge-accent)] bg-[color:var(--badge-grey-surface)]',
'[&>span>svg]:text-[color:var(--badge-accent)]',
],
'[&>span>svg]:text-[color:var(--badge-accent)]'
),
},
color: {
primary: '[--badge-surface:var(--badge-primary-surface)] [--badge-accent:var(--badge-primary-accent)]',
Expand Down Expand Up @@ -60,6 +61,6 @@ export const BadgeVariants = cva(badgeBaseClasses, {
},
});

export const badgeIconClasses = ['badge-icon flex shrink-0 items-center', '[&>svg]:h-3 [&>svg]:w-3'];
export const badgeIconClasses = cn('badge-icon flex shrink-0 items-center', '[&>svg]:h-3 [&>svg]:w-3');

export const badgeLabelClasses = 'badge-label translate-y-[1px]';
Original file line number Diff line number Diff line change
Expand Up @@ -16,38 +16,38 @@
import { cva } from 'class-variance-authority';
import { cn } from '../utils';

export const buttonBaseClasses = [
export const buttonBaseClasses = cn(
'inline-flex items-center justify-center whitespace-nowrap transition-colors',
'focus:outline-none focus-visible:focus-ring',
'disabled:text-[color:var(--button-disabled-text)] disabled:pointer-events-none',
'has-[svg]:gap-2 [&>svg]:shrink-0',
];
'has-[svg]:gap-2 [&>svg]:shrink-0'
);

const containedButtonGeometryClasses = 'rounded p-2 text-sm/none';

const containedButtonStateClasses = [
const containedButtonStateClasses = cn(
'hover:bg-secondary hover:text-inverse',
'focus-visible:bg-secondary focus-visible:text-inverse',
'active:bg-primary-variant active:text-inverse',
];
'active:bg-primary-variant active:text-inverse'
);

const containedPrimaryClasses = [
const containedPrimaryClasses = cn(
'border border-transparent bg-primary text-inverse',
...containedButtonStateClasses,
'disabled:bg-[color:var(--button-disabled-fill)]',
];
containedButtonStateClasses,
'disabled:bg-[color:var(--button-disabled-fill)]'
);

const containedSecondaryClasses = [
const containedSecondaryClasses = cn(
'border border-[color:var(--button-secondary-border)] bg-elevation-1 text-main',
...containedButtonStateClasses,
'disabled:bg-[color:var(--button-disabled-fill)]',
];
containedButtonStateClasses,
'disabled:bg-[color:var(--button-disabled-fill)]'
);

const containedTertiaryClasses = [
const containedTertiaryClasses = cn(
'border border-brand-orange bg-elevation-1 text-main',
...containedButtonStateClasses,
'disabled:border-[color:var(--button-disabled-fill)] disabled:bg-[color:var(--button-disabled-fill)]',
];
containedButtonStateClasses,
'disabled:border-[color:var(--button-disabled-fill)] disabled:bg-[color:var(--button-disabled-fill)]'
);

export const ButtonVariants = cva(buttonBaseClasses, {
variants: {
Expand All @@ -60,20 +60,20 @@ export const ButtonVariants = cva(buttonBaseClasses, {
/**
* @deprecated Use TextButton instead.
*/
transparent: [
transparent: cn(
'rounded-3xl border border-[color:var(--button-transparent-border)] bg-transparent text-main',
'hover:border-secondary hover:bg-secondary hover:text-[color:var(--button-highlight-text)] hover:no-underline',
'focus-visible:border-primary focus-visible:bg-secondary focus-visible:text-[color:var(--button-highlight-text)]',
],
'focus-visible:border-primary focus-visible:bg-secondary focus-visible:text-[color:var(--button-highlight-text)]'
),
// TODO - legacy, remove in BED-7635
/**
* @deprecated Use IconButton instead.
*/
icon: [
icon: cn(
'rounded-full bg-[color:var(--button-icon-fill)] text-common-dark shadow-outer-1 has-[svg]:p-2',
'hover:border-2 hover:border-primary',
'active:border-none',
],
'active:border-none'
),
},
// TODO - remove as the only usage of this is with variant="text"
/**
Expand Down Expand Up @@ -123,7 +123,7 @@ export const ButtonVariants = cva(buttonBaseClasses, {
});

export const TextButtonBaseClasses = cn(
...buttonBaseClasses,
buttonBaseClasses,
'rounded-3xl px-2 py-1 has-[svg]:px-1',
'active:text-[color:var(--button-text-active)]',
'hover:text-secondary',
Expand Down
23 changes: 23 additions & 0 deletions packages/javascript/doodle-ui/src/components/Card/Card.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2026 Specter Ops, Inc.
//
// Licensed under the Apache License, Version 2.0
// 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.
//
// SPDX-License-Identifier: Apache-2.0

export const cardClasses = 'rounded-lg text-main bg-neutral-1 border border-solid border-[color:var(--card-border)]';

export const cardHeaderClasses = 'flex flex-col space-y-1.5 p-3 pt-0 first:pt-3';

export const cardContentClasses = 'p-3 pt-0 first:pt-3';

export const cardFooterClasses = 'flex items-center p-3 pt-0 first:pt-3';
21 changes: 6 additions & 15 deletions packages/javascript/doodle-ui/src/components/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,19 @@
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

import * as React from 'react';
import { Typography } from '../Typography';
import { cn } from '../utils';
import { cardClasses, cardContentClasses, cardFooterClasses, cardHeaderClasses } from './Card.styles';

const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
'rounded-lg text-main bg-neutral-1 border border-solid border-[color:var(--card-border)]',
className
)}
{...props}
/>
<div ref={ref} className={cn(cardClasses, className)} {...props} />
));
Card.displayName = 'Card';

const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex flex-col space-y-1.5 p-3 pt-0 first:pt-3', className)} {...props} />
)
({ className, ...props }, ref) => <div ref={ref} className={cn(cardHeaderClasses, className)} {...props} />
);
CardHeader.displayName = 'CardHeader';

Expand All @@ -49,14 +42,12 @@ const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttribu
CardDescription.displayName = 'CardDescription';

const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => <div ref={ref} className={cn('p-3 pt-0 first:pt-3', className)} {...props} />
({ className, ...props }, ref) => <div ref={ref} className={cn(cardContentClasses, className)} {...props} />
);
CardContent.displayName = 'CardContent';

const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex items-center p-3 pt-0 first:pt-3', className)} {...props} />
)
({ className, ...props }, ref) => <div ref={ref} className={cn(cardFooterClasses, className)} {...props} />
);
CardFooter.displayName = 'CardFooter';

Expand Down
Loading
Loading