Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: re-organize monorepo #32

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .vim/coc-settings.json

This file was deleted.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@commitlint/config-conventional": "^17.1.0",
"@eslint/js": "^9.16.0",
"@imccausl/eslint-config": "^3.0.1",
"@internal/typescript-config": "workspace:*",
"@mdx-js/react": "^2.1.3",
"@monodeploy/plugin-github": "^0.6.2",
"@storybook/addon-a11y": "^8.4.7",
Expand Down
3 changes: 3 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"description": "Accessible react component examples",
"homepage": "https://docs.a11y-react.com",
"license": "MIT",
"type": "module",
"main": "lib/cjs/index.js",
"module": "lib/esm/index.js",
"sideEffects": false,
Expand Down Expand Up @@ -48,13 +49,15 @@
"styled-components": "^5.3.5"
},
"devDependencies": {
"@internal/typescript-config": "workspace:*",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/styled-components": "^5.1.26",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-is": "^18.3.1",
"styled-components": "^5.3.5",
"typescript": "^5.7.2",
"vitest": "^2.1.8"
}
}
15 changes: 9 additions & 6 deletions packages/core/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
{
"extends": "../../tsconfig.json",
"include": ["src", "index.ts"],
"exclude": ["stories"],
"extends": "@internal/typescript-config/workspace.json",
"compilerOptions": {
"declaration": true,
"rootDir": "src",
"outDir": "lib",
"declarationDir": "lib"
}
},
"include": [
"src",
"index.ts"
],
"excluse": [
"stories"
]
}
18 changes: 18 additions & 0 deletions packages/headless/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "@a11y-react/headless",
"version": "0.0.0",
"type": "module",
"description": "Headless components for a11y-react",
"exports": {
"./Slots": "./src/components/Slots.ts"
},
"devDependencies": {
"@types/react": "^19",
"@types/react-dom": "^19",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"dependencies": {
"clsx": "^2.1.1"
}
}
63 changes: 63 additions & 0 deletions packages/headless/src/components/ActionMenu/Menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useActionMenuContext } from './context'

type LinkItemProps = React.AnchorHTMLAttributes<HTMLAnchorElement>
type ButtonItemProps = React.AnchorHTMLAttributes<HTMLButtonElement> & {
type?: 'button' | 'submit'
}

export const LinkItem: React.FC<LinkItemProps & React.PropsWithChildren> = ({
href,
id,
children,
...restProps
}) => {
return (
<li
role="presentation"
className="block px-4 py-2 text-sm text-gray-700"
>
<a href={href} role="menuitem" tabIndex={-1} id={id} {...restProps}>
{children}
</a>
</li>
)
}

export const ButtonItem: React.FC<
ButtonItemProps & React.PropsWithChildren
> = ({ id, children, type = 'button', ...restProps }) => {
return (
<li
role="presentation"
className="block px-4 py-2 text-sm text-gray-700"
>
<button
role="menuitem"
type={type}
tabIndex={-1}
id={id}
{...restProps}
>
{children}
</button>
</li>
)
}

export const Menu: React.FC<React.PropsWithChildren> = ({ children }) => {
const { isOpen } = useActionMenuContext()

if (!isOpen) return null

return (
<ul
className={`absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none`}
role="menu"
aria-orientation="vertical"
aria-labelledby="user-menu-button"
tabIndex={-1}
>
{children}
</ul>
)
}
26 changes: 26 additions & 0 deletions packages/headless/src/components/ActionMenu/Trigger.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Children, cloneElement, isValidElement } from 'react'

import { useActionMenuContext } from './context'

export const Trigger: React.FC<React.PropsWithChildren> = ({ children }) => {
const { isOpen, setIsOpen } = useActionMenuContext()

if (
!children ||
!isValidElement(children) ||
Children.count(children) > 1
) {
throw new Error(
`The Trigger component must have exactly one child element`,
)
}

const handleOnClick = () => {
setIsOpen(!isOpen)
}

return cloneElement(children as React.ReactElement, {
onClick: handleOnClick,
type: 'button',
})
}
20 changes: 20 additions & 0 deletions packages/headless/src/components/ActionMenu/context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { createContext, useContext } from 'react'

type ActionMenuContextType = {
isOpen: boolean
setIsOpen: (isOpen: boolean) => void
} | null

export const ActionMenuContext = createContext<ActionMenuContextType>(null)

export const useActionMenuContext = () => {
const context = useContext(ActionMenuContext)

if (!context) {
throw new Error(
`ActionMenu components cannot be rendered outside the ActionMenu component`,
)
}

return context
}
29 changes: 29 additions & 0 deletions packages/headless/src/components/ActionMenu/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React, { useState } from 'react'

import { ButtonItem, LinkItem, Menu } from './Menu'
import { Trigger } from './Trigger'
import { ActionMenuContext } from './context'

