- JavaScript 52.4%
- PHP 14.8%
- Shell 13.2%
- CSS 11%
- Twig 8.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
Propagate to consumers / Re-resolve the bundle in every consumer (push) Successful in 1m31s
Reviewed-on: #35 |
||
| .claude | ||
| .github/workflows | ||
| assets | ||
| bin | ||
| config | ||
| docs | ||
| src | ||
| templates | ||
| tools | ||
| .gitignore | ||
| .gitmodules | ||
| CLAUDE.md | ||
| composer.json | ||
| README.md | ||
grey.ooo brand kit
grey-ooo/brand-kit — a small Symfony bundle that ships the shared grey.ooo chrome: a
consistent top bar, an app switcher, and an app shell (top bar + left sidebar + centered main +
footer). Drop it into any Grey.ooo Symfony app so they all wear the same skin.
The chrome is self-contained: all styles are namespaced under .gooo-* and driven by
--gooo-* custom properties, so it renders identically whatever CSS/Tailwind setup the host app
uses, and needs no build step. It is dark-first, matching the someones.computer aesthetic.
Install
composer require grey-ooo/brand-kit
Register the bundle (config/bundles.php):
GreyOoo\BrandKit\GreyOooBrandKitBundle::class => ['all' => true],
Configure it (config/packages/grey_ooo_brand_kit.yaml) — normally just the one line
naming which app this is:
grey_ooo_brand_kit:
current_app: printshop
The app roster is owned by the bundle, not by consumers: it defaults to
GreyOoo\BrandKit\Config\GreyOooApps::ALL, rather than the same list repeated in every
repo where the copies drift.
That constant is generated, not hand-edited. An app is in the roster because its own
repo on git.grey.ooo carries the grey-ooo-app-roster topic, and its URL is that repo's
website field; a nightly job regenerates the constant and opens the PR. Adding an app is
a topic on its repo plus a name and icon in tools/roster/presentation.mjs, then a
composer update grey-ooo/brand-kit in each consumer. See
tools/roster/README.md.
You do not set external per app either. Whether an entry is "another app" depends on
who is rendering, so it is derived from current_app: every entry is external except the
one being served. Set it explicitly only to force a full page load.
Each entry carries an icon, in one of two forms — the switcher, the brand mark and the
styleguide all render it through components/_app_icon.html.twig, so they agree:
| Field | ||
|---|---|---|
icon |
An emoji. The default, and enough for an app with no mark of its own | escaped |
icon_svg |
Inline SVG, superseding icon for an app that has a mark — someones ships the terminal display it draws in its own appbar, niall the face he talks with |
printed raw |
Give icon_svg geometry only (a viewBox, no width/height): brand-kit.css sizes it
in em so a mark and an emoji sit at one optical scale side by side. It is the only field
the kit prints unescaped, so it takes bundle or app config and nothing a request can reach.
An entry may also carry staging_url, which gives its row a blade: a second, amber
surface behind the button showing a sliver past the right edge, sliding out from under it
on hover (and simply sitting out on touch, where a 4px sliver is not a tap target). It
reads as one app with another copy behind it rather than as two apps, which is why it is
drawn behind the row and not beside it. The blade lives in the shared roster, so it shows
in every app's switcher — a staging box linked here is a shortcut, not a hiding place.
Override the list only when you genuinely need a different one (staging URLs, a local
sandbox); apps still accepts a full replacement, and brand still tunes the wordmark:
grey_ooo_brand_kit:
current_app: printshop
brand: { name: grey, suffix: .ooo }
apps:
- { key: printshop, name: 'PrintShop (staging)', url: 'https://staging.printshop.grey.ooo', icon: '🖨️' }
Expose the bundle's assets to AssetMapper (config/packages/asset_mapper.yaml):
framework:
asset_mapper:
paths:
'%kernel.project_dir%/vendor/grey-ooo/brand-kit/assets': grey-ooo-brand-kit
Then load the CSS + JS. Link the stylesheet in your base template <head> and pull the script in
via your importmap.php / app.js (or a <script type="module">):
<link rel="stylesheet" href="{{ asset('grey-ooo-brand-kit/styles/brand-kit.css') }}">
Use
<twig:GreyOoo:Shell current-app="printshop">
{% block topbar %}
<twig:GreyOoo:TopBar current-app="printshop">
{# host app actions: notifications, user menu, … #}
</twig:GreyOoo:TopBar>
{% endblock %}
{% block sidebar %}
<twig:GreyOoo:NavItem href="{{ path('dashboard') }}" label="Dashboard" :active="true">
{% block icon %}<twig:ux:icon name="lucide:layout-dashboard"/>{% endblock %}
</twig:GreyOoo:NavItem>
{# … #}
{% endblock %}
{% block content %}{# page content #}{% endblock %}
{% block footer %}© grey.ooo{% endblock %}
</twig:GreyOoo:Shell>
Icons are passed via the icon block so the bundle stays independent of any icon set.
The sidebar is optional: omit the sidebar block and the shell renders without a rail and
without the drawer toggle, with main spanning the full width. An app with a single page
(Niall.chat) has nothing to navigate to, so it leaves the block out.
Components
| Component | Purpose |
|---|---|
GreyOoo:Shell |
Page scaffold — topbar / sidebar (optional) / content / footer blocks. |
GreyOoo:TopBar |
The grey.ooo bar: mark + wordmark + app switcher, with a right-hand actions slot (default content). |
GreyOoo:AppSwitcher |
Grid-button dropdown of the configured apps. |
GreyOoo:NavItem |
A sidebar link (href, label, active, icon block). |
The resolved config is also available in templates as the gooo_brand_kit global.
Style guide
Import the bundle's routes (dev only) to browse a living style guide at /_brand-kit/styleguide:
# config/routes/grey_ooo_brand_kit.yaml
grey_ooo_brand_kit:
resource: '@GreyOooBrandKitBundle/config/routes.php'