diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 006aa63..924249f 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,31 +1,19 @@ import type { Preview } from '@storybook/vue3-vite'; import '@/assets/styles/fonts.css'; import '@/assets/styles/main.css'; -import { withThemeByClassName, withThemeByDataAttribute } from '@storybook/addon-themes'; +import { withThemeByClassName } from '@storybook/addon-themes'; import { setup } from '@storybook/vue3-vite'; import i18ninstance from '@/composable/i18n'; export const decorators = [ withThemeByClassName({ themes: { - lightApmt: '', - darkApmt: 'dark', - lightSend: '', - darkSend: 'dark', + light: '', + dark: 'dark', }, - defaultTheme: 'lightApmt', + defaultTheme: 'light', parentSelector: 'html', }), - withThemeByDataAttribute({ - themes: { - lightApmt: 'apmt', - darkApmt: 'apmt', - lightSend: 'send', - darkSend: 'send', - }, - defaultTheme: 'lightApmt', - attributeName: 'data-tbpro-service', - }), ]; const preview: Preview = { diff --git a/src/assets/styles/colours.css b/src/assets/styles/colours.css index 976f62a..d4def6e 100644 --- a/src/assets/styles/colours.css +++ b/src/assets/styles/colours.css @@ -1,37 +1,32 @@ html { /* - * Bolt Colours * Reference: https://bolt.thunderbird.net/8b179dbfd/p/61e4c7-color */ - --colour-user-red: #e53935; - --colour-user-orange: #ff8c00; - --colour-user-yellow: #f4c430; - --colour-user-graphite: #b0b0b0; - --colour-user-green: #34c759; - --colour-user-blue: #0a84ff; - --colour-user-purple: #7753cc; - --colour-user-pink: #e91e63; - /* - * Reference: https://www.figma.com/design/3pWlMatoNdA2BT3BPVziFL/ - */ + --bolt-design-synced: "2026-09"; /* - * Generic Colours - * Prefer the named colours over these! + * Neutral (DEPRECATED, superseded by Surface) – Delete me once we're sure no one else is using it. + * Applied frequently for backgrounds, surfaces, dividers, and outlines. */ - --colour-neutral-900: #26272a; + --colour-neutral-base: #fcfcfd; + --colour-neutral-raised: #ffffff; + --colour-neutral-lower: #f7f7f8; + --colour-neutral-subtle: #efeff1; + --colour-neutral-border: #d9d9de; + --colour-neutral-border-intense: #a1a1aa; /* - * Neutral + * Surface * Applied frequently for backgrounds, surfaces, dividers, and outlines. */ - --colour-neutral-base: #feffff; - --colour-neutral-lower: #f7f7f7; - --colour-neutral-raised: var(--colour-neutral-base); - --colour-neutral-subtle: var(--colour-neutral-lower); - --colour-neutral-border: #e4e4e7; - --colour-neutral-border-intense: #777580; + --colour-surface-base: #fcfcfd; + --colour-surface-raised: #ffffff; + --colour-surface-lower: #f7f7f8; + --colour-surface-subtle: #efeff1; + --colour-surface-deep: #e4e4e7; + --colour-surface-border: #d9d9de; + --colour-surface-border-intense: #a1a1aa; /* * Primary @@ -45,15 +40,6 @@ html { --colour-primary-hover-light: #175fb6; /* No override */ --colour-primary-pressed: #19518f; - /* - * Secondary - * Applied sparingly to highlight information. - */ - --colour-secondary-soft: #faf5ff; - --colour-secondary-default: #6b21a8; - --colour-secondary-hover: #571c87; - --colour-secondary-pressed: #3a0764; - /* * Success * Applied sparingly to show positive feedback/status. @@ -74,7 +60,7 @@ html { --colour-warning-pressed: #caa204; /* - * Danger + * Danger (DEPRECATED, superseded by Critical) – Delete me once we're sure no one else is using it. * Applied sparingly to show negative feedback/status or dangerous actions. * Errors */ @@ -83,19 +69,32 @@ html { --colour-danger-hover: #7f1d1d; --colour-danger-pressed: #450a0a; + /* + * Critical + * Applied sparingly to show negative feedback/status or dangerous actions. + * Errors + */ + --colour-critical-soft: #fef2f2; + --colour-critical-default: #991b1b; + --colour-critical-hover: #7f1d1d; + --colour-critical-pressed: #450a0a; + /* * Text+Icon * Applied frequently for text and icons. */ - --colour-ti-base: #1a202c; - --colour-ti-base-light: #1a202c; /* No override */ + --colour-ti-base: #18181b; + --colour-ti-base-light: #18181b; /* No override */ --colour-ti-base-dark: #eeeef0; /* No override */ - --colour-ti-secondary: #4c4d58; - --colour-ti-muted: #737584; + --colour-ti-base-inverted: #fcfcfd; + --colour-ti-secondary: #52525b; + --colour-ti-muted: #71717a; + --colour-ti-disabled: #a1a1aa; + --colour-ti-brand: #0b5cbd; --colour-ti-highlight: #1373d9; --colour-ti-warning: #713f12; - --colour-ti-critical: #dc2626; - --colour-ti-success: #1d783b; + --colour-ti-critical: #b91c1c; + --colour-ti-success: #1c5f32; /* * Accent Colours @@ -103,47 +102,35 @@ html { * *Do not use with text */ --colour-accent-teal: #25a6a0; - --colour-accent-blue: #37adf9; + --colour-accent-blue: #58c9ff; --colour-accent-purple: #ae55f7; --colour-accent-orange: #ea7308; --colour-accent-pink: #e247c4; --colour-accent-gray: #6e6f9b; - /* - * Brand Colours - * Appointment - */ - --colour-apmt-soft: #f3f9fc; - --colour-apmt-primary: #008080; - --colour-apmt-primary-hover: #066769; - --colour-apmt-primary-pressed: #0a5757; - --colour-apmt-secondary: #81d4b5; - --colour-apmt-accent-1: #a3ece3; - --colour-apmt-accent-2: #03afd7; - - /* - * Brand Colours - * Send - */ - --colour-send-soft: #fef7ec; - --colour-send-primary: #f09d38; - --colour-send-primary-hover: #f09d38; - --colour-send-primary-pressed: #f09d38; - --colour-send-primary-secondary: #aa6000; - --colour-send-primary-accent-1: #1f8ddb; - --colour-send-primary-accent-2: #1f8ddb; - &.dark { /* - * Neutral + * Neutral (DEPRECATED, superseded by Surface) – Delete me once we're sure no one else is using it. + * Applied frequently for backgrounds, surfaces, dividers, and outlines. + */ + --colour-neutral-base: #18181b; + --colour-neutral-raised: #2b2b2f; + --colour-neutral-lower: #111113; + --colour-neutral-subtle: #232326; + --colour-neutral-border: #52525b; + --colour-neutral-border-intense: #71717a; + + /* + * Surface * Applied frequently for backgrounds, surfaces, dividers, and outlines. */ - --colour-neutral-base: #1a202c; - --colour-neutral-lower: #18181b; - --colour-neutral-raised: #262d3b; - --colour-neutral-subtle: var(--colour-neutral-raised); - --colour-neutral-border: #303a4a; - --colour-neutral-border-intense: #3d4d67; + --colour-surface-base: #18181b; + --colour-surface-raised: #2b2b2f; + --colour-surface-lower: #111113; + --colour-surface-subtle: #232326; + --colour-surface-deep: #3f3f46; + --colour-surface-border: #52525b; + --colour-surface-border-intense: #71717a; /* * Primary @@ -155,15 +142,6 @@ html { --colour-primary-pressed: #1b90f5; /*--primary-text: #1A202C; // No matching light-mode variable! */ - /* - * Secondary - * Applied sparingly to highlight information. - */ - --colour-secondary-soft: #3a0764; - --colour-secondary-default: #f5e8ff; - --colour-secondary-hover: #ecd5ff; - --colour-secondary-pressed: #ddb4fe; - /* * Success * Applied sparingly to show positive feedback/status. @@ -171,7 +149,7 @@ html { --colour-success-soft: #082b16; --colour-success-default: #defae7; --colour-success-hover: #c0f2cf; - --colour-success-pressed: #8ee7aa; + --colour-success-pressed: #81d4b5; /* * Warning @@ -184,25 +162,37 @@ html { --colour-warning-pressed: #fdd847; /* - * Danger + * Danger (DEPRECATED, superseded by Critical) – Delete me once we're sure no one else is using it. * Applied sparingly to show negative feedback/status or dangerous actions. * Errors */ --colour-danger-soft: #7f1d1d; - --colour-danger-default: #fee2e2; - --colour-danger-hover: #fecaca; - --colour-danger-pressed: #fca5a5; + --colour-danger-default: #fca5a5; + --colour-danger-hover: #f87171; + --colour-danger-pressed: #f05252; + + /* + * Critical + * Applied sparingly to show negative feedback/status or dangerous actions. + * Errors + */ + --colour-critical-soft: #7f1d1d; + --colour-critical-default: #fca5a5; + --colour-critical-hover: #f87171; + --colour-critical-pressed: #f05252; /* * Text+Icon * Applied frequently for text and icons. */ - --colour-ti-base: #eeeef0; - --colour-ti-secondary: #d9d9de; - --colour-ti-muted: #7f94ac; + --colour-ti-base: #f7f7f8; + --colour-ti-secondary: #d4d4d8; + --colour-ti-muted: #a1a1aa; + --colour-ti-disabled: #71717a; + --colour-ti-brand: #5fa6e8; --colour-ti-highlight: #58c9ff; --colour-ti-warning: #fee78a; - --colour-ti-critical: #ef4444; + --colour-ti-critical: #f87171; --colour-ti-success: #55d37e; /* @@ -216,72 +206,5 @@ html { --colour-accent-orange: #fec18a; --colour-accent-pink: #fbd3f6; --colour-accent-gray: #c3cade; - - /* - * Brand Colours - * Appointment - */ - --colour-apmt-soft: #152f3c; - --colour-apmt-primary: #a3ece3; - --colour-apmt-primary-hover: #6ddbd1; - --colour-apmt-primary-pressed: #3ec3ba; - --colour-apmt-secondary: #25a6a0; - --colour-apmt-accent-1: #a3ece3; - --colour-apmt-accent-2: #58c9ff; - - /* - * Brand Colours - * Send - */ - --colour-send-soft: #411107; - --colour-send-primary: #f09d38; - --colour-send-primary-hover: #f09d38; - --colour-send-primary-pressed: #f09d38; - --colour-send-primary-secondary: #aa6000; - --colour-send-primary-accent-1: #1f8ddb; - --colour-send-primary-accent-2: #1f8ddb; - } - - /* - * Brand Colours - * Thunderbird - * The fallback if no service is set. - */ - --colour-service-soft: var(--colour-primary-soft); - --colour-service-primary: var(--colour-primary-default); - --colour-service-primary-hover: var(--colour-primary-hover); - --colour-service-primary-pressed: var(--colour-primary-pressed); - --colour-service-secondary: var(--colour-secondary-default); - --colour-service-accent-1: var(--colour-accent-purple); - --colour-service-accent-2: var(--colour-accent-teal); - - &[data-tbpro-service='apmt'] { - --colour-service-soft: var(--colour-apmt-soft); - --colour-service-primary: var(--colour-apmt-primary); - --colour-service-primary-hover: var(--colour-apmt-primary-hover); - --colour-service-primary-pressed: var(--colour-apmt-primary-pressed); - --colour-service-secondary: var(--colour-apmt-secondary); - --colour-service-accent-1: var(--colour-apmt-accent-1); - --colour-service-accent-2: var(--colour-apmt-accent-2); - } - &[data-tbpro-service='send'] { - --colour-service-soft: var(--colour-primary-soft); - --colour-service-primary: var(--colour-primary-default); - --colour-service-primary-hover: var(--colour-primary-hover); - --colour-service-primary-pressed: var(--colour-primary-pressed); - --colour-service-secondary: var(--colour-neutral-subtle); - --colour-service-accent-1: var(--colour-send-primary-accent-1); - --colour-service-accent-2: var(--colour-send-primary-accent-2); - } - /* - &[data-tbpro-service='send'] { - --colour-service-soft: var(--colour-send-soft); - --colour-service-primary: var(--colour-send-primary); - --colour-service-primary-hover: var(--colour-send-primary-hover); - --colour-service-primary-pressed: var(--colour-send-primary-pressed); - --colour-service-secondary: var(--colour-send-primary-secondary); - --colour-service-accent-1: var(--colour-send-primary-accent-1); - --colour-service-accent-2: var(--colour-send-primary-accent-2); } - */ } diff --git a/src/components/BaseButton.vue b/src/components/BaseButton.vue index b2b84a9..2936311 100644 --- a/src/components/BaseButton.vue +++ b/src/components/BaseButton.vue @@ -322,7 +322,7 @@ button:hover > .tooltip { background-color: transparent; /* color-mix darkens the text just enough to meet 4.5:1 contrast */ - color: color-mix(in srgb, var(--colour-service-primary) 92%, black); + color: color-mix(in srgb, var(--colour-primary-default) 92%, black); text-decoration: underline; box-shadow: none !important; border: none !important; diff --git a/src/components/LoadingSkeleton.vue b/src/components/LoadingSkeleton.vue index 606f5e3..a6325fb 100644 --- a/src/components/LoadingSkeleton.vue +++ b/src/components/LoadingSkeleton.vue @@ -47,7 +47,7 @@ const props = withDefaults(defineProps(), { &.scan { background: var(--colour-ti-base-dark) - linear-gradient(90deg, transparent 25%, var(--colour-user-graphite) 50%, transparent 75%); + linear-gradient(90deg, transparent 25%, var(--colour-surface-border-intense) 50%, transparent 75%); background-size: 1000% 1000%; @media (prefers-reduced-motion: no-preference) { @@ -89,7 +89,7 @@ const props = withDefaults(defineProps(), { @keyframes pulse-animation { 50% { - box-shadow: 0 0 0 0 var(--colour-user-graphite); + box-shadow: 0 0 0 0 var(--colour-surface-border-intense); } 100% { box-shadow: 0 0 0 20px transparent; diff --git a/src/components/SegmentedControl.vue b/src/components/SegmentedControl.vue index 794fa29..1a03ce6 100644 --- a/src/components/SegmentedControl.vue +++ b/src/components/SegmentedControl.vue @@ -138,7 +138,8 @@ const setOption = (option: SelectOption) => { height: 1.5rem; min-width: 1.5rem; border-radius: 1.5rem; - background-color: rgba(19, 115, 217, 0.4); /* TODO: One off color from Zeplin */ + background-color: var(--colour-surface-subtle); + color: var(--colour-ti-secondary); padding: 0 0.5rem; box-sizing: border-box; diff --git a/src/components/SyncCard.vue b/src/components/SyncCard.vue index c5ed34a..901b7d7 100644 --- a/src/components/SyncCard.vue +++ b/src/components/SyncCard.vue @@ -60,7 +60,7 @@ withDefaults(defineProps(), { } .wrapper { - --colour-highlight: var(--colour-service-primary); + --colour-highlight: var(--colour-primary-default); border-radius: 0.5625rem; border: 0.0625rem solid var(--colour-neutral-border); background: var(--colour-neutral-base); @@ -71,7 +71,7 @@ withDefaults(defineProps(), { .dark { .wrapper { - --colour-highlight: var(--colour-service-secondary); + --colour-highlight: var(--colour-primary-default-dark); } } diff --git a/src/components/UserAvatar.vue b/src/components/UserAvatar.vue index 8536849..5ab9e74 100644 --- a/src/components/UserAvatar.vue +++ b/src/components/UserAvatar.vue @@ -49,9 +49,9 @@ const usernameInitial = computed(() => { justify-content: center; align-items: center; - border: solid 1px var(--colour-user-blue); + border: solid 1px var(--colour-primary-default); border-radius: 100%; - background-color: rgba(10, 132, 255, 0.15); + background-color: color-mix(in srgb, var(--colour-primary-default) 15%, transparent); .img { width: 100%; diff --git a/src/foundation/Colours.stories.ts b/src/foundation/Colours.stories.ts new file mode 100644 index 0000000..8d4e3ed --- /dev/null +++ b/src/foundation/Colours.stories.ts @@ -0,0 +1,103 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite'; + +import ColourPalette, { type ColourGroup } from '@/stories/ColourPalette.vue'; + +// Keep in sync with src/assets/styles/colours.css, use the theme switcher to preview dark mode. +// Reference: https://bolt.thunderbird.net/8b179dbfd/p/61e4c7-color +const tokens = (prefix: string, names: string[]) => names.map((name) => `--colour-${prefix}-${name}`); +const states = ['soft', 'default', 'hover', 'pressed']; + +const surface: ColourGroup = { + name: 'Surface', + description: 'Applied frequently for backgrounds, surfaces, dividers, and outlines.', + tokens: tokens('surface', ['base', 'raised', 'lower', 'subtle', 'deep', 'border', 'border-intense']), +}; + +const primary: ColourGroup = { + name: 'Primary', + description: 'Applied frequently to surfaces highlight actions & information.', + tokens: tokens('primary', [...states, 'default-light', 'default-dark', 'hover-light']), +}; + +const success: ColourGroup = { + name: 'Success', + description: 'Applied sparingly to show positive feedback/status.', + tokens: tokens('success', states), +}; + +const warning: ColourGroup = { + name: 'Warning', + description: 'Applied sparingly to show warning feedback/status. Notifications/messaging.', + tokens: tokens('warning', states), +}; + +const critical: ColourGroup = { + name: 'Critical', + description: 'Applied sparingly to show negative feedback/status or dangerous actions. Errors.', + tokens: tokens('critical', states), +}; + +const textAndIcon: ColourGroup = { + name: 'Text+Icon', + description: 'Applied frequently for text and icons.', + tokens: tokens('ti', [ + 'base', + 'base-light', + 'base-dark', + 'base-inverted', + 'secondary', + 'muted', + 'disabled', + 'brand', + 'highlight', + 'warning', + 'critical', + 'success', + ]), +}; + +const accent: ColourGroup = { + name: 'Accent Colours', + description: 'Used for accents, gradients, highlights. Do not use with text.', + tokens: tokens('accent', ['teal', 'blue', 'purple', 'orange', 'pink', 'gray']), +}; + +const neutral: ColourGroup = { + name: 'Neutral', + description: 'Superseded by Surface.', + deprecated: true, + tokens: tokens('neutral', ['base', 'lower', 'raised', 'subtle', 'border', 'border-intense']), +}; + +const danger: ColourGroup = { + name: 'Danger', + description: 'Superseded by Critical.', + deprecated: true, + tokens: tokens('danger', states), +}; + +const meta: Meta = { + title: 'Foundational Items/Colours', + component: ColourPalette, + tags: ['autodocs'], + parameters: { + docs: { canvas: { sourceState: 'none' } }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const All: Story = { + args: { groups: [surface, primary, success, warning, critical, textAndIcon, accent, neutral, danger] }, +}; + +export const Surface: Story = { args: { groups: [surface] } }; +export const Primary: Story = { args: { groups: [primary] } }; +export const Success: Story = { args: { groups: [success] } }; +export const Warning: Story = { args: { groups: [warning] } }; +export const Critical: Story = { args: { groups: [critical] } }; +export const TextAndIcon: Story = { args: { groups: [textAndIcon] } }; +export const Accent: Story = { args: { groups: [accent] } }; +export const DeprecatedNeutral: Story = { args: { groups: [neutral] } }; +export const DeprecatedDanger: Story = { args: { groups: [danger] } }; diff --git a/src/stories/ColourPalette.vue b/src/stories/ColourPalette.vue new file mode 100644 index 0000000..8fae9cd --- /dev/null +++ b/src/stories/ColourPalette.vue @@ -0,0 +1,122 @@ + + + + +