Added instructions
This commit is contained in:
parent
06b9643183
commit
efd2121d0b
1 changed files with 553 additions and 0 deletions
553
.github/copilot-instructions.md
vendored
Normal file
553
.github/copilot-instructions.md
vendored
Normal file
|
|
@ -0,0 +1,553 @@
|
|||
# Project Structure
|
||||
|
||||
This is an Eleventy (11ty) project based on [eleventy-excellent](https://github.com/madrilene/eleventy-excellent.git).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Configuration
|
||||
|
||||
This project uses ESM syntax instead of CommonJS. Configurations are structured into separate modules in `src/_config` and are then imported into the main configuration file.
|
||||
|
||||
- **collections.js**: Manages Eleventy collections such as posts and tags: https://www.11ty.dev/docs/collections/
|
||||
- **events.js**: For code that should run at certain times during the compiling process: https://www.11ty.dev/docs/events/
|
||||
- **filters.js**: Used within templating syntax to transform data into a more presentable format: https://www.11ty.dev/docs/filters/
|
||||
- **plugins.js**: Everything I or Eleventy considers to be a plugin: https://www.11ty.dev/docs/plugins/
|
||||
- **shortcodes.js**: Defines shortcodes for reusable content: https://www.11ty.dev/docs/shortcodes/
|
||||
|
||||
Each configuration category (filters, plugins, shortcodes, etc.) is modularized. or example, `dates.js` within the `filters` folder contains date-related filters.
|
||||
|
||||
```js
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export const toISOString = dateString => dayjs(dateString).toISOString();
|
||||
export const formatDate = (date, format) => dayjs(date).format(format);
|
||||
```
|
||||
|
||||
These individual modules are then imported and consolidated in a central `filters.js` file, which exports all the filters as a single default object.
|
||||
|
||||
```js
|
||||
import {toISOString, formatDate} from './filters/dates.js';
|
||||
// more imports
|
||||
|
||||
export default {
|
||||
toISOString,
|
||||
formatDate,
|
||||
// more exports
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Integration in Eleventy Config
|
||||
|
||||
In the main Eleventy configuration file (`eleventy.config.js`), these modules are imported:
|
||||
|
||||
```js
|
||||
import filters from './src/_config/filters.js';
|
||||
import shortcodes from './src/_config/shortcodes.js';
|
||||
```
|
||||
|
||||
They are then used to register filters and shortcodes with Eleventy, using this nice concise syntax:
|
||||
|
||||
```js
|
||||
eleventyConfig.addFilter('toIsoString', filters.toISOString);
|
||||
eleventyConfig.addFilter('formatDate', filters.formatDate);
|
||||
// More filters...
|
||||
eleventyConfig.addShortcode('svg', shortcodes.svgShortcode);
|
||||
```
|
||||
|
||||
This method hopefully keeps the Eleventy config clean and focused, only concerning itself with the registration of functionalities, while the logic and definition remain abstracted in their respective modules.
|
||||
|
||||
|
||||
# CSS
|
||||
|
||||
We are using Tailwind CSS to generate utility classes on demand, based on our design tokens. Add and delete your globally available custom block stylesheets in `src/assets/css/global/blocks/*.css`.
|
||||
|
||||
The methodology used is [CUBE CSS.](https://cube.fyi/)
|
||||
|
||||
If you have a look at the tailwind.config.js, you can see how that is done. For example, we are deactivating Tailwinds default reset.
|
||||
|
||||
We are hooking into the components layer, to make Tailwind output classes based on our tokens, instead of their default design system.
|
||||
|
||||
That is, you are able to use mt-xs-s instead of a class like mt-20 for example. Same goes for colors, depending on the namesin your colors.json, you get custom classes like text-pink. These use the usual Tailwind prefixes (see docs to learn how to generate colors).
|
||||
|
||||
You get a custom property mapped to the color name --color-my-custom-color-name and the classes bg-my-custom-color-name as well as text-my-custom-color-name.
|
||||
|
||||
Consider that we limit those utilities in the theme section:
|
||||
|
||||
```js
|
||||
backgroundColor: ({theme}) => theme('colors'),
|
||||
textColor: ({theme}) => theme('colors'),
|
||||
margin: ({theme}) => ({ auto: 'auto', ...theme('spacing')}),
|
||||
padding: ({theme}) => theme('spacing')
|
||||
```
|
||||
If you want to add the generation for border-color classes for example, you’d have to add that right there:
|
||||
|
||||
{% raw %}
|
||||
```js
|
||||
borderWidth: ({theme}) => theme('borderWidth'),
|
||||
borderColor: ({theme}) => theme('colors')
|
||||
```
|
||||
{% endraw %}
|
||||
|
||||
Also. you do have something like md:text-right available because we define the screens (src/_data/designTokens/viewports.json):
|
||||
|
||||
{% raw %}
|
||||
```js
|
||||
screens: {
|
||||
ltsm: {max: `${viewportTokens.sm}px`},
|
||||
sm: `${viewportTokens.sm}px`,
|
||||
md: `${viewportTokens.md}px`,
|
||||
navigation: `${viewportTokens.navigation}px`
|
||||
},
|
||||
```
|
||||
{% endraw %}
|
||||
|
||||
Additionally, you get custom properties based on the naming of your design token files:
|
||||
|
||||
{% raw %}
|
||||
```js
|
||||
const groups = [
|
||||
{key: 'colors', prefix: 'color'},
|
||||
{key: 'borderRadius', prefix: 'border-radius'},
|
||||
{key: 'spacing', prefix: 'space'},
|
||||
{key: 'fontSize', prefix: 'size'},
|
||||
{key: 'lineHeight', prefix: 'leading'},
|
||||
{key: 'fontFamily', prefix: 'font'},
|
||||
{key: 'fontWeight', prefix: 'font'}
|
||||
];
|
||||
```
|
||||
{% endraw %}
|
||||
|
||||
The clampGenerator generates Utopia-like CSS clamp values for fluid type and space and the tokensToTailwind function converts whatever format the project’s design tokens are in, into Tailwind friendly configuration objects.
|
||||
|
||||
## module.export
|
||||
|
||||
We don’t let Tailwind create all the utilities that it can do at the core. Each of the single keyword properties like fontSize are defined at the top of the file by running through the tokensToTailwind function, so they get referenced straight in the config.
|
||||
|
||||
The Tailwind media query function — screen — is very rarely used, but we at least want that rigged up to design tokens.
|
||||
|
||||
```js
|
||||
// Prevents Tailwind's core components
|
||||
blocklist: ['container'],
|
||||
```
|
||||
|
||||
|
||||
Container is not needed because there’s a wrapper in css/compositions.
|
||||
|
||||
```js
|
||||
|
||||
experimental: {
|
||||
optimizeUniversalDefaults: true
|
||||
},
|
||||
```
|
||||
|
||||
This config value also contributes to getting rid of the massive wall of Custom Properties.
|
||||
|
||||
## plugins
|
||||
|
||||
{% raw %}
|
||||
```js
|
||||
|
||||
plugin(function ({addComponents, config}) {
|
||||
let result = '';
|
||||
|
||||
const currentConfig = config();
|
||||
|
||||
const groups = [
|
||||
{key: 'colors', prefix: 'color'},
|
||||
{key: 'spacing', prefix: 'space'},
|
||||
{key: 'fontSize', prefix: 'size'},
|
||||
{key: 'fontLeading', prefix: 'leading'},
|
||||
{key: 'fontFamily', prefix: 'font'},
|
||||
{key: 'fontWeight', prefix: 'font'}
|
||||
];
|
||||
|
||||
groups.forEach(({key, prefix}) => {
|
||||
const group = currentConfig.theme[key];
|
||||
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(group).forEach(key => {
|
||||
result += `--${prefix}-${key}: ${group[key]};`;
|
||||
});
|
||||
});
|
||||
|
||||
addComponents({
|
||||
':root': postcssJs.objectify(postcss.parse(result))
|
||||
});
|
||||
}),
|
||||
```
|
||||
{% endraw %}
|
||||
|
||||
Right, this is the sort of thing that sold me on Tailwind. They have a whole custom plugin system that you can tap into. One thing I want to always do is generate a nice block of Custom Properties, based on design tokens. That’s exactly what the above code does.
|
||||
|
||||
It goes through each defined group (groups) and then grabs the values > generates Custom Property values > sticks them to the result.
|
||||
|
||||
Finally, using postcssJs and postcss, an object that Tailwind can understand is created. The addComponents function sticks that custom properties block on the @components layer. It’s a bit of a hack, but it does the job.
|
||||
|
||||
{% raw %}
|
||||
```js
|
||||
|
||||
plugin(function ({addUtilities, config}) {
|
||||
const currentConfig = config();
|
||||
const customUtilities = [
|
||||
{key: 'spacing', prefix: 'flow-space', property: '--flow-space'},
|
||||
{key: 'spacing', prefix: 'region-space', property: '--region-space'},
|
||||
{key: 'spacing', prefix: 'gutter', property: '--gutter'}
|
||||
];
|
||||
|
||||
customUtilities.forEach(({key, prefix, property}) => {
|
||||
const group = currentConfig.theme[key];
|
||||
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(group).forEach(key => {
|
||||
addUtilities({
|
||||
[`.${prefix}-${key}`]: postcssJs.objectify(
|
||||
postcss.parse(`${property}: ${group[key]}`)
|
||||
)
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
```
|
||||
{% endraw %}
|
||||
|
||||
This function creates custom utilities that can be used in markup, such as gutter-m or flow-space-s. It’s all rigged up to the design tokens Custom Property block. It’s damn useful, especially when tweaking layout compositions in context.
|
||||
|
||||
## PostCSS
|
||||
|
||||
{% raw %}
|
||||
```js
|
||||
|
||||
@import 'tailwindcss/base';
|
||||
|
||||
@import 'global/reset.css';
|
||||
@import 'global/fonts.css';
|
||||
|
||||
@import 'tailwindcss/components';
|
||||
|
||||
@import 'global/variables.css';
|
||||
@import 'global/global-styles.css';
|
||||
|
||||
@import-glob 'blocks/*.css';
|
||||
@import-glob 'compositions/*.css';
|
||||
@import-glob 'utilities/*.css';
|
||||
|
||||
@import 'tailwindcss/utilities';
|
||||
|
||||
```
|
||||
{% endraw %}
|
||||
I try to maintain a decent source order for specificity purposes as you can see. The @import 'tailwindcss/components' is where that block of Custom Properties generated in the Tailwind config gets put. Because everything else that layer does is disabled in config, it’s nice and clean.
|
||||
|
||||
The CUBE parts are all imported using the extremely useful import-glob PostCSS plugin. This allows new files to be added to directories and imported straight away.
|
||||
|
||||
|
||||
### Inline CSS and bundles
|
||||
|
||||
The main CSS file is now inline in production to improve performance, see `.src/_includes/head/css-inline.njk`.
|
||||
|
||||
You can add per-page or component bundles of CSS. Instead of adding your CSS file to the `src/assets/css/global/blocks/` directory, you can place them in `src/assets/css/local/`. All CSS files in there will be stored alongside `global.css` in `.src/_includes/css/`. You can now include them in the "local" bundle only on pages or components where you need them:
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jinja2
|
||||
{% css "local" %}
|
||||
{% include "css/your-stylesheet.css" %}
|
||||
{% endcss %}
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Component CSS
|
||||
|
||||
All CSS files placed in `src/assets/css/components/` will be sent to the output folder, where components can reference them: `/assets/css/components/*.css`.
|
||||
|
||||
### Debugging CSS
|
||||
|
||||
In `src/assets/css/global.css` you can decomment `@import-glob 'tests/*.css';` to include CSS for debugging.
|
||||
|
||||
It makes visible when your code[ wrapped in `<is-land>` elements](https://github.com/11ty/is-land) is being hydrated, where things might overflow and many other warnings and errors [that Heydon Pickering came up with](https://heydonworks.com/article/testing-html-with-modern-css/).
|
||||
|
||||
### Cascade layers
|
||||
|
||||
We now use cascade layers! Up until now, I used the `:where()` pseudo-class to create low specificity selectors for the reset and compositions. [Mayank inspired me](https://mayank.co/blog/css-reset-layer/) to change to cascade layers. We have two major bundles of CSS: everything included in "global" In `src/assets/css/global/global.css` is now organized in cascade layers. The "local" bundle is for per-page or component CSS, and does not use cascade layers - it has thus a higher specificity, independent of any selector specificity in the global CSS.
|
||||
|
||||
|
||||
## Design Tokens
|
||||
|
||||
Edit all your preferences (colors, fluid text sizes etc.) in `src/_data/designTokens/*.json`.
|
||||
|
||||
Additional colors, variants and gradients for custom properties are automatically created in `src/assets/css/global/base/variables.css` based on the colors set in `colors.json`.
|
||||
|
||||
In the [style guide](/styleguide/) you can see how everything turns out.
|
||||
|
||||
### Special case: colors
|
||||
|
||||
As of version 4.0, you can create colors dynamically. Run `npm run colors` after setting your color values in `src/_data/designTokens/colorsBase.json`. This will create / overwrite the required `colors.json` file in the same directory. These colors become custom properties (e.g. `--color-gray-100`) and utility classes similar to the Tailwind CSS syntax (for example `bg-gray-100`, `text-gray-900`).
|
||||
|
||||
If you want to adjust how the colors turn out, edit `src/_config/setup/create-colors.js`.
|
||||
|
||||
Colors placed under `shades_neutral` or `shades_vibrant` are converted into scalable palettes. `shades_neutral` is better for grayish / monochromatic colors, while `shades_vibrant` is better for colorful palettes. Colors listed under `standalone` and `light_dark` are left as they are, `light_dark` items output a second "subdued" version optimized for dark themes.
|
||||
|
||||
```js
|
||||
// this creates a palette with shades of green, 100 to 900
|
||||
"shades_vibrant": [
|
||||
{
|
||||
"name": "green",
|
||||
"value": "#008000"
|
||||
}
|
||||
],
|
||||
```
|
||||
|
||||
Important: If you change the color names, you must edit `src/assets/css/global/base/variables.css` with your color names. The rest of the CSS files should only reference custom properties set in `variables.css`.
|
||||
|
||||
|
||||
# Javascript
|
||||
|
||||
This project uses ESM syntax instead of CommonJS. Configurations are structured into separate modules in `src/_config` and are then imported into the main configuration file.
|
||||
|
||||
There are two kinds of bundles for JavaScript in this starter, see `.src/_includes/head/js-inline.njk` and `.src/_includes/head/js-defer.njk`.
|
||||
By default, I include Eleventy's [is-land](https://github.com/11ty/is-land) framework and the theme toggle inline.
|
||||
|
||||
You can include more scripts like so:
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jinja2
|
||||
{% js "inline" %}
|
||||
{% include "scripts/your-inline-script.js" %}
|
||||
{% endjs %}
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
Same goes for scripts that should be defered:
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jinja2
|
||||
{% js "defer" %}
|
||||
{% include "scripts/your-defered-script.js" %}
|
||||
{% endjs %}
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
Scripts stored in `src/assets/scripts/components/` are sent to the output folder, while scripts in `src/assets/scripts/bundle/` are sent to `.src/_includes/scripts/`, from where you can include them in the respective bundle.
|
||||
|
||||
|
||||
# Details
|
||||
|
||||
The `<custom-details>` WebC component has a corresponding Nunjucks include.
|
||||
It uses the `<details>` and `<summary>` elements to create a collapsible section and enhances them aesthetically and functionally.
|
||||
|
||||
The JavaScript for the `<custom-details>` component adds functionality to buttons to expand and collapse the sections with one action. When JavaScript is disabled, the sections are still accessible and collapsible, but the extra buttons are hidden.
|
||||
|
||||
On page load, it checks if a hash corresponding to a details ID exists in the URL. If such an ID is found, the corresponding details section is programmatically opened, allowing direct navigation to an open section from a shared URL.
|
||||
|
||||
The sorting is set by default on "alphabetic", but you can also pass in "shuffle" or "reverse" as a parameter (directly in the `details.njk` partial).
|
||||
|
||||
### Usage
|
||||
|
||||
{% raw %}
|
||||
```jinja2
|
||||
{% set itemList = collections.docs %}
|
||||
{% set headingLevel = "h2" %} {# optional, defaults to false #}
|
||||
{% include 'partials/details.njk' %}
|
||||
```
|
||||
{% endraw %}
|
||||
|
||||
|
||||
# Images
|
||||
Using the [Eleventy Image](https://www.11ty.dev/docs/plugins/image/) plugin, there are three ways to handle image optimization: HTML Transform, Markdown syntax, and Nunjucks shortcodes. [See the dedicated blog post to dive (much) deeper.](/blog/post-with-an-image/)
|
||||
|
||||
Have a look at the [Attribute Overrides](https://www.11ty.dev/docs/plugins/image/#attribute-overrides) for the HTML Transform methods (1 and 2) for per instance overrides. Adding `eleventy:ignore` to an `<img>` element for example, skips this image.
|
||||
|
||||
### 1. HTML Transform
|
||||
The HTML Transform automatically processes `<img>` and `<picture>` elements in your HTML files as a post-processing step during the build.
|
||||
|
||||
```html
|
||||
<img src="./path/to/image.jpg" alt="alt text">
|
||||
```
|
||||
|
||||
### 2. Markdown Syntax
|
||||
|
||||
The Markdown syntax creates the `<img>` element that the _HTML Transform plugin_ is looking for, and then transforms it to the `<picture>` element (if more than one format is set).
|
||||
|
||||
```markdown
|
||||

|
||||
```
|
||||
|
||||
### 3. Nunjucks Shortcodes
|
||||
|
||||
In Nunjucks templates you can also use shortcodes (`image` and `imageKeys`).
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jinja2
|
||||
{% image '/path/to/image.jpg', 'alt text' %}
|
||||
{% imageKeys {
|
||||
"alt": "alt text",
|
||||
"src": "/path/to/image.jpg"
|
||||
} %}
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
# Custom masonry
|
||||
|
||||
`<custom-masonry>` is designed to function as a masonry grid by dynamically adjusting item positions based on the available column space and the size of its content. The necessary JavaScript (`custom-masonry.js`) is loaded only once per component usage due to the `data-island="once"` attribute.
|
||||
Optional: pass in `layout="50-50"` to set a 50% width for each column.
|
||||
|
||||
If no JavaScript is available, the grid will fall back to the regular grid layout defined in `src/assets/css/global/compositions/grid.css`.
|
||||
|
||||
```js
|
||||
<custom-masonry> (children) </custom-masonry>
|
||||
<custom-masonry layout="50-50"> (children) </custom-masonry>
|
||||
```
|
||||
|
||||
# Navigation
|
||||
|
||||
Edit your navigation items in `src/_data/navigation.js`.
|
||||
|
||||
You have two options for mobile navigation: by default, the navigation on small displays is converted to small pills that wrap. This does not require any additional JavaScript.
|
||||
|
||||
### Drawer Menu
|
||||
|
||||
You can activate a drawer menu for mobile in `src/_data/meta.js`:
|
||||
|
||||
```js
|
||||
navigation: {
|
||||
// other settings
|
||||
drawerNav: true,
|
||||
},
|
||||
```
|
||||
|
||||
`drawerNav` activates the navigation drawer, [built according to Manuel Matuzović's article on web.dev.](https://web.dev/articles/website-navigation)
|
||||
|
||||
Adjust your menu breakpoint in `src/_data/designTokens/viewports.json`
|
||||
|
||||
```json
|
||||
{
|
||||
// ...
|
||||
"navigation": 662,
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Submenu
|
||||
|
||||
You can activate submenus in `src/_data/meta.js`:
|
||||
|
||||
```js
|
||||
navigation: {
|
||||
// other settings
|
||||
subMenu: true,
|
||||
},
|
||||
```
|
||||
|
||||
This includes the JavaScript for the submenu functionality. Add your submenu items to `src/_data/navigation.js` using this structure:
|
||||
|
||||
```js
|
||||
{
|
||||
text: 'Unlinked parent',
|
||||
url: '#',
|
||||
submenu: [
|
||||
{
|
||||
text: 'Sub Item',
|
||||
url: '/sub-item/'
|
||||
},
|
||||
... more items
|
||||
]
|
||||
},
|
||||
```
|
||||
|
||||
# Pagination
|
||||
|
||||
The blog posts use [Eleventy's pagination feature](https://www.11ty.dev/docs/pagination/). The logic for this can be found in tha partial `src/_includes/partials/pagination.njk`, the layout `src/_layouts/blog.njk` includes it, how many entries should be on a page is defined in `src/pages/blog.md`.
|
||||
|
||||
If you do not want any pagination at all, it is easiest to set a very high number for the pagination size, for example:
|
||||
|
||||
```yaml
|
||||
pagination:
|
||||
data: collections.posts
|
||||
size: 10000
|
||||
```
|
||||
|
||||
In `src/_data_/meta.js` you can set some values for the visible content (previous / next buttons) and the aria labels.
|
||||
|
||||
You can also **hide the number fields** between the previous and next buttons by setting `paginationNumbers` to `false`.
|
||||
|
||||
```js
|
||||
blog: {
|
||||
// other adjustments
|
||||
paginationLabel: 'Blog',
|
||||
paginationPage: 'Page',
|
||||
paginationPrevious: 'Previous',
|
||||
paginationNext: 'Next',
|
||||
paginationNumbers: true
|
||||
}
|
||||
```
|
||||
|
||||
If you want to change the collection that is paginated (by default `collections.posts`), you must do so in two places: the front matter of the template, `src/pages/blog.md`:
|
||||
|
||||
```yaml
|
||||
pagination:
|
||||
data: collections.posts
|
||||
```
|
||||
|
||||
and where the pagination component is included: `src/_layouts/blog.njk`:
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jinja2
|
||||
<!-- set collection to paginate -->
|
||||
{% set collectionToPaginate = collections.posts %}
|
||||
<!-- if the number of items in the collection is greater than the number of items shown on one page -->
|
||||
{% if collectionToPaginate.length > pagination.size %}
|
||||
<!-- include pagination -->
|
||||
{% include 'partials/pagination.njk' %}
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
# Cards
|
||||
|
||||
WebC, we can use the custom element and opt in to the different slots.
|
||||
Available slots:
|
||||
|
||||
image: has slot="image" on the container (picture or figure) by default)
|
||||
headline: display the card's main title
|
||||
date and tag: Grouped within the classes meta and cluster for date and tagging information
|
||||
content
|
||||
footer: for links or whatever footer information
|
||||
|
||||
I added some variants, avaliable via attribute selectors:
|
||||
|
||||
img-square: Enforces a square aspect ratio for images
|
||||
clickable: Makes the whole card clickable
|
||||
no-padding: Removes padding and background modifications
|
||||
|
||||
Usage
|
||||
|
||||
{% raw %}
|
||||
```html
|
||||
<custom-card>
|
||||
{% image "path-to-img", "alt-text" %}
|
||||
<span slot="date"></span>
|
||||
<span slot="tag" class="button"></span>
|
||||
<h2 slot="headline"></h2>
|
||||
<p slot="content"></p>
|
||||
<footer slot="footer"></footer>
|
||||
</custom-card>
|
||||
|
||||
```
|
||||
{% endraw %}
|
||||
Loading…
Reference in a new issue