Skip to content
This repository has been archived by the owner on Feb 4, 2025. It is now read-only.

Commit

Permalink
Merge pull request #2 from oxen-io/ui_library
Browse files Browse the repository at this point in the history
Session UI Library
  • Loading branch information
Aerilym authored Jun 4, 2024
2 parents 684021c + d74437a commit 7c22520
Show file tree
Hide file tree
Showing 46 changed files with 5,514 additions and 27 deletions.
1 change: 1 addition & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"plugins": ["prettier-plugin-tailwindcss"],
"tabWidth": 2,
"useTabs": false,
"printWidth": 100,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"eslint": "8.57.0",
"husky": "^9.0.11",
"prettier": "3.2.5",
"prettier-plugin-tailwindcss": "^0.6.1",
"turbo": "1.13.3",
"typescript": "5.4.5"
},
Expand Down
10 changes: 10 additions & 0 deletions packages/testing/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/** @type {import("eslint").Linter.Config} */
module.exports = {
root: true,
extends: ['@session/eslint-config/react-internal.js'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
};
30 changes: 30 additions & 0 deletions packages/testing/ids.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export type DataTestIdTag = string;
export type GenericDataTestId<BaseDataTestId extends string> = `${BaseDataTestId}:${DataTestIdTag}`;

/**
* Creates a generic data test ID by combining a base data test ID and a test ID.
* @param baseDataTestId The base data test ID.
* @param testId The test ID.
* @returns The generic data test ID.
*/
export function genericCreateDataTestId<BaseDataTestId extends string>(
baseDataTestId: BaseDataTestId,
testId: string
): GenericDataTestId<BaseDataTestId> {
return `${baseDataTestId}:${testId}` as GenericDataTestId<BaseDataTestId>;
}

/**
* Checks if a given string is a valid data test ID.
* @param dataTestId The string to be checked.
* @returns A boolean indicating whether the string is a valid data test ID.
*/
export function genericIsDataTestId<BaseDataTestId extends string>(
dataTestId: string
): dataTestId is GenericDataTestId<BaseDataTestId> {
const splitId = dataTestId.split(':');
if (splitId.length !== 2 || splitId[0]?.length === 0 || splitId[0]?.length === 0) {
return false;
}
return true;
}
21 changes: 21 additions & 0 deletions packages/testing/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@session/testing",
"version": "0.0.0",
"private": true,
"exports": {
"./*": "./*.ts"
},
"scripts": {
"lint": "eslint ."
},
"devDependencies": {
"@session/typescript-config": "workspace:*",
"@session/eslint-config": "workspace:*"
},
"engines": {
"node": ">=22",
"pnpm": ">=9",
"yarn": "use pnpm",
"npm": "use pnpm"
}
}
10 changes: 10 additions & 0 deletions packages/testing/react.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { DataTestIdTag, GenericDataTestId } from './ids';

/**
* The generic testing props for a React component.
* @template DataTestId - The type of the data test ID.
*/
export type GenericTestingProps<DataTestId extends DataTestIdTag> = {
/** The data test ID for the component. */
'data-testid': GenericDataTestId<DataTestId>;
};
9 changes: 9 additions & 0 deletions packages/testing/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "@session/typescript-config/react-library.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "dist",
},
"include": ["./*"],
"exclude": ["node_modules", "turbo", "dist"]
}
10 changes: 10 additions & 0 deletions packages/ui/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/** @type {import("eslint").Linter.Config} */
module.exports = {
root: true,
extends: ['@session/eslint-config/library.js'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
};
17 changes: 17 additions & 0 deletions packages/ui/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "styles/global.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "../components",
"utils": "../../lib/utils"
}
}
52 changes: 52 additions & 0 deletions packages/ui/components/CopyToClipboardButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { forwardRef } from 'react';
import { BaseDataTestId, TestingProps } from '../data-test-ids';
import { ClipboardIcon } from '../icons/ClipboardIcon';
import { toast } from '../lib/sonner';
import { cn } from '../lib/utils';
import { Button } from './ui/button';

export interface CopyToClipboardButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
TestingProps<BaseDataTestId.Button> {
textToCopy: string;
copyToClipboardToastMessage: string;
onCopyComplete?: () => void;
}

/**
* Copies the specified text to the clipboard and displays a success toast message.
*
* @param textToCopy The text to be copied to the clipboard.
* @param copyToClipboardToastMessage The message to be displayed in the success toast.
*/
function copyToClipboard(
textToCopy: string,
copyToClipboardToastMessage: string,
onCopyComplete?: () => void
) {
navigator.clipboard.writeText(textToCopy);
toast.success(copyToClipboardToastMessage);
if (onCopyComplete) {
onCopyComplete();
}
}

const CopyToClipboardButton = forwardRef<HTMLButtonElement, CopyToClipboardButtonProps>(
({ textToCopy, copyToClipboardToastMessage, onCopyComplete, className, ...props }, ref) => {
return (
<Button
onClick={() => copyToClipboard(textToCopy, copyToClipboardToastMessage, onCopyComplete)}
variant="ghost"
className={cn(className, 'select-all p-0')}
ref={ref}
{...props}
data-testid={props['data-testid']}
>
<ClipboardIcon />
</Button>
);
}
);
CopyToClipboardButton.displayName = 'CopyToClipboardButton';