interface ActionMenu {
Link: typeof LinkItem
Button: typeof ButtonItem
Menu: typeof Menu
Trigger: typeof Trigger
}

const ActionMenu: ActionMenu = ({ children }: React.PropsWithChildren) => {
const [isOpen, setIsOpen] = useState(false)

return (
<ActionMenuContext.Provider value={{ isOpen, setIsOpen }}>
{children}
</ActionMenuContext.Provider>
)
}

ActionMenu.Link = LinkItem
ActionMenu.Button = ButtonItem
ActionMenu.Menu = Menu
ActionMenu.Trigger = Trigger

export { ActionMenu }
71 changes: 71 additions & 0 deletions packages/headless/src/components/Alert/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {
CheckCircleIcon,
ExclamationCircleIcon,
ExclamationTriangleIcon,
InformationCircleIcon,
} from '@heroicons/react/16/solid'
import { type FC, type ReactNode } from 'react'

type AlertVariant = (typeof Variant)[keyof typeof Variant]
type HeadingLevel = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
type AlertRole = 'status' | 'alert'

type AlertProps = {
variant?: AlertVariant
children?: ReactNode
heading: string
role?: AlertRole
headingLevel?: HeadingLevel
}

const Variant = {
WARNING: 'warning',
ERROR: 'error',
SUCCESS: 'success',
INFO: 'info',
} as const

const VariantStyles = {
[Variant.WARNING]: 'bg-yellow-50 text-yellow-700 border-yellow-700',
[Variant.ERROR]: 'bg-red-50 text-red-700 border-red-700',
[Variant.SUCCESS]: 'bg-green-50 text-green-700 border-green-700',
[Variant.INFO]: 'bg-blue-50 text-blue-700 border-blue-700',
} as const

const VariantIcons = {
[Variant.WARNING]: ExclamationTriangleIcon,
[Variant.ERROR]: ExclamationCircleIcon,
[Variant.SUCCESS]: CheckCircleIcon,
[Variant.INFO]: InformationCircleIcon,
} as const

export const Alert: FC<AlertProps> = ({
variant = 'warning',
children,
heading,
role = 'status',
headingLevel = 'h2',
}) => {
const AlertIcon = VariantIcons[variant]
const HeadingLevel = headingLevel

return (
<div
role={role}
className={`rounded-l-md rounded-r-lg border-l-8 p-4 ${VariantStyles[variant]} grid grid-cols-[auto,1fr] grid-rows-[auto,1fr] gap-x-3 text-sm text-opacity-90`}
>
<div className="row-span-1 h-6 w-6 self-center">
<AlertIcon
height="20px"
width="20px"
className="m-0"
aria-label={`${variant} message`}
/>
</div>
<HeadingLevel className="font-semibold">{heading}</HeadingLevel>
{children && (
<div className="col-start-2 mt-2 font-normal">{children}</div>
)}
</div>
)
}
42 changes: 42 additions & 0 deletions packages/headless/src/components/Badge/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export const Variant = {
DEFAULT: 'default',
PRIMARY: 'primary',
SUCCESS: 'success',
WARNING: 'warning',
DANGER: 'danger',
INFO: 'info',
PURPLE: 'purple',
PINK: 'pink',
} as const

const variantsToClassNames = {
[Variant.DEFAULT]: 'bg-gray-50 text-gray-600 ring-gray-500/10',
[Variant.PRIMARY]: 'bg-indigo-50 text-indigo-700 ring-indigo-700/10',
[Variant.SUCCESS]: 'bg-green-50 text-green-700 ring-green-600/10',
[Variant.WARNING]: 'bg-yellow-50 text-yellow-800 ring-yellow-600/10',
[Variant.DANGER]: 'bg-red-50 text-red-700 ring-red-600/10',
[Variant.INFO]: 'bg-blue-50 text-blue-700 ring-blue-700/10',
[Variant.PURPLE]: 'bg-purple-50 text-purple-700 ring-purple-700/10',
[Variant.PINK]: 'bg-pink-50 text-pink-700 ring-pink-700/10',
} as const

const baseStyles =
'inline-flex items-center rounded-md h-5 px-2 py-2 uppercase text-[12px] font-medium ring-1 ring-inset'

type BadgeProps = {
variant?: (typeof Variant)[keyof typeof Variant]
}
export const Badge: React.FC<React.PropsWithChildren & BadgeProps> = ({
children,
variant = Variant.DEFAULT,
}) => {
return (
<span
className={`${baseStyles} ${
variantsToClassNames[variant ?? Variant.DEFAULT]
}`}
>
{children}
</span>
)
}
24 changes: 24 additions & 0 deletions packages/headless/src/components/Button/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export const Spinner: React.FC<React.SVGAttributes<SVGElement>> = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
{...props}
className="-ml-1 animate-spin"
aria-hidden="true"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
)
Loading
Loading