diff --git a/.github/workflows/nodejsci.yml b/.github/workflows/nodejsci.yml index ec65a70..7d7b3b8 100644 --- a/.github/workflows/nodejsci.yml +++ b/.github/workflows/nodejsci.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ main ] +permissions: + contents: read + jobs: build: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9742876..05a8453 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,3 +1,5 @@ +name: Publish to npm + on: push: branches: @@ -26,13 +28,5 @@ jobs: - name: 🚀 Publish Package run: npm publish - - name: 🏷️ Create and Push Git Tag - run: | - VERSION=$(node -p "require('./package.json').version") - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag "v$VERSION" - git push origin "v$VERSION" - - name: ✅ Publish Complete run: echo "Package published and tagged successfully." \ No newline at end of file diff --git a/README.md b/README.md index aa0e2e6..7563aef 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ import { DesignSystem } from '@maskingtech/designsystem'; import '@maskingtech/designsystem/style.css'; - {/* elements and components here */} + {/* layout, elements and components here */} ``` -The list of elements, components, and customization options can be found in the [documentation](docs/README.md). +The list of layouts, elements, components, and customization options can be found in the [documentation](docs/README.md). diff --git a/docs/COMPONENTS.md b/docs/COMPONENTS.md index bd85289..70d5c94 100644 --- a/docs/COMPONENTS.md +++ b/docs/COMPONENTS.md @@ -2,64 +2,10 @@ The following components are available: -* **Dropdown** - Simple selection dropdown. * **Tabs** - Tabbed navigation component. All details are described per component below. -## Dropdown - -Simple selection dropdown (custom UI) that accepts a `Map` of options. - -Properties: - -- **options** - `Map` (required) -- **selected** - string (optional, key of the selected option) -- **onChange** - `(oldKey: string, newKey: string) => void` (optional) - -Example: - -```tsx -import { Dropdown } from '@maskingtech/designsystem'; - -const opts = new Map([['a','A'],['b','B']]); -console.log(o,n)} /> -``` - -Customization options (selector: `.dropdown`): - -- `--background-color` (default: `transparent`) -- `--background-color-options` (default: `var(--color-primary-background)`) -- `--background-color-options-hover` (default: `var(--color-background-hover)`) -- `--arrow-color` (default: `var(--color-alert-foreground)`) -- `--border-color` (default: `var(--color-border)`) - -## ModalManager - -Manages multiple models in a single component. - -Properties: - -- **selectedId** - string (optional) -- **children** - one or more `Modal` elements (required) - -Example: - -```tsx -import { Modal, ModalManager } from '@maskingtech/designsystem'; - - - - First modal message. - - - Second modal message. - - -``` - -This component does not have customization options. - ## Tabs Tabs provide a tabbed navigation. Use `Tab` elements as children of `Tabs`. diff --git a/docs/CUSTOMIZATION.md b/docs/CUSTOMIZATION.md index 6a14514..2bb0de7 100644 --- a/docs/CUSTOMIZATION.md +++ b/docs/CUSTOMIZATION.md @@ -1,6 +1,6 @@ # Customization | Masking Technology Design System -Customizations can be made globally and per element / component. +Customizations can be made globally and per layout, element / component. ```css /* customizations.css */ @@ -10,7 +10,7 @@ Customizations can be made globally and per element / component. /* Global options */ --font-family: 'Inter', sans-serif; - /* Element / component specific options (by selector) */ + /* Layout, element / component specific options (by selector) */ .button { --type-primary-background-color: #e06666; @@ -20,7 +20,7 @@ Customizations can be made globally and per element / component. } ``` -All global options can be found below. Specific selectors and options can be found in the [elements](./ELEMENTS.md) and [components](./COMPONENTS.md) documentation. +All global options can be found below. Specific selectors and options can be found in the [layouts](./LAYOUTS.md), [elements](./ELEMENTS.md) and [components](./COMPONENTS.md) documentation. ## Activation @@ -33,7 +33,7 @@ import '/path/to/customizations.css'; ## Global options -### Font +### Font properties - `--font-family` (default: `sans-serif`) - `--font-size` (default: `16px`) @@ -44,7 +44,7 @@ import '/path/to/customizations.css'; - `--font-weight-semi-bold` (default: `600`) - `--font-weight-bold` (default: `700`) -### Color +### Color definitions - `--color-background` (default: `transparent`) - `--color-background-hover` (default: `#efefef`) @@ -69,7 +69,31 @@ import '/path/to/customizations.css'; - `--color-success-background` (default: `#efefef`) - `--color-success-foreground` (default: `#666666`) -### Size +### Element properties -- `--margin-container` (default: `0 0 1.2em 0`) -- `--width-border` (default: `0.05em`) +- `--element-border-small` (default: `0.1em`) +- `--element-border-medium` (default: `0.2em`) +- `--element-border-large` (default: `0.3em`) +- `--element-font-small` (default: `0.9em`) +- `--element-font-medium` (default: `1em`) +- `--element-font-large` (default: `1.2em`) + +### Container properties + +- `--container-gap-small` (default: `0.4em`) +- `--container-gap-medium` (default: `1em`) +- `--container-gap-large` (default: `2em`) +- `--container-padding-small` (default: `1em`) +- `--container-padding-medium` (default: `1.5em`) +- `--container-padding-large` (default: `2em`) + +### Input properties + +- `--input-border-style` (default: `solid`) +- `--input-border-color` (default: `var(--color-border)`) +- `--input-border-small` (default: `0.05em`) +- `--input-border-medium` (default: `0.075em`) +- `--input-border-large` (default: `0.1em`) +- `--input-padding-small` (default: `0.4em`) +- `--input-padding-medium` (default: `0.8em`) +- `--input-padding-large` (default: `1.2em`) diff --git a/docs/ELEMENTS.md b/docs/ELEMENTS.md index 361175b..6827db3 100644 --- a/docs/ELEMENTS.md +++ b/docs/ELEMENTS.md @@ -21,8 +21,8 @@ A bordered container element. Properties: - **type** - `normal`| `dashed` | `dotted` (optional, default `normal`) -- **padding** - `large` | `medium` | `small` | `none` (optional, default `large`) -- **size** - `large` | `medium` | `small` (optional, default `large`) +- **padding** - `none` | `small` | `medium` | `large` (optional, default `large`) +- **size** - `small` | `medium` | `large` (optional, default `large`) Example: @@ -39,12 +39,12 @@ Customization options (selector: `.border`): - `--border-radius` (default: `0`) - `--color` (default: `var(--color-border)`) - `--margin` (default: `0`) -- `--padding-large` (default: `2em`) -- `--padding-medium` (default: `1.5em`) -- `--padding-small` (default: `1em`) -- `--size-large` (default: `0.3em`) -- `--size-medium` (default: `0.2em`) -- `--size-small` (default: `0.1em`) +- `--padding-large` (default: `var(--container-padding-small)`) +- `--padding-medium` (default: `var(--container-padding-medium)`) +- `--padding-small` (default: `var(--container-padding-large)`) +- `--size-large` (default: `var(--element-border-small)`) +- `--size-medium` (default: `var(--element-border-medium)`) +- `--size-small` (default: `var(--element-border-large)`) ### Panel @@ -52,7 +52,7 @@ A versatile container for contextual content (alerts, messages, etc.). Properties: -- **padding** - `large` | `medium` | `small` (optional, default `large`) +- **padding** - `small` | `medium` | `large` (optional, default `large`) - **type** - `normal` | `alert` | `warning` | `success` | `error` | `transparent` (optional, default `normal`) - **children** - ReactNode (optional) @@ -67,10 +67,10 @@ import { Panel } from '@maskingtech/designsystem'; Customization options (selector: `.panel`): - `--border-radius` (default: `0`) -- `--margin` (default: `0 0 1.5em 0`) -- `--padding-large` (default: `2em`) -- `--padding-medium` (default: `1.5em`) -- `--padding-small` (default: `1em`) +- `--margin` (default: `0`) +- `--padding-small` (default: `--container-padding-small`) +- `--padding-medium` (default: `--container-padding-medium`) +- `--padding-large` (default: `--container-padding-large`) - `--type-normal-background-color` (default: `var(--color-secondary-background)`) - `--type-normal-foreground-color` (default: `var(--color-primary-foreground)`) - `--type-alert-background-color` (default: `var(--color-alert-background)`) @@ -114,7 +114,7 @@ A grid layout with preset column layouts and gaps. Properties: - **layout** - `two-columns` | `three-columns` | `four-columns` (required) -- **gap** - `large` | `medium` | `small` | `none` (optional, default `medium`) +- **gap** - `none` | `small` | `medium` | `large` (optional, default `medium`) - **children** - ReactNode Example: @@ -131,9 +131,9 @@ import { Grid } from '@maskingtech/designsystem'; Customization options (selector: `.grid`): -- `--gap-small` (default: `0.5em`) -- `--gap-medium` (default: `1em`) -- `--gap-large` (default: `2em`) +- `--gap-small` (default: `var(--container-padding-small)`) +- `--gap-medium` (default: `var(--container-padding-medium)`) +- `--gap-large` (default: `var(--container-padding-large)`) ### Column @@ -143,7 +143,7 @@ Properties: - **alignX** - `left` | `center` | `right` | `stretch` (optional, default `left`) - **alignY** - `top` | `center` | `bottom` | `justify` (optional, default `top`) -- **gap** - `large` | `medium` | `small` | `none` (optional, default `medium`) +- **gap** - `none` | `small` | `medium` | `large` (optional, default `medium`) - **wrap** - `wrap` | `nowrap` (optional, default `nowrap`) - **children** - ReactNode @@ -160,9 +160,9 @@ import { Column } from '@maskingtech/designsystem'; Customization options (selector: `.column`): -- `--gap-small` (default: `0.4em`) -- `--gap-medium` (default: `1em`) -- `--gap-large` (default: `2em`) +- `--gap-small` (default: `var(--container-gap-small)`) +- `--gap-medium` (default: `var(--container-gap-medium)`) +- `--gap-large` (default: `var(--container-gap-large)`) ### Row @@ -172,7 +172,7 @@ Properties: - **alignX** - `left` | `center` | `right` | `justify` (optional, default `left`) - **alignY** - `top` | `center` | `bottom` | `stretch` (optional, default `top`) -- **gap** - `large` | `medium` | `small` | `none` (optional, default `medium`) +- **gap** - `none` | `small` | `medium` | `large` (optional, default `medium`) - **wrap** - `wrap` | `nowrap` (optional, default `nowrap`) - **children** - ReactNode @@ -189,9 +189,9 @@ import { Row } from '@maskingtech/designsystem'; Customization options (selector: `.row`): -- `--gap-small` (default: `0.4em`) -- `--gap-medium` (default: `1em`) -- `--gap-large` (default: `2em`) +- `--gap-small` (default: `var(--container-gap-small)`) +- `--gap-medium` (default: `var(--container-gap-medium)`) +- `--gap-large` (default: `var(--container-gap-large)`) ### Cell @@ -218,7 +218,7 @@ A table layout with a head, body and footer containing rows and cells. `Table` properties: -- **padding** - `large` | `medium` | `small` (optional, default `medium`) +- **padding** - `small` | `medium` | `large` (optional, default `medium`) - **children** - `ReactElement` (required) `Table.Header` properties: @@ -227,7 +227,7 @@ A table layout with a head, body and footer containing rows and cells. `Table.Body` properties: -- **border** - `large` | `medium` | `small` | `name` (optional, default `medium`) +- **border** - `none` | `small` | `medium` | `large` (optional, default `medium`) - **children** - `ReactElement` (required) `Table.Footer` properties: @@ -282,9 +282,9 @@ Customization options (selector: `.table`): - `--header-forground-color` (default: `var(--color-primary-foreground)`) - `--row-odd-color` (default: `var(--color-primary-background)`) - `--row-even-color` (default: `var(--color-secondary-background)`) -- `--footer-border-small` (default: `0.1rem`) -- `--footer-border-medium` (default: `0.2rem`) -- `--footer-border-large` (default: `0.3rem`) +- `--footer-border-small` (default: `var(--element-border-small)`) +- `--footer-border-medium` (default: `var(--element-border-medium)`) +- `--footer-border-large` (default: `var(--element-border-large)`) - `--footer-border-color` (default: `var(--color-border)`) - `--footer-forground-color` (default: `var(--color-primary-foreground)`) @@ -296,7 +296,7 @@ Heading element that maps `size` to `h1`/`h2`/`h3`. Properties: -- **size** - `large` | `medium` | `small` (optional, default `large`) +- **size** - `small` | `medium` | `large` (optional, default `large`) - **children** - ReactNode Example: @@ -310,9 +310,9 @@ import { Title } from '@maskingtech/designsystem'; Customization options (selector: `.title`): - `--margin` (default: `0 0 0.8em 0`) -- `--size-large` (default: `2em`) -- `--size-medium` (default: `1.5em`) - `--size-small` (default: `1em`) +- `--size-medium` (default: `1.5em`) +- `--size-large` (default: `2em`) ### Paragraph @@ -320,7 +320,7 @@ Styled paragraph element. Properties: -- **size** - `large` | `medium` | `small` (optional, default `medium`) +- **size** - `small` | `medium` | `large` (optional, default `medium`) - **children** - ReactNode Example: @@ -335,9 +335,9 @@ Customization options (selector: `.paragraph`): - `--margin` (default: `var(--margin-container)`) - `--line-height` (default: `1.5em`) -- `--size-large` (default: `1.5em`) -- `--size-medium` (default: `1em`) -- `--size-small` (default: `0.75em`) +- `--size-small` (default: `var(--element-font-small)`) +- `--size-medium` (default: `var(--element-font-medium)`) +- `--size-large` (default: `var(--element-font-large)`) ### Text @@ -347,7 +347,7 @@ Properties: - **value** - string (required) - **type** - `primary` | `secondary` (optional, default `primary`) -- **size** - `large` | `medium` | `small` (optional, default `medium`) +- **size** - `small` | `medium` | `large` (optional, default `medium`) - **weight** - `light` | `normal` | `bold` (optional, default `normal`) - **wrap** - `none` | `normal` | `break-word` (optional, default `none`) @@ -363,9 +363,9 @@ Customization options (selector: `.text`): - `--primary-color` (default: `var(--color-primary-foreground)`) - `--secondary-color` (default: `var(--color-secondary-foreground)`) -- `--size-large` (default: `1.2em`) -- `--size-medium` (default: `1.0em`) -- `--size-small` (default: `0.9em`) +- `--size-small` (default: `var(--element-font-small)`) +- `--size-medium` (default: `var(--element-font-medium)`) +- `--size-large` (default: `var(--element-font-large)`) - weights are defined at global level. ## Interaction elements @@ -377,7 +377,8 @@ A clickable input element styled as a button. Properties: - **type** - `submit` | `primary` | `secondary` | `disabled` (optional, default `primary`) -- **size** - `large` | `medium` | `small` (optional, default `medium`) +- **border** - `none` | `small` | `medium` | `large` (optional, default `small`) +- **size** - `small` | `medium` | `large` (optional, default `medium`) - **text** - string (required) - **onClick** - `() => void` (optional) @@ -393,8 +394,11 @@ Customization options (selector: `.button`): - `--border-radius` (default: `2em`) - `--margin` (default: `0`) -- `--border-style` (default: `solid`) -- `--border-color` (default: `var(--color-border)`) +- `--border-style` (default: `var(--input-border-style)`) +- `--border-color-disabled` (default: `var(--input-border-color)`) +- `--border-small` (default: `var(--input-border-small)`) +- `--border-medium` (default: `var(--input-border-medium)`) +- `--border-large` (default: `var(--input-border-large)`) - `--border-width` (default: `var(--width-border)`) - `--type-primary-background-color` (default: `var(--color-primary-action-background)`) - `--type-primary-foreground-color` (default: `var(--color-primary-action-foreground)`) @@ -413,7 +417,7 @@ A wrapper element that handles clicks and optional padding. Properties: -- **padding** - `large` | `medium` | `small` | `none` (optional, default `none`) +- **padding** - `none` | `small` | `medium` | `large` (optional, default `none`) - **onClick** - `() => void` (optional) - **children** - ReactNode (optional) @@ -428,10 +432,9 @@ import { ClickArea } from '@maskingtech/designsystem'; Customization options (selector: `.clickarea`): - `--margin` (default: `0`) -- `--padding-large` (default: `2em`) -- `--padding-medium` (default: `1.5em`) -- `--padding-small` (default: `1em`) -- `--padding-none` (default: `0em`) +- `--padding-small` (default: `var(--container-padding-small)`) +- `--padding-medium` (default: `var(--container-padding-medium)`) +- `--padding-large` (default: `var(--container-padding-large)`) ### Link @@ -534,7 +537,8 @@ Properties: - **value** - string (optional) - **title** - string (optional) - **type** - `datetime` | `date` | `time` | `month` | `week` (optional, default `datetime`) -- **size** - `large` | `medium` | `small` (optional, default `medium`) +- **border** - `small` | `medium` | `large` (optional, default `small`) +- **size** - `small` | `medium` | `large` (optional, default `medium`) - **required** - boolean (optional) - **onChange** - `ChangeEventHandler` (optional) @@ -549,12 +553,14 @@ import { TextBox } from '@maskingtech/designsystem'; Customization options (selector: `.datetime`): - `--margin` (default: `0`) -- `--size-small` (default: `0.4em`) -- `--size-medium` (default: `0.8em`) -- `--size-large` (default: `1.2em`) -- `--border-style` (default: `solid`) -- `--border-color` (default: `var(--color-border)`) -- `--border-width` (default: `var(--width-border)`) +- `--border-color` (default: `var(--input-border-color)`) +- `--border-style` (default: `var(--input-border-style)`) +- `--border-small` (default: `var(--input-border-small)`) +- `--border-medium` (default: `var(--input-border-medium)`) +- `--border-large` (default: `var(--input-border-large)`) +- `--size-small` (default: `var(--input-padding-large)`) +- `--size-medium` (default: `var(--input-padding-large)`) +- `--size-large` (default: `var(--input-padding-large)`) ### Select @@ -566,7 +572,8 @@ Properties: - **options** - `Map` (required) - **defaultValue** - string (optional) - **value** - string (optional) -- **size** - `large` | `medium` | `small` (optional, default `medium`) +- **border** - `small` | `medium` | `large` (optional, default `small`) +- **size** - `small` | `medium` | `large` (optional, default `medium`) - **onChange** - `ChangeEventHandler` (optional) Example: @@ -582,12 +589,14 @@ const options = new Map([['a', 'Option A'], ['b', 'Option B']]); Customization options (selector: `.select`): - `--margin` (default: `0`) -- `--size-small` (default: `0.4em`) -- `--size-medium` (default: `0.8em`) -- `--size-large` (default: `1.2em`) -- `--border-style` (default: `solid`) -- `--border-color` (default: `var(--color-border)`) -- `--border-width` (default: `var(--width-border)`) +- `--border-color` (default: `var(--input-border-color)`) +- `--border-style` (default: `var(--input-border-style)`) +- `--border-small` (default: `var(--input-border-small)`) +- `--border-medium` (default: `var(--input-border-medium)`) +- `--border-large` (default: `var(--input-border-large)`) +- `--size-small` (default: `var(--input-padding-large)`) +- `--size-medium` (default: `var(--input-padding-large)`) +- `--size-large` (default: `var(--input-padding-large)`) ### TextArea @@ -599,7 +608,8 @@ Properties: - **placeholder** - string (optional) - **defaultValue** - string (optional) - **value** - string (optional) -- **size** - `large` | `medium` | `small` (optional, default `medium`) +- **border** - `small` | `medium` | `large` (optional, default `small`) +- **size** - `small` | `medium` | `large` (optional, default `medium`) - **rows** - number (optional) - **limit** - number (optional) - **onChange** - `ChangeEventHandler` (optional) @@ -615,12 +625,14 @@ import { TextArea } from '@maskingtech/designsystem'; Customization options (selector: `.textarea`): - `--margin` (default: `0`) -- `--size-small` (default: `0.4em`) -- `--size-medium` (default: `0.8em`) -- `--size-large` (default: `1.2em`) -- `--border-style` (default: `solid`) -- `--border-color` (default: `var(--color-border)`) -- `--border-width` (default: `var(--width-border)`) +- `--border-color` (default: `var(--input-border-color)`) +- `--border-style` (default: `var(--input-border-style)`) +- `--border-small` (default: `var(--input-border-small)`) +- `--border-medium` (default: `var(--input-border-medium)`) +- `--border-large` (default: `var(--input-border-large)`) +- `--size-small` (default: `var(--input-padding-large)`) +- `--size-medium` (default: `var(--input-padding-large)`) +- `--size-large` (default: `var(--input-padding-large)`) ### TextBox @@ -636,7 +648,8 @@ Properties: - **pattern** - string (optional) - **title** - string (optional) - **type** - `email` | `number` | `password` | `search` | `text` | `tel` | `url` (optional, default `text`) -- **size** - `large` | `medium` | `small` (optional, default `medium`) +- **border** - `small` | `medium` | `large` (optional, default `small`) +- **size** - `small` | `medium` | `large` (optional, default `medium`) - **required** - boolean (optional) - **onChange** - `ChangeEventHandler` (optional) @@ -651,12 +664,14 @@ import { TextBox } from '@maskingtech/designsystem'; Customization options (selector: `.textbox`): - `--margin` (default: `0`) -- `--size-small` (default: `0.4em`) -- `--size-medium` (default: `0.8em`) -- `--size-large` (default: `1.2em`) -- `--border-style` (default: `solid`) -- `--border-color` (default: `var(--color-border)`) -- `--border-width` (default: `var(--width-border)`) +- `--border-color` (default: `var(--input-border-color)`) +- `--border-style` (default: `var(--input-border-style)`) +- `--border-small` (default: `var(--input-border-small)`) +- `--border-medium` (default: `var(--input-border-medium)`) +- `--border-large` (default: `var(--input-border-large)`) +- `--size-small` (default: `var(--input-padding-large)`) +- `--size-medium` (default: `var(--input-padding-large)`) +- `--size-large` (default: `var(--input-padding-large)`) ### CheckBox @@ -780,9 +795,9 @@ import { Ruler } from '@maskingtech/designsystem'; Customization options (selector: `.ruler`): - `--color` (default: `var(--color-border)`) -- `--size-small` (default: `0.1rem`) -- `--size-medium` (default: `0.2rem`) -- `--size-large` (default: `0.3rem`) +- `--size-large` (default: `var(--element-border-small)`) +- `--size-medium` (default: `var(--element-border-medium)`) +- `--size-small` (default: `var(--element-border-large)`) ### Spinner diff --git a/docs/LAYOUTS.md b/docs/LAYOUTS.md new file mode 100644 index 0000000..675f471 --- /dev/null +++ b/docs/LAYOUTS.md @@ -0,0 +1,77 @@ +# Layouts | Masking Technology Design System + +The following responsive layouts are available: + +* **Centered** - Simple layout that centers its content. +* **Sidebar** - Two panel layout with a sidebar on the left and the main content on the right. + +All details are described per component below. + +## Centered + +Simple layout that vertically and horizontally centers its content. + +Properties: + +- **children** - ReactNode (required) + +Example: + +```tsx +import { CenteredLayout } from '@maskingtech/designsystem'; + + + This is centered. + +``` + +Responsive behaviour: + +- **width < 500px** - The side padding gets halved. + +Customization options (selector: `.layout-centered`): + +- `--content-max-width` (default: `696px`) + +## Sidebar + +Horizontally centerred, two panel layout with a sidebar on the left and the main content on the right, and an optional header and footer for the response view. + +Properties: + +- **sidebar** - ReactNode (required) +- **children** - ReactNode (required) +- **header** - ReactNode (optional) +- **footer** - ReactNode (optional) + +Example: + +```tsx +import { SidebarLayout } from '@maskingtech/designsystem'; + +const sidebar = <> + + +>; + +const header = ; +const footer = ; + + + Main content. + +``` + +Responsive behaviour: + +- **width < 960px** - The sidebar is hidden, the header and footer shown. +- **width < 500px** - The side padding gets halved. + +Customization options (selector: `.layout-sidebar`): + +- `--header-height` (default: `3.4em`) +- `--footer-height` (default: `4.4em`) +- `--sidebar-width` (default: `260px`) +- `--content-width` (default: `640px`) +- `--modal-width` (default: `700px`) +- `--content-gap` (default: `2em`) diff --git a/docs/README.md b/docs/README.md index 10748f4..a228b55 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,4 +5,5 @@ Table of Contents: 1. [**Getting Started**](./GETTING_STARTED.md) - Installation and activation. 1. [**Elements**](./ELEMENTS.md) - The available elements. 1. [**Components**](./COMPONENTS.md) - The available components. +1. [**Layouts**](./LAYOUTS.md) - The available layouts. 1. [**Customization**](./CUSTOMIZATION.md) - Customization options. diff --git a/package-lock.json b/package-lock.json index 2fcb4c0..eeabd5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@maskingtech/designsystem", - "version": "0.0.3", + "version": "0.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@maskingtech/designsystem", - "version": "0.0.3", + "version": "0.0.4", "devDependencies": { "@eslint/js": "9.39.1", "@types/node": "25.0.1", diff --git a/package.json b/package.json index fdf5786..fe577c7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@maskingtech/designsystem", "private": false, - "version": "0.0.3", + "version": "0.0.4", "type": "module", "repository": { "url": "https://github.com/MaskingTechnology/designsystem" diff --git a/src/DesignSystem.css b/src/DesignSystem.css index f9c844e..df6e2ea 100644 --- a/src/DesignSystem.css +++ b/src/DesignSystem.css @@ -42,9 +42,32 @@ --color-success-background: #efefef; --color-success-foreground: #666666; - --margin-container: 0 0 1.2em 0; + --element-border-small: 0.1em; + --element-border-medium: 0.2em; + --element-border-large: 0.3em; - --width-border: 0.05em; + --element-font-small: 0.9em; + --element-font-medium: 1em; + --element-font-large: 1.2em; + + --container-gap-small: 0.4em; + --container-gap-medium: 1em; + --container-gap-large: 2em; + + --container-padding-small: 1em; + --container-padding-medium: 1.5em; + --container-padding-large: 2em; + + --input-border-style: solid; + --input-border-color: var(--color-border); + + --input-border-small: 0.05em; + --input-border-medium: 0.075em; + --input-border-large: 0.1em; + + --input-padding-small: 0.4em; + --input-padding-medium: 0.8em; + --input-padding-large: 1.2em; font-family: var(--font-family); font-size: var(--font-size); diff --git a/src/components/dropdown/Dropdown.css b/src/components/dropdown/Dropdown.css deleted file mode 100644 index 033d1d1..0000000 --- a/src/components/dropdown/Dropdown.css +++ /dev/null @@ -1,69 +0,0 @@ -.mtds -{ - .dropdown - { - --background-color: transparent; - --background-color-options: var(--color-primary-background); - --background-color-options-hover: var(--color-background-hover); - --arrow-color: var(--color-alert-foreground); - --border-color: var(--color-border); - - display: inline-block; - position: relative; - - min-width: fit-content; - - cursor: pointer; - - .text - { - background-color: var(--background-color); - - padding: 0.8em 2em 0.8em 0em; - - &:after - { - content: ""; - - position: absolute; - right: 0.7em; - top: 1.2em; - - border: 0.45em solid transparent; - border-color: var(--arrow-color) transparent transparent transparent; - } - - &.active:after - { - top: 0.8em; - - border-color: transparent transparent var(--arrow-color) transparent; - } - } - - .options - { - position: absolute; - - min-width: fit-content; - white-space: nowrap; - text-align: left; - - background-color: var(--background-color-options); - border: 0.1em solid var(--border-color); - - .option - { - padding: 0.8em 2em 0.8em 0.8em; - - background: var(--color-primary-background); - cursor: pointer; - - &:hover - { - background-color: var(--background-color-options-hover); - } - } - } - } -} \ No newline at end of file diff --git a/src/components/dropdown/Dropdown.tsx b/src/components/dropdown/Dropdown.tsx deleted file mode 100644 index 790e13b..0000000 --- a/src/components/dropdown/Dropdown.tsx +++ /dev/null @@ -1,66 +0,0 @@ - -import type { MouseEvent} from 'react'; -import { useState } from 'react'; - -import './Dropdown.css'; - -type Props = { - readonly options: Map; - readonly selected?: string; // Key of the selected option - readonly onChange?: (oldKey: string, newKey: string) => void; -}; - -export function Dropdown({ options, selected, onChange }: Props) -{ - const defaultKey = selected ?? [...options.keys()][0]; - const defaultText = options.get(defaultKey) ?? ''; - - const [selectedKey, setSelectedKey] = useState(defaultKey); - const [selectedText, setSelectedText] = useState(defaultText); - const [showOptions, setShowOptions] = useState(false); - - const toggleOptions = () => - { - setShowOptions(!showOptions); - }; - - const handleOptionClick = (event: MouseEvent) => - { - const target = event.target as HTMLElement; - - const oldKey = selectedKey; - const newKey = target.getAttribute('data-key') ?? defaultKey; - const newText = options.get(newKey) ?? defaultText; - - setSelectedKey(newKey); - setSelectedText(newText); - setShowOptions(false); - - if (onChange) - { - onChange(oldKey, newKey); - } - }; - - return ( - - - {selectedText} - - {showOptions && ( - - { - Array.from(options).map(([key, value]) => - { - return ( - - {value} - - ); - }) - } - - )} - - ); -} diff --git a/src/components/modals/ModalManager.tsx b/src/components/modals/ModalManager.tsx deleted file mode 100644 index 0119be2..0000000 --- a/src/components/modals/ModalManager.tsx +++ /dev/null @@ -1,48 +0,0 @@ - -import type { ReactElement } from 'react'; -import { useEffect, useEffectEvent, useMemo, useState } from 'react'; - -import type { Props as ModalProps } from '../../elements/modal/Modal'; - -type Props = { - readonly selectedId?: string; - readonly children: ReactElement | ReactElement[]; -}; - -export function ModalManager({ selectedId, children}: Props) -{ - const [selected, setSelected] = useState(selectedId ?? ''); - - const modalList = useMemo(() => - { - return Array.isArray(children) ? children : [children]; - - }, [children]); - - const modalMap = useMemo(() => - { - const modals: Record = {}; - - modalList.forEach((element) => - { - const tabId = element.props.id; - modals[tabId] = element; - }); - - return modals; - - }, [modalList]); - - const updateSelected = useEffectEvent((selectedId?: string) => - { - setSelected(selectedId ?? ''); - }); - - useEffect(() => - { - updateSelected(selectedId); - - }, [selectedId, modalList]); - - return modalMap[selected] ?? <>>; -} diff --git a/src/elements/border/Border.css b/src/elements/border/Border.css index 041e800..28d4d04 100644 --- a/src/elements/border/Border.css +++ b/src/elements/border/Border.css @@ -3,17 +3,17 @@ .border { --border-radius: 0; - --margin: 0; --color: var(--color-border); + --margin: 0; - --size-large: 0.3em; - --size-medium: 0.2em; - --size-small: 0.1em; - - --padding-large: 2em; - --padding-medium: 1.5em; - --padding-small: 1em; - + --size-small: var(--element-border-small); + --size-medium: var(--element-border-medium); + --size-large: var(--element-border-large); + + --padding-small: var(--container-padding-small); + --padding-medium: var(--container-padding-medium); + --padding-large: var(--container-padding-large); + border-color: var(--color); border-radius: var(--border-radius); margin: var(--margin); diff --git a/src/elements/border/Border.tsx b/src/elements/border/Border.tsx index 2dc7043..394ac61 100644 --- a/src/elements/border/Border.tsx +++ b/src/elements/border/Border.tsx @@ -5,8 +5,8 @@ import './Border.css'; type Props = { readonly type?: 'normal' | 'dashed' | 'dotted'; - readonly size?: 'large' | 'medium' | 'small'; - readonly padding?: 'large' | 'medium' | 'small' | 'none'; + readonly size?: 'small' | 'large' | 'medium'; + readonly padding?: 'none' | 'small' | 'medium' | 'large'; readonly children?: ReactNode; }; diff --git a/src/elements/button/Button.css b/src/elements/button/Button.css index 2ef2379..23a9ec8 100644 --- a/src/elements/button/Button.css +++ b/src/elements/button/Button.css @@ -5,9 +5,13 @@ --margin: 0; --border-radius: 2em; - --border-style: solid; - --border-color: var(--color-border); - --border-width: var(--width-border); + --border-style: var(--input-border-style); + + --border-color-disabled: var(--input-border-color); + + --border-small: var(--input-border-small); + --border-medium: var(--input-border-medium); + --border-large: var(--input-border-large); --size-small: 0.2em 2.2em; --size-medium: 0.5em 2.2em; @@ -20,7 +24,8 @@ --type-disabled-background-color: transparent; border-radius: var(--border-radius); - border: 0; + border-style: var(--border-style); + border-color: var(--border-color); cursor: pointer; margin: var(--margin); @@ -30,6 +35,26 @@ font-family: inherit; font-size: inherit; + &.border-none + { + border-width: 0; + } + + &.border-small + { + border-width: var(--border-small); + } + + &.border-medium + { + border-width: var(--border-medium); + } + + &.border-large + { + border-width: var(--border-large); + } + &.size-small { padding: var(--size-small); @@ -49,6 +74,7 @@ &.type-submit { background-color: var(--type-primary-background-color); + border-color: var(--type-primary-background-color); color: var(--type-primary-foreground-color); font-weight: var(--font-weight-bold); @@ -57,17 +83,24 @@ &.type-primary:hover, &.type-submit:hover { - background-color: color-mix(in srgb, black 10%, var(--type-primary-background-color)); + --hover-color: color-mix(in srgb, black 10%, var(--type-primary-background-color)); + + background-color: var(--hover-color); + border-color: var(--hover-color); } &.type-primary:focus { - background-color: color-mix(in srgb, black 20%, var(--type-primary-background-color)); + --hover-color: color-mix(in srgb, black 20%, var(--type-primary-background-color)); + + background-color: var(--hover-color); + border-color: var(--hover-color); } &.type-secondary { background-color: var(--type-secondary-background-color); + border-color: var(--type-secondary-background-color); color: var(--type-secondary-foreground-color); font-weight: var(--font-weight-normal); @@ -75,21 +108,24 @@ &.type-secondary:hover { - background-color: color-mix(in srgb, black 10%, var(--type-secondary-background-color)); + --hover-color: color-mix(in srgb, black 10%, var(--type-secondary-background-color)); + + background-color: var(--hover-color); + border-color: var(--hover-color); } &.type-secondary:focus { - background-color: color-mix(in srgb, black 20%, var(--type-secondary-background-color)); + --hover-color: color-mix(in srgb, black 20%, var(--type-secondary-background-color)); + + background-color: var(--hover-color); + border-color: var(--hover-color); } &.type-disabled { background-color: var(--type-disabled-background-color); - - border-color: var(--border-color); - border-style: var(--border-style); - border-width: var(--border-width); + border-color: var(--border-color-disabled); font-weight: var(--font-weight-light); diff --git a/src/elements/button/Button.tsx b/src/elements/button/Button.tsx index 767dfa7..43c0306 100644 --- a/src/elements/button/Button.tsx +++ b/src/elements/button/Button.tsx @@ -3,15 +3,17 @@ import './Button.css'; type Props = { readonly type?: 'submit' | 'primary' | 'secondary' | 'disabled'; + readonly border?: 'none' | 'small' | 'medium' | 'large'; readonly size?: 'large' | 'medium' | 'small'; readonly text: string; readonly onClick?: () => void; }; -export function Button({ type = 'primary', size = 'medium', text, onClick }: Props) +export function Button({ type = 'primary', border = 'small', size = 'medium', text, onClick }: Props) { const className = 'button' + ' type-' + type + + ' border-' + border + ' size-' + size; const disabled = type === 'disabled'; diff --git a/src/elements/clickarea/ClickArea.css b/src/elements/clickarea/ClickArea.css index a862298..ba4830e 100644 --- a/src/elements/clickarea/ClickArea.css +++ b/src/elements/clickarea/ClickArea.css @@ -4,19 +4,17 @@ { --margin: 0; - --padding-large: 2em; - --padding-medium: 1.5em; - --padding-small: 1em; - --padding-none: 0; + --padding-small: var(--container-padding-small); + --padding-medium: var(--container-padding-medium); + --padding-large: var(--container-padding-large); cursor: pointer; display: inline-block; margin: var(--margin); - padding: var(--padding); &.padding-none { - padding: var(--padding-none); + padding: 0; } &.padding-small diff --git a/src/elements/clickarea/ClickArea.tsx b/src/elements/clickarea/ClickArea.tsx index 78fba23..d4a0b2c 100644 --- a/src/elements/clickarea/ClickArea.tsx +++ b/src/elements/clickarea/ClickArea.tsx @@ -4,7 +4,7 @@ import type { ReactNode } from 'react'; import './ClickArea.css'; type Props = { - readonly padding?: 'large' | 'medium' | 'small' | 'none'; + readonly padding?: 'none' | 'small' | 'medium' | 'large'; readonly onClick?: () => void; readonly children?: ReactNode; }; diff --git a/src/elements/column/Column.css b/src/elements/column/Column.css index b07697d..b30ae13 100644 --- a/src/elements/column/Column.css +++ b/src/elements/column/Column.css @@ -2,9 +2,9 @@ { .column { - --gap-small: 0.4em; - --gap-medium: 1em; - --gap-large: 2em; + --gap-small: var(--container-gap-small); + --gap-medium: var(--container-gap-medium); + --gap-large: var(--container-gap-large); display: flex; flex-direction: column; diff --git a/src/elements/column/Column.tsx b/src/elements/column/Column.tsx index 9e2d733..e025b2b 100644 --- a/src/elements/column/Column.tsx +++ b/src/elements/column/Column.tsx @@ -6,7 +6,7 @@ import './Column.css'; type Props = { readonly alignX?: 'left' | 'center' | 'right' | 'stretch'; readonly alignY?: 'top' | 'center' | 'bottom' | 'justify'; - readonly gap?: 'large' | 'medium' | 'small' | 'none'; + readonly gap?: 'none' | 'small' | 'medium' | 'large'; readonly wrap?: 'wrap' | 'nowrap'; readonly children: ReactNode; }; diff --git a/src/elements/datetime/DateTime.css b/src/elements/datetime/DateTime.css index f1edb1a..b6db6e3 100644 --- a/src/elements/datetime/DateTime.css +++ b/src/elements/datetime/DateTime.css @@ -4,18 +4,20 @@ { --margin: 0; - --size-small: 0.4em; - --size-medium: 0.8em; - --size-large: 1.2em; + --border-style: var(--input-border-style); + --border-color: var(--input-border-color); - --border-style: solid; - --border-color: var(--color-border); - --border-width: var(--width-border); + --border-small: var(--input-border-small); + --border-medium: var(--input-border-medium); + --border-large: var(--input-border-large); + + --size-small: var(--input-padding-small); + --size-medium: var(--input-padding-medium); + --size-large: var(--input-padding-large); margin: var(--margin); border-style: var(--border-style); - border-width: var(--border-width); border-color: var(--border-color); background-color: var(--color-input-background); @@ -28,6 +30,26 @@ outline-color: color-mix(in srgb, black 10%, var(--border-color)); } + &.border-none + { + border: 0; + } + + &.border-small + { + border-width: var(--border-small); + } + + &.border-medium + { + border-width: var(--border-medium); + } + + &.border-large + { + border-width: var(--border-large); + } + &.size-small { padding: var(--size-small); diff --git a/src/elements/datetime/DateTime.tsx b/src/elements/datetime/DateTime.tsx index 2a5e703..a340131 100644 --- a/src/elements/datetime/DateTime.tsx +++ b/src/elements/datetime/DateTime.tsx @@ -10,16 +10,18 @@ export type Props = { readonly value?: string; readonly title?: string; readonly type?: 'datetime' | 'date' | 'time' | 'month' | 'week'; - readonly size?: 'large' | 'medium' | 'small'; + readonly border?: 'none' | 'small' | 'medium' | 'large'; + readonly size?: 'small' | 'medium' | 'large'; readonly required?: boolean; readonly onChange?: ChangeEventHandler; }; type Ref = HTMLInputElement; -export const DateTime = forwardRef(function Element({ name, defaultValue, value, title, type = 'datetime', size = 'medium', required, onChange }, ref) +export const DateTime = forwardRef(function Element({ name, defaultValue, value, title, type = 'datetime', border ='small', size = 'medium', required, onChange }, ref) { const className = 'datetime' + + ' border-' + border + ' size-' + size; return ; readonly defaultValue?: string; readonly value?: string; - readonly size?: 'large' | 'medium' | 'small'; + readonly border?: 'none' | 'small' | 'medium' | 'large'; + readonly size?: 'small' | 'medium' | 'large'; readonly onChange?: ChangeEventHandler; }; type Ref = HTMLSelectElement; -export const Select = forwardRef(function Element({ name, options, defaultValue, value, size = 'medium', onChange }, ref) +export const Select = forwardRef(function Element({ name, options, defaultValue, value, border ='small', size = 'medium', onChange }, ref) { const className = 'select' + + ' border-' + border + ' size-' + size; return ; }; diff --git a/src/elements/table/Table.css b/src/elements/table/Table.css index 6337fb9..d9b3634 100644 --- a/src/elements/table/Table.css +++ b/src/elements/table/Table.css @@ -12,9 +12,9 @@ --row-odd-color: var(--color-primary-background); --row-even-color: var(--color-secondary-background); - --footer-border-small: 0.1rem; - --footer-border-medium: 0.2rem; - --footer-border-large: 0.3rem; + --footer-border-small: var(--element-border-small); + --footer-border-medium: var(--element-border-medium); + --footer-border-large: var(--element-border-large); --footer-border-color: var(--color-border); --footer-forground-color: var(--color-primary-foreground); diff --git a/src/elements/table/Table.tsx b/src/elements/table/Table.tsx index b5b6924..4b9e677 100644 --- a/src/elements/table/Table.tsx +++ b/src/elements/table/Table.tsx @@ -14,7 +14,7 @@ import type { Props as FooterProps } from './Footer'; import './Table.css'; type Props = { - readonly padding?: 'large' | 'medium' | 'small'; + readonly padding?: 'small' | 'medium' | 'large'; readonly children?: ReactElement | ReactElement | ReactElement[]; }; diff --git a/src/elements/text/Text.css b/src/elements/text/Text.css index 2f095d2..f3a3af0 100644 --- a/src/elements/text/Text.css +++ b/src/elements/text/Text.css @@ -5,9 +5,9 @@ --primary-color: var(--color-primary-foreground); --secondary-color: var(--color-secondary-foreground); - --size-small: 0.9em; - --size-medium: 1em; - --size-large: 1.2em; + --size-small: var(--element-font-small); + --size-medium: var(--element-font-medium); + --size-large: var(--element-font-large); display: inline-block; diff --git a/src/elements/text/Text.tsx b/src/elements/text/Text.tsx index 3406b81..e229c9c 100644 --- a/src/elements/text/Text.tsx +++ b/src/elements/text/Text.tsx @@ -4,7 +4,7 @@ import './Text.css'; type Props = { readonly value: string; readonly type?: 'primary' | 'secondary'; - readonly size?: 'large' | 'medium' | 'small'; + readonly size?: 'small' | 'medium' | 'large'; readonly weight?: 'light' | 'normal' | 'bold'; readonly wrap?: 'none' | 'normal' | 'break-word'; }; diff --git a/src/elements/textarea/TextArea.css b/src/elements/textarea/TextArea.css index 7b1d489..051e062 100644 --- a/src/elements/textarea/TextArea.css +++ b/src/elements/textarea/TextArea.css @@ -4,19 +4,21 @@ { --margin: 0; - --size-small: 0.4em; - --size-medium: 0.8em; - --size-large: 1.2em; + --border-style: var(--input-border-style); + --border-color: var(--input-border-color); - --border-style: solid; - --border-color: var(--color-border); - --border-width: var(--width-border); + --border-small: var(--input-border-small); + --border-medium: var(--input-border-medium); + --border-large: var(--input-border-large); + + --size-small: var(--input-padding-small); + --size-medium: var(--input-padding-medium); + --size-large: var(--input-padding-large); margin: var(--margin); padding: var(--padding); border-style: var(--border-style); - border-width: var(--border-width); border-color: var(--border-color); background-color: var(--color-input-background); @@ -31,6 +33,26 @@ outline-color: color-mix(in srgb, black 10%, var(--border-color)); } + &.border-none + { + border: 0; + } + + &.border-small + { + border-width: var(--border-small); + } + + &.border-medium + { + border-width: var(--border-medium); + } + + &.border-large + { + border-width: var(--border-large); + } + &.size-small { padding: var(--size-small); diff --git a/src/elements/textarea/TextArea.tsx b/src/elements/textarea/TextArea.tsx index 4ab0f7c..0db2ea7 100644 --- a/src/elements/textarea/TextArea.tsx +++ b/src/elements/textarea/TextArea.tsx @@ -9,7 +9,8 @@ export type Props = { readonly placeholder?: string; readonly defaultValue?: string; readonly value?: string; - readonly size?: 'large' | 'medium' | 'small'; + readonly border?: 'none' | 'small' | 'medium' | 'large'; + readonly size?: 'small' | 'medium' | 'large'; readonly rows?: number; readonly limit?: number; readonly onChange?: ChangeEventHandler; @@ -17,9 +18,10 @@ export type Props = { type Ref = HTMLTextAreaElement; -export const TextArea = forwardRef(function Element({ name, placeholder, defaultValue, value, size = 'medium', rows, limit, onChange }, ref) +export const TextArea = forwardRef(function Element({ name, placeholder, defaultValue, value, border ='small', size = 'medium', rows, limit, onChange }, ref) { const className = 'textarea' + + ' border-' + border + ' size-' + size; return ; }; type Ref = HTMLInputElement; -export const TextBox = forwardRef(function Element({ name, placeholder, defaultValue, value, limit, pattern, title, type = 'text', size = 'medium', required, onChange }, ref) +export const TextBox = forwardRef(function Element({ name, placeholder, defaultValue, value, limit, pattern, title, type = 'text', border ='small', size = 'medium', required, onChange }, ref) { const className = 'textbox' + + ' border-' + border + ' size-' + size; return .content + { + box-sizing: border-box; + padding: 1rem; + width: 100vw; + max-width: var(--content-max-width); + text-align: center; + } + + @container (max-width: 499px) + { + & > .content + { + padding: 0.5rem; + } + } + } +} \ No newline at end of file diff --git a/src/layouts/centered/Centered.tsx b/src/layouts/centered/Centered.tsx new file mode 100644 index 0000000..1695c2c --- /dev/null +++ b/src/layouts/centered/Centered.tsx @@ -0,0 +1,17 @@ + +import type { ReactNode } from 'react'; + +import './Centered.css'; + +type Props = { + readonly children?: ReactNode; +}; + +export function CenteredLayout({ children }: Props) +{ + return + + {children} + + ; +} diff --git a/src/layouts/sidebar/Sidebar.css b/src/layouts/sidebar/Sidebar.css new file mode 100644 index 0000000..f091fbf --- /dev/null +++ b/src/layouts/sidebar/Sidebar.css @@ -0,0 +1,150 @@ +.mtds +{ + .layout-sidebar + { + --header-height: 3.4em; + --footer-height: 4.4em; + + --modal-width: 700px; + --sidebar-width: 260px; + --content-width: 640px; + + --content-gap: 2em; + + --calc-gap-half: calc(var(--content-gap) / 2); + --calc-content-offset: calc((var(--content-width) - var(--sidebar-width) + var(--calc-gap-half)) / 2); + + --left-width: calc(50vw - var(--calc-content-offset)); + --right-width: calc(100vw - var(--left-width)); + + container-type: size; + height: 100dvh; + overflow: hidden; + + & > header + { + background-color: var(--background-color); + border-bottom: 0.1em solid var(--border-color); + display: none; + padding: 0.5em 1em; + height: var(--header-height); + width: 100vw; + top: 0; + left: 0; + position: absolute; + z-index: 1000; + } + + & > .content + { + display: flex; + flex-direction: row; + gap: var(--content-gap); + width: 100vw; + height: 100vh; + max-height: 100vh; + + .left + { + display: flex; + flex-direction: row; + justify-content: flex-end; + width: var(--left-width); + + aside + { + flex-shrink: 0; + flex-grow: 0; + padding: 1em 0; + width: var(--sidebar-width); + } + } + + .right + { + display: flex; + flex-direction: column; + justify-content: flex-start; + overflow-y: auto; + overscroll-behavior: none; + width: var(--right-width); + + main + { + flex-shrink: 0; + flex-grow: 0; + padding: 1em 0; + width: var(--content-width); + } + } + + .modal + { + margin-top: 2em; + max-width: var(--modal-width); + } + } + + & > footer + { + background-color: var(--background-color); + border-top: 0.1em solid var(--border-color); + display: none; + padding: 0.4em 1em; + width: 100vw; + left: 0; + bottom: 0; + position: absolute; + z-index: 1000; + } + + @container (max-width: 959px) + { + & > .content + { + .left + { + display: none; + } + + .right + { + width: 100vw; + padding: 0 1em; + position: absolute; + top: var(--header-height); + bottom: var(--footer-height); + + main + { + width: 100%; + + } + } + } + + & > header, + & > footer + { + display: block; + } + } + + @container (max-width: 499px) + { + & > .content + { + .right + { + padding: 0 0.5em; + } + } + + & > header, + & > footer + { + padding: 0.4em 0.5em; + } + } + } +} \ No newline at end of file diff --git a/src/layouts/sidebar/Sidebar.tsx b/src/layouts/sidebar/Sidebar.tsx new file mode 100644 index 0000000..9dcb474 --- /dev/null +++ b/src/layouts/sidebar/Sidebar.tsx @@ -0,0 +1,35 @@ + +import type { ReactNode } from 'react'; + +import './Sidebar.css'; + +type Props = { + readonly header?: ReactNode; + readonly footer?: ReactNode; + readonly sidebar: ReactNode; + readonly children: ReactNode; +}; + +export function SidebarLayout({ header, footer, sidebar, children }: Props) +{ + return + + {header} + + + + + + + + {children} + + + + + ; +}