001 <script lang="ts" module>
002 import type { HTMLAttributes } from 'svelte/elements';
003
004 export type CardProps = HTMLAttributes<HTMLElement> & {
005 ref?: HTMLElement | null;
006 title?: string;
007 alignTitle?: 'left' | 'right';
008 };
009 </script>
010
011 <script lang="ts">
012 let {
013 class: className = '',
014 ref = $bindable(null),
015 title = '',
016 alignTitle = 'left',
017 children,
018 ...restProps
019 }: CardProps = $props();
020 </script>
021
022 {#snippet titleElement()}
023 <header class="action">
024 {#if alignTitle === 'left'}
025 <div class="leftCorner" aria-hidden="true"></div>
026 {:else}
027 <div class="left" aria-hidden="true"></div>
028 {/if}
029 {#if title}<h2 class="title">{title}</h2>{/if}
030 {#if alignTitle === 'right'}
031 <div class="rightCorner" aria-hidden="true"></div>
032 {:else}
033 <div class="right" aria-hidden="true"></div>
034 {/if}
035 </header>
036 {/snippet}
037
038 <article class="card {className}" bind:this={ref} {...restProps}>
039 {@render titleElement()}
040 <section class="children">{@render children?.()}</section>
041 </article>
042
043 <style lang="postcss">
044 .card {
045 position: relative;
046 display: block;
047 padding: 0;
048 }
049
050 /*
051 The header pieces and the body both paint their borders with inset
052 box-shadows (no CSS border). Same primitive on both sides of the seam
053 means the 2px lines land in the same place and stay continuous.
054 */
055 .children {
056 box-shadow:
057 inset 2px 0 0 0 var(--text-primary),
058 inset -2px 0 0 0 var(--text-primary),
059 inset 0 -2px 0 0 var(--text-primary);
060 display: block;
061 padding-top: var(--line);
062 padding-left: 2ch;
063 padding-right: 2ch;
064 padding-bottom: var(--line);
065 overflow-x: auto;
066 overflow-y: hidden;
067 margin: 0 !important;
068
069 &::-webkit-scrollbar {
070 display: none;
071 }
072
073 scrollbar-width: none;
074 }
075
076 .action {
077 display: flex;
078 align-items: flex-end;
079 justify-content: space-between;
080 }
081
082 .left {
083 min-width: 10%;
084 width: 100%;
085 box-shadow:
086 inset 2px 0 0 0 var(--text-primary),
087 inset 0 2px 0 0 var(--text-primary);
088 padding: var(--line) 2ch 0px 1ch;
089 }
090
091 .leftCorner {
092 flex-shrink: 0;
093 box-shadow:
094 inset 2px 0 0 0 var(--text-primary),
095 inset 0 2px 0 0 var(--text-primary);
096 padding: var(--line) 1ch 0px 1ch;
097 }
098
099 .right {
100 min-width: 10%;
101 width: 100%;
102 box-shadow:
103 inset -2px 0 0 0 var(--text-primary),
104 inset 0 2px 0 0 var(--text-primary);
105 padding: var(--line) 2ch 0px 1ch;
106 }
107
108 .rightCorner {
109 flex-shrink: 0;
110 box-shadow:
111 inset -2px 0 0 0 var(--text-primary),
112 inset 0 2px 0 0 var(--text-primary);
113 padding: var(--line) 1ch 0px 1ch;
114 }
115
116 .title {
117 font-size: inherit;
118 font-weight: inherit;
119 line-height: inherit;
120 margin: 0;
121 padding: 0 1ch;
122 flex-shrink: 0;
123 }
124 </style>
125