export { CopyToClipboardButton };
179 changes: 179 additions & 0 deletions packages/ui/components/Module.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { cva, type VariantProps } from 'class-variance-authority';
import { forwardRef, type HTMLAttributes } from 'react';
import { cn } from '../lib/utils';
import { Loading } from './loading';

export const outerModuleVariants = cva(
'rounded-[40px] transition-all ease-in-out bg-gradient-to-br from-[#7FB1AE] to-[#2A4337] bg-blend-lighten shadow-md p-px',
{
variants: {
variant: {
default: '',
hero: '',
},
size: {
default: 'col-span-1',
lg: 'col-span-1 sm:col-span-2',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);

const innerModuleVariants = cva(
'rounded-[40px] w-full h-full flex align-middle flex-col [&>span]:font-normal [&>*>span]:font-normal from-[#0a0a0a] to-[#081B14] bg-gradient-to-br',
{
variants: {
variant: {
default:
'[&>h3]:text-lg [&>*>h3]:text-lg [&>span]:text-3xl [&>*>span]:text-3xl [&>h3]:font-light [&>*>h3]:font-light bg-blend-lighten shadow-md gap-1',
hero: '[&>h3]:text-3xl [&>h3]:font-regular [&>span]:text-8xl [&>*>h3]:text-2xl [&>*>h3]:font-regular [&>*>span]:text-7xl gap-4 hover:brightness-125',
},
size: {
default: 'p-8',
lg: 'p-12',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);

export interface ModuleProps
extends HTMLAttributes<HTMLDivElement>,
VariantProps<typeof innerModuleVariants> {
loading?: boolean;
noPadding?: boolean;
}

const Module = forwardRef<HTMLDivElement, ModuleProps>(
({ className, variant, size, loading, children, noPadding, ...props }, ref) => {
return (
<div className={cn(outerModuleVariants({ size, variant, className }))}>
<div
className={cn(
innerModuleVariants({ size, variant, className }),
noPadding && 'p-0',
props.onClick && 'hover:bg-session-green hover:text-session-black hover:cursor-pointer'
)}
ref={ref}
{...props}
style={
variant === 'hero'
? {
background: 'url(/images/module-hero.png)',
backgroundPositionX: '35%',
backgroundPositionY: '43%',
backgroundSize: '150%',
}
: undefined
}
>
{loading ? <Loading /> : children}
</div>
</div>
);
}
);

Module.displayName = 'Module';

const moduleHeaderVariants = cva('w-full', {
variants: {
variant: {
default: '',
overlay: 'absolute z-1',
},
},
defaultVariants: {
variant: 'default',
},
});

export interface ModuleHeaderProps
extends HTMLAttributes<HTMLDivElement>,
VariantProps<typeof moduleHeaderVariants> {
loading?: boolean;
}

const ModuleHeader = forwardRef<HTMLDivElement, ModuleHeaderProps>(
({ className, variant, loading, children, ...props }, ref) => {
return (
<div className={cn(moduleHeaderVariants({ variant, className }))} ref={ref} {...props}>
{loading ? <Loading /> : children}
</div>
);
}
);
ModuleHeader.displayName = 'ModuleHeader';

const ModuleTitle = forwardRef<HTMLHeadingElement, HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn('font-atyp-display text-text-gradient leading-none tracking-tight', className)}
{...props}
/>
)
);
ModuleTitle.displayName = 'ModuleTitle';

const ModuleText = forwardRef<HTMLSpanElement, HTMLAttributes<HTMLSpanElement>>(
({ className, ...props }, ref) => (
<span ref={ref} className={cn('font-atyp-display text-text-gradient', className)} {...props} />
)
);
ModuleText.displayName = 'ModuleText';

const ModuleDescription = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => (
<p ref={ref} className={cn('text-muted-foreground', className)} {...props} />
)
);
ModuleDescription.displayName = 'ModuleDescription';

const moduleContentVariants = cva(
'flex flex-col align-middle justify-center w-full h-full text-center items-center',
{
variants: {
variant: {
default: '',
underlay: 'absolute inset-0 -z-1',
},
},
defaultVariants: {
variant: 'default',
},
}
);
export interface ModuleContentProps
extends HTMLAttributes<HTMLDivElement>,
VariantProps<typeof moduleContentVariants> {
loading?: boolean;
}
const ModuleContent = forwardRef<HTMLDivElement, ModuleContentProps>(
({ className, variant, loading, children, ...props }, ref) => {
return (
<div className={cn(moduleContentVariants({ variant, className }))} ref={ref} {...props}>
{loading ? <Loading /> : children}
</div>
);
}
);

ModuleContent.displayName = 'ModuleContent';

export {
Module,
ModuleContent,
ModuleDescription,
ModuleHeader,
ModuleText,
ModuleTitle,
innerModuleVariants as moduleVariants,
};
Loading

0 comments on commit 7c22520

Please sign in to comment.