Browse Source

wip new UI with spectre

Ondřej Hruška 3 months ago
parent
commit
4a51f7d3c2
Signed by: Ondřej Hruška <ondra@ondrovo.com> GPG key ID: 2C5FD5035250423D
95 changed files with 20592 additions and 624 deletions
  1. 0 1
      Cargo.toml
  2. 0 19
      yopa-test/Cargo.toml
  3. 0 120
      yopa-test/src/main.rs
  4. 13 5
      yopa-web/resources/src/components/EditObjectForm.vue
  5. 36 22
      yopa-web/resources/src/components/EditPropertyField.vue
  6. 29 16
      yopa-web/resources/src/components/EditRelationForm.vue
  7. 2 2
      yopa-web/resources/src/components/PropertyField.vue
  8. 4 1
      yopa-web/resources/src/components/value/BooleanValue.vue
  9. 1 1
      yopa-web/resources/src/components/value/DecimalValue.vue
  10. 1 1
      yopa-web/resources/src/components/value/IntegerValue.vue
  11. 1 1
      yopa-web/resources/src/components/value/StringValue.vue
  12. 4 4
      yopa-web/resources/src/main.js
  13. 21 0
      yopa-web/resources/src/style/_icons-extra.scss
  14. 27 0
      yopa-web/resources/src/style/_spectre-patches.scss
  15. 14 1
      yopa-web/resources/src/style/app.scss
  16. 21 0
      yopa-web/resources/src/style/spectre/LICENSE
  17. 38 0
      yopa-web/resources/src/style/spectre/src/_accordions.scss
  18. 20 0
      yopa-web/resources/src/style/spectre/src/_animations.scss
  19. 43 0
      yopa-web/resources/src/style/spectre/src/_asian.scss
  20. 47 0
      yopa-web/resources/src/style/spectre/src/_autocomplete.scss
  21. 77 0
      yopa-web/resources/src/style/spectre/src/_avatars.scss
  22. 60 0
      yopa-web/resources/src/style/spectre/src/_badges.scss
  23. 71 0
      yopa-web/resources/src/style/spectre/src/_bars.scss
  24. 44 0
      yopa-web/resources/src/style/spectre/src/_base.scss
  25. 29 0
      yopa-web/resources/src/style/spectre/src/_breadcrumbs.scss
  26. 193 0
      yopa-web/resources/src/style/spectre/src/_buttons.scss
  27. 222 0
      yopa-web/resources/src/style/spectre/src/_calendars.scss
  28. 43 0
      yopa-web/resources/src/style/spectre/src/_cards.scss
  29. 136 0
      yopa-web/resources/src/style/spectre/src/_carousels.scss
  30. 33 0
      yopa-web/resources/src/style/spectre/src/_chips.scss
  31. 31 0
      yopa-web/resources/src/style/spectre/src/_codes.scss
  32. 116 0
      yopa-web/resources/src/style/spectre/src/_comparison-sliders.scss
  33. 36 0
      yopa-web/resources/src/style/spectre/src/_dropdowns.scss
  34. 21 0
      yopa-web/resources/src/style/spectre/src/_empty.scss
  35. 37 0
      yopa-web/resources/src/style/spectre/src/_filters.scss
  36. 555 0
      yopa-web/resources/src/style/spectre/src/_forms.scss
  37. 22 0
      yopa-web/resources/src/style/spectre/src/_hero.scss
  38. 5 0
      yopa-web/resources/src/style/spectre/src/_icons.scss
  39. 34 0
      yopa-web/resources/src/style/spectre/src/_labels.scss
  40. 446 0
      yopa-web/resources/src/style/spectre/src/_layout.scss
  41. 75 0
      yopa-web/resources/src/style/spectre/src/_media.scss
  42. 66 0
      yopa-web/resources/src/style/spectre/src/_menus.scss
  43. 57 0
      yopa-web/resources/src/style/spectre/src/_meters.scss
  44. 10 0
      yopa-web/resources/src/style/spectre/src/_mixins.scss
  45. 87 0
      yopa-web/resources/src/style/spectre/src/_modals.scss
  46. 28 0
      yopa-web/resources/src/style/spectre/src/_navbar.scss
  47. 34 0
      yopa-web/resources/src/style/spectre/src/_navs.scss
  48. 446 0
      yopa-web/resources/src/style/spectre/src/_normalize.scss
  49. 95 0
      yopa-web/resources/src/style/spectre/src/_off-canvas.scss
  50. 60 0
      yopa-web/resources/src/style/spectre/src/_pagination.scss
  51. 23 0
      yopa-web/resources/src/style/spectre/src/_panels.scss
  52. 135 0
      yopa-web/resources/src/style/spectre/src/_parallax.scss
  53. 65 0
      yopa-web/resources/src/style/spectre/src/_popovers.scss
  54. 45 0
      yopa-web/resources/src/style/spectre/src/_progress.scss
  55. 99 0
      yopa-web/resources/src/style/spectre/src/_sliders.scss
  56. 71 0
      yopa-web/resources/src/style/spectre/src/_steps.scss
  57. 57 0
      yopa-web/resources/src/style/spectre/src/_tables.scss
  58. 66 0
      yopa-web/resources/src/style/spectre/src/_tabs.scss
  59. 38 0
      yopa-web/resources/src/style/spectre/src/_tiles.scss
  60. 56 0
      yopa-web/resources/src/style/spectre/src/_timelines.scss
  61. 48 0
      yopa-web/resources/src/style/spectre/src/_toasts.scss
  62. 79 0
      yopa-web/resources/src/style/spectre/src/_tooltips.scss
  63. 129 0
      yopa-web/resources/src/style/spectre/src/_typography.scss
  64. 8 0
      yopa-web/resources/src/style/spectre/src/_utilities.scss
  65. 117 0
      yopa-web/resources/src/style/spectre/src/_variables.scss
  66. 34 0
      yopa-web/resources/src/style/spectre/src/_viewer-360.scss
  67. 315 0
      yopa-web/resources/src/style/spectre/src/icons/_icons-action.scss
  68. 54 0
      yopa-web/resources/src/style/spectre/src/icons/_icons-core.scss
  69. 128 0
      yopa-web/resources/src/style/spectre/src/icons/_icons-navigation.scss
  70. 161 0
      yopa-web/resources/src/style/spectre/src/icons/_icons-object.scss
  71. 6 0
      yopa-web/resources/src/style/spectre/src/mixins/_avatar.scss
  72. 54 0
      yopa-web/resources/src/style/spectre/src/mixins/_button.scss
  73. 8 0
      yopa-web/resources/src/style/spectre/src/mixins/_clearfix.scss
  74. 27 0
      yopa-web/resources/src/style/spectre/src/mixins/_color.scss
  75. 11 0
      yopa-web/resources/src/style/spectre/src/mixins/_label.scss
  76. 65 0
      yopa-web/resources/src/style/spectre/src/mixins/_position.scss
  77. 9 0
      yopa-web/resources/src/style/spectre/src/mixins/_shadow.scss
  78. 6 0
      yopa-web/resources/src/style/spectre/src/mixins/_text.scss
  79. 5 0
      yopa-web/resources/src/style/spectre/src/mixins/_toast.scss
  80. 18 0
      yopa-web/resources/src/style/spectre/src/spectre-exp.scss
  81. 10 0
      yopa-web/resources/src/style/spectre/src/spectre-icons.scss
  82. 49 0
      yopa-web/resources/src/style/spectre/src/spectre.scss
  83. 31 0
      yopa-web/resources/src/style/spectre/src/utilities/_colors.scss
  84. 24 0
      yopa-web/resources/src/style/spectre/src/utilities/_cursors.scss
  85. 44 0
      yopa-web/resources/src/style/spectre/src/utilities/_display.scss
  86. 50 0
      yopa-web/resources/src/style/spectre/src/utilities/_divider.scss
  87. 37 0
      yopa-web/resources/src/style/spectre/src/utilities/_loading.scss
  88. 54 0
      yopa-web/resources/src/style/spectre/src/utilities/_position.scss
  89. 8 0
      yopa-web/resources/src/style/spectre/src/utilities/_shapes.scss
  90. 76 0
      yopa-web/resources/src/style/spectre/src/utilities/_text.scss
  91. 10411 1
      yopa-web/resources/static/bundle.js
  92. 1 1
      yopa-web/resources/static/bundle.js.map
  93. 4083 158
      yopa-web/resources/static/style.css
  94. 0 270
      yopa-web/resources/static/test_form.html
  95. 286 0
      yopa-web/resources/test/edit_object.html

+ 0 - 1
Cargo.toml View File

@@ -3,5 +3,4 @@
3 3
 members = [
4 4
     "yopa",
5 5
     "yopa-web",
6
-    #"yopa-test",
7 6
 ]

+ 0 - 19
yopa-test/Cargo.toml View File

@@ -1,19 +0,0 @@
1
-[package]
2
-name = "yopa-test"
3
-version = "0.1.0"
4
-authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
5
-edition = "2018"
6
-
7
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8
-
9
-[dependencies]
10
-log = "0.4.13"
11
-simple-logging = "2.0.2"
12
-
13
-yopa = { path = "../yopa", features = [ "uuid-ids" ] }
14
-
15
-serde_json = "1.0.61"
16
-serde = { version = "1.0.120", features = ["derive"] }
17
-
18
-anyhow = "1.0.38"
19
-thiserror = "1.0.23"

+ 0 - 120
yopa-test/src/main.rs View File

@@ -1,120 +0,0 @@
1
-#[macro_use] extern crate log;
2
-
3
-use log::LevelFilter;
4
-
5
-use yopa::{model, InMemoryStorage};
6
-use yopa::model::DataType;
7
-use yopa::insert::{InsertObj, InsertValue, InsertRel};
8
-use yopa::data::TypedValue;
9
-
10
-fn main() {
11
-    simple_logging::log_to_stderr(LevelFilter::Debug);
12
-
13
-    main_test_recipes().unwrap();
14
-}
15
-
16
-#[allow(non_snake_case)]
17
-fn main_test_recipes() -> anyhow::Result<()> {
18
-    simple_logging::log_to_stderr(LevelFilter::Debug);
19
-
20
-    let mut store = yopa::InMemoryStorage::new();
21
-
22
-    let Recipe = store.define_object(model::ObjectModel {
23
-        id: Default::default(),
24
-        name: "recipe".to_string(),
25
-        parent: None
26
-    })?;
27
-
28
-    let RecipeTitle = store.define_property(model::PropertyModel {
29
-        id: Default::default(),
30
-        object: Recipe,
31
-        name: "title".to_string(),
32
-        optional: false,
33
-        multiple: false,
34
-        data_type: DataType::String,
35
-        default: None
36
-    })?;
37
-
38
-    let _PrepHours = store.define_property(model::PropertyModel {
39
-        id: Default::default(),
40
-        object: Recipe,
41
-        name: "prep_hours".to_string(),
42
-        optional: true,
43
-        multiple: false,
44
-        data_type: DataType::Decimal,
45
-        default: None
46
-    })?;
47
-
48
-    let Book = store.define_object(model::ObjectModel {
49
-        id: Default::default(),
50
-        name: "book".to_string(),
51
-        parent: None
52
-    })?;
53
-
54
-    let BookName = store.define_property(model::PropertyModel {
55
-        id: Default::default(),
56
-        object: Book,
57
-        name: "name".to_string(),
58
-        optional: false,
59
-        multiple: false,
60
-        data_type: DataType::String,
61
-        default: None
62
-    })?;
63
-
64
-    let BookToRecipe = store.define_relation(model::RelationModel {
65
-        id: Default::default(),
66
-        object: Recipe,
67
-        name: "book reference".to_string(),
68
-        optional: true,
69
-        multiple: true,
70
-        related: Book
71
-    })?;
72
-
73
-    let BookToRecipePage = store.define_property(model::PropertyModel {
74
-        id: Default::default(),
75
-        object: BookToRecipe,
76
-        name: "page".to_string(),
77
-        optional: false,
78
-        multiple: false,
79
-        data_type: DataType::Integer,
80
-        default: None
81
-    })?;
82
-
83
-    debug!("{:#?}", store);
84
-
85
-    let MyBook1 = store.insert_object(InsertObj {
86
-        model_id: Book,
87
-        values: vec![
88
-            InsertValue::new(BookName, TypedValue::String("Recipe Book 1".into())),
89
-        ],
90
-        relations: vec![],
91
-    })?;
92
-
93
-    store.insert_object(InsertObj {
94
-        model_id: Recipe,
95
-        values: vec![
96
-            InsertValue::new(RecipeTitle, TypedValue::String("Pancakes".into())),
97
-        ],
98
-        relations: vec![
99
-            InsertRel {
100
-                model_id: BookToRecipe,
101
-                related_id: MyBook1,
102
-                values: vec![
103
-                    InsertValue::new(BookToRecipePage, TypedValue::Integer(123))
104
-                ]
105
-            }
106
-        ],
107
-    })?;
108
-
109
-    debug!("{:#?}", store);
110
-
111
-    let as_s = serde_json::to_string_pretty(&store).unwrap();
112
-
113
-    println!("{}", as_s);
114
-
115
-    let _back : InMemoryStorage = serde_json::from_str(&as_s)?;
116
-
117
-    debug!("After unpack: {:#?}", store);
118
-
119
-    Ok(())
120
-}

+ 13 - 5
yopa-web/resources/src/components/EditObjectForm.vue View File

@@ -134,13 +134,21 @@ export default {
134 134
 </script>
135 135
 
136 136
 <template>
137
-  <h2>Edit {{ model.name }}</h2>
138
-
139
-  <p><input type="button" value="Save" @click="trySave"></p>
137
+  <div class="cols">
138
+    <div class="col-9">
139
+      <h2>Edit {{ model.name }}</h2>
140
+    </div>
141
+    <div class="col-3 text-right">
142
+      <button @click="trySave" class="btn btn-primary">
143
+        <i class="icon icon-check"></i>
144
+        Save
145
+      </button>
146
+    </div>
147
+  </div>
140 148
 
141
-  <table>
149
+  <div class="form-horizontal">
142 150
     <edit-property v-for="(property, pi) in properties" :model="property" :values="values[property.id]" :key="pi"></edit-property>
143
-  </table>
151
+  </div>
144 152
 
145 153
   <div v-if="haveRelations">
146 154
     <h3>Relations</h3>

+ 36 - 22
yopa-web/resources/src/components/EditPropertyField.vue View File

@@ -19,7 +19,7 @@ export default {
19 19
       });
20 20
 
21 21
       Vue.nextTick(() => {
22
-        this.fieldRefs[this.values.length-1].focus();
22
+        this.fieldRefs[this.values.length - 1].focus();
23 23
       })
24 24
     },
25 25
     removeValue(vi) {
@@ -38,25 +38,39 @@ export default {
38 38
 </script>
39 39
 
40 40
 <template>
41
-  <tr v-if="values.length===0">
42
-    <th @click="addValue">{{model.name}}</th>
43
-    <td>
44
-      <a href="#" @click="addValue">Add</a>
45
-    </td>
46
-  </tr>
47
-  <tr v-else v-for="(instance, vi) in values" :key="vi">
48
-    <th :rowspan="values.length + model.multiple" v-if="vi == 0"><label :for="widget_id">{{model.name}}</label></th>
49
-    <td>
50
-      <string-value  :ref="setFieldRef" :value="instance.value" :id="vi===0?widget_id:null" v-if="model.data_type==='String'"></string-value>
51
-      <integer-value :ref="setFieldRef" :value="instance.value" :id="vi===0?widget_id:null" v-if="model.data_type==='Integer'"></integer-value>
52
-      <decimal-value :ref="setFieldRef" :value="instance.value" :id="vi===0?widget_id:null" v-if="model.data_type==='Decimal'"></decimal-value>
53
-      <boolean-value :ref="setFieldRef" :value="instance.value" :id="vi===0?widget_id:null" v-if="model.data_type==='Boolean'"></boolean-value>
54
-      <a href="#" @click="removeValue(vi)" v-if="vi > 0 || model.optional" style="margin-left:5px">X</a>
55
-    </td>
56
-  </tr>
57
-  <tr v-if="values.length > 0 && model.multiple">
58
-    <td>
59
-      <a href="#" @click="addValue">Add</a>
60
-    </td>
61
-  </tr>
41
+  <div v-if="values.length===0" class="form-group">
42
+    <div class="col-3">
43
+      <label class="form-label" :for="widget_id">{{ model.name }}</label>
44
+    </div>
45
+    <div class="col-9">
46
+      <a class="btn" @click="addValue">
47
+        <i class="icon icon-plus"></i>
48
+        Add
49
+      </a>
50
+    </div>
51
+  </div>
52
+  <div v-if="values.length>0" class="form-group" v-for="(instance, vi) in values" :key="vi">
53
+    <div class="col-3">
54
+      <label class="form-label" :for="widget_id" v-if="vi===0">{{ model.name }}</label>
55
+    </div>
56
+    <div class="col-9">
57
+      <string-value :ref="setFieldRef" :value="instance.value" :id="vi === 0 ? widget_id : null" v-if="model.data_type==='String'"></string-value>
58
+      <integer-value :ref="setFieldRef" :value="instance.value" :id="vi === 0 ? widget_id : null" v-if="model.data_type==='Integer'"></integer-value>
59
+      <decimal-value :ref="setFieldRef" :value="instance.value" :id="vi === 0 ? widget_id : null" v-if="model.data_type==='Decimal'"></decimal-value>
60
+      <boolean-value :ref="setFieldRef" :value="instance.value" :id="vi === 0 ? widget_id : null" v-if="model.data_type==='Boolean'"></boolean-value>
61
+      <a class="btn btn-delete ml-1" @click="removeValue(vi)" v-if="values.length > 1 || model.optional">
62
+        <i class="icon icon-cross"></i>
63
+        Delete
64
+      </a>
65
+    </div>
66
+  </div>
67
+  <div v-if="values.length>0 && model.multiple" class="form-group">
68
+    <div class="col-3"></div>
69
+    <div class="col-9">
70
+      <a class="btn" @click="addValue">
71
+        <i class="icon icon-plus"></i>
72
+        Add
73
+      </a>
74
+    </div>
75
+  </div>
62 76
 </template>

+ 29 - 16
yopa-web/resources/src/components/EditRelationForm.vue View File

@@ -104,22 +104,35 @@ export default {
104 104
 </style>
105 105
 
106 106
 <template>
107
-  <div class="new-relation" v-for="(instance, ri) in instances" :key="ri">
108
-    <b>{{ model.name }} -&gt; {{ related_model.name }}
109
-      <select v-model="instance.related">
110
-        <option v-for="(name, id) in object_names" :value="id">{{name}}</option>
111
-      </select>
112
-    </b>
113
-
114
-    <a href="#" v-if="model.multiple || model.optional && instances.length > 0"
115
-       style="margin-left: 5px"
116
-       @click="removeInstance(ri)">X</a>
117
-
118
-    <table v-if="properties">
119
-      <edit-property v-for="(property, id) in properties" :model="property" :values="instance.values[id]" :key="id"></edit-property>
120
-    </table>
107
+  <div class="form-horizontal panel mt-1 mb-1 p-2" v-for="(instance, ri) in instances" :key="ri">
108
+    <div class="form-group">
109
+      <div class="col-3">
110
+        <label class="form-label">{{ model.name }} -&gt; {{ related_model.name }}</label>
111
+      </div>
112
+      <div class="col-7">
113
+        <select class="form-select input-inline" v-model="instance.related">
114
+          <option v-for="(name, id) in object_names" :value="id">{{name}}</option>
115
+        </select>
116
+      </div>
117
+      <div class="col-2 text-right">
118
+        <a class="btn btn-delete" v-if="model.multiple || model.optional && instances.length > 0"
119
+           @click="removeInstance(ri)">
120
+          <i class="icon-cross icon"></i>
121
+          Delete
122
+        </a>
123
+      </div>
124
+    </div>
125
+
126
+    <edit-property v-for="(property, id) in properties"
127
+                   :model="property"
128
+                   :values="instance.values[id]" :key="id"></edit-property>
121 129
   </div>
122 130
 
123
-  <a href="#" v-if="model.multiple || model.optional && instances.length==0"
124
-     @click="addInstance">Add {{ model.name }} -&gt; {{ related_model.name }}</a><br>
131
+  <div class="mt-2 mb-2">
132
+    <a class="btn" v-if="model.multiple || model.optional && instances.length==0"
133
+       @click="addInstance">
134
+      <i class="icon icon-plus"></i>
135
+      Add {{ model.name }} -&gt; {{ related_model.name }}
136
+    </a>
137
+  </div>
125 138
 </template>

+ 2 - 2
yopa-web/resources/src/components/PropertyField.vue View File

@@ -48,12 +48,12 @@ export default {
48 48
       <integer-value :ref="setFieldRef" :value="value" :id="vi===0?widget_id:null" v-if="model.data_type==='Integer'"></integer-value>
49 49
       <decimal-value :ref="setFieldRef" :value="value" :id="vi===0?widget_id:null" v-if="model.data_type==='Decimal'"></decimal-value>
50 50
       <boolean-value :ref="setFieldRef" :value="value" :id="vi===0?widget_id:null" v-if="model.data_type==='Boolean'"></boolean-value>
51
-      <a href="#" @click="removeValue(vi)" v-if="vi > 0 || model.optional" style="margin-left:5px">X</a>
51
+      <a class="button ml-2" @click="removeValue(vi)" v-if="vi > 0 || model.optional" style="margin-left:5px">Remove</a>
52 52
     </td>
53 53
   </tr>
54 54
   <tr v-if="values.length > 0 && model.multiple">
55 55
     <td>
56
-      <a href="#" @click="addValue">Add</a>
56
+      <a class="button" @click="addValue">Add</a>
57 57
     </td>
58 58
   </tr>
59 59
 </template>

yopa-web/resources/src/components/BooleanValue.vue → yopa-web/resources/src/components/value/BooleanValue.vue View File

@@ -27,5 +27,8 @@ export default {
27 27
 </script>
28 28
 
29 29
 <template>
30
-  <input ref="input" type="checkbox" :id="id" value="true" v-model="inputValue">
30
+  <label class="form-switch input-inline">
31
+    <input ref="input" type="checkbox" :id="id" value="true" v-model="inputValue">
32
+    <i class="form-icon"></i>
33
+  </label>
31 34
 </template>

yopa-web/resources/src/components/DecimalValue.vue → yopa-web/resources/src/components/value/DecimalValue.vue View File

@@ -27,5 +27,5 @@ export default {
27 27
 </script>
28 28
 
29 29
 <template>
30
-  <input ref="input" type="number" :id="id" step="any" v-model="inputValue">
30
+  <input ref="input" class="form-input input-inline" type="number" :id="id" step="any" v-model="inputValue">
31 31
 </template>

yopa-web/resources/src/components/IntegerValue.vue → yopa-web/resources/src/components/value/IntegerValue.vue View File

@@ -27,5 +27,5 @@ export default {
27 27
 </script>
28 28
 
29 29
 <template>
30
-  <input ref="input" type="number" :id="id" step="1" v-model="inputValue">
30
+  <input ref="input" class="form-input input-inline" type="number" :id="id" step="1" v-model="inputValue">
31 31
 </template>

yopa-web/resources/src/components/StringValue.vue → yopa-web/resources/src/components/value/StringValue.vue View File

@@ -27,5 +27,5 @@ export default {
27 27
 </script>
28 28
 
29 29
 <template>
30
-  <input ref="input" type="text" :id="id" v-model="inputValue">
30
+  <input ref="input" class="form-input input-inline" type="text" :id="id" v-model="inputValue">
31 31
 </template>

+ 4 - 4
yopa-web/resources/src/main.js View File

@@ -2,10 +2,10 @@ import * as Vue from "vue";
2 2
 
3 3
 import './style/app.scss';
4 4
 
5
-import StringValue from "./components/StringValue.vue";
6
-import DecimalValue from "./components/DecimalValue.vue";
7
-import BooleanValue from "./components/BooleanValue.vue";
8
-import IntegerValue from "./components/IntegerValue.vue";
5
+import StringValue from "./components/value/StringValue.vue";
6
+import DecimalValue from "./components/value/DecimalValue.vue";
7
+import BooleanValue from "./components/value/BooleanValue.vue";
8
+import IntegerValue from "./components/value/IntegerValue.vue";
9 9
 import PropertyField from "./components/PropertyField.vue";
10 10
 
11 11
 import NewObjectForm from "./components/NewObjectForm.vue";

+ 21 - 0
yopa-web/resources/src/style/_icons-extra.scss View File

@@ -0,0 +1,21 @@
1
+@import "spectre/src/variables";
2
+
3
+$icon-border-width: $border-width-lg;
4
+
5
+.icon-home {
6
+  &::before {
7
+    border: $icon-border-width solid currentColor;
8
+    border-bottom: 0;
9
+    border-right: 0;
10
+    transform: translate(-50%, -40%) rotate(45deg);
11
+    width: .8em;
12
+    height: .8em;
13
+  }
14
+
15
+  &::after {
16
+    transform: translate(-50%, -50%);
17
+    border-bottom: $icon-border-width solid currentColor;
18
+    height: 1em;
19
+    width: .8em;
20
+  }
21
+}

+ 27 - 0
yopa-web/resources/src/style/_spectre-patches.scss View File

@@ -0,0 +1,27 @@
1
+@import "spectre/src/variables";
2
+
3
+.form-select.input-inline {
4
+  display: inline-block;
5
+  vertical-align: middle;
6
+  width: auto;
7
+}
8
+
9
+$error-color-faint: lighten($error-color, 50%) !default;
10
+$error-color-dark: darken($error-color, 3%) !default;
11
+
12
+.btn.btn-delete {
13
+  color: $error-color;
14
+  border-color: $error-color;
15
+
16
+  &:focus,
17
+  &:hover {
18
+    background: $error-color-faint;
19
+    border-color: $error-color-dark;
20
+  }
21
+  &:active,
22
+  &.active {
23
+    color: $light-color;
24
+    background: $error-color;
25
+    border-color: darken($error-color-dark, 5%);
26
+  }
27
+}

+ 14 - 1
yopa-web/resources/src/style/app.scss View File

@@ -1,5 +1,12 @@
1
-@import "common";
1
+//@import "common";
2
+//@import "bulma/bulma";
3
+@import "spectre/src/spectre";
4
+@import "spectre/src/spectre-exp";
5
+@import "spectre/src/spectre-icons";
6
+@import "icons-extra";
7
+@import "spectre-patches";
2 8
 
9
+/*
3 10
 .EditForm th {
4 11
 	text-align: left;
5 12
 	vertical-align: top;
@@ -8,3 +15,9 @@
8 15
 label {
9 16
 	cursor: pointer;
10 17
 }
18
+*/
19
+
20
+//.navbar-brand {
21
+//  font-size: 120%;
22
+//  font-weight: bold;
23
+//}

+ 21 - 0
yopa-web/resources/src/style/spectre/LICENSE View File

@@ -0,0 +1,21 @@
1
+The MIT License (MIT)
2
+
3
+Copyright (c) 2016 - 2020 Yan Zhu
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in all
13
+copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+SOFTWARE.

+ 38 - 0
yopa-web/resources/src/style/spectre/src/_accordions.scss View File

@@ -0,0 +1,38 @@
1
+// Accordions
2
+.accordion {
3
+  input:checked ~,
4
+  &[open] {
5
+    & .accordion-header > {
6
+      .icon:first-child {
7
+        transform: rotate(90deg);
8
+      }
9
+    }
10
+
11
+    & .accordion-body {
12
+      max-height: 50rem;
13
+    }
14
+  }
15
+
16
+  .accordion-header {
17
+    display: block;
18
+    padding: $unit-1 $unit-2;
19
+
20
+    .icon {
21
+      transition: transform .25s;
22
+    }
23
+  }
24
+
25
+  .accordion-body {
26
+    margin-bottom: $layout-spacing;
27
+    max-height: 0;
28
+    overflow: hidden;
29
+    transition: max-height .25s;
30
+  }
31
+}
32
+
33
+// Remove default details marker in Webkit
34
+summary.accordion-header {
35
+  &::-webkit-details-marker {
36
+    display: none;
37
+  }
38
+}

+ 20 - 0
yopa-web/resources/src/style/spectre/src/_animations.scss View File

@@ -0,0 +1,20 @@
1
+// Animations
2
+@keyframes loading {
3
+  0% {
4
+    transform: rotate(0deg);
5
+  }
6
+  100% {
7
+    transform: rotate(360deg);
8
+  }
9
+}
10
+
11
+@keyframes slide-down {
12
+  0% {
13
+    opacity: 0;
14
+    transform: translateY(-$unit-8);
15
+  }
16
+  100% {
17
+    opacity: 1;
18
+    transform: translateY(0);
19
+  }
20
+}

+ 43 - 0
yopa-web/resources/src/style/spectre/src/_asian.scss View File

@@ -0,0 +1,43 @@
1
+// Optimized for East Asian CJK
2
+html:lang(zh),
3
+html:lang(zh-Hans),
4
+.lang-zh,
5
+.lang-zh-hans {
6
+  font-family: $cjk-zh-hans-font-family;
7
+}
8
+
9
+html:lang(zh-Hant),
10
+.lang-zh-hant {
11
+  font-family: $cjk-zh-hant-font-family;
12
+}
13
+
14
+html:lang(ja),
15
+.lang-ja {
16
+  font-family: $cjk-jp-font-family;
17
+}
18
+
19
+html:lang(ko),
20
+.lang-ko {
21
+  font-family: $cjk-ko-font-family;
22
+}
23
+
24
+:lang(zh),
25
+:lang(ja),
26
+.lang-cjk {
27
+  ins,
28
+  u {
29
+    border-bottom: $border-width solid;
30
+    text-decoration: none;
31
+  }
32
+
33
+  del + del,
34
+  del + s,
35
+  ins + ins,
36
+  ins + u,
37
+  s + del,
38
+  s + s,
39
+  u + ins,
40
+  u + u {
41
+    margin-left: .125em;
42
+  }
43
+}

+ 47 - 0
yopa-web/resources/src/style/spectre/src/_autocomplete.scss View File

@@ -0,0 +1,47 @@
1
+// Autocomplete
2
+.form-autocomplete {
3
+  position: relative;
4
+
5
+  .form-autocomplete-input {
6
+    align-content: flex-start;
7
+    display: flex;
8
+    flex-wrap: wrap;
9
+    height: auto;
10
+    min-height: $unit-8;
11
+    padding: $unit-h;
12
+
13
+    &.is-focused {
14
+      @include control-shadow();
15
+      border-color: $primary-color;
16
+    }
17
+
18
+    .form-input {
19
+      border-color: transparent;
20
+      box-shadow: none;
21
+      display: inline-block;
22
+      flex: 1 0 auto;
23
+      height: $unit-6;
24
+      line-height: $unit-4;
25
+      margin: $unit-h;
26
+      width: auto;
27
+    }
28
+  }
29
+
30
+  .menu {
31
+    left: 0;
32
+    position: absolute;
33
+    top: 100%;
34
+    width: 100%;
35
+  }
36
+
37
+  &.autocomplete-oneline {
38
+    .form-autocomplete-input {
39
+      flex-wrap: nowrap;
40
+      overflow-x: auto;
41
+    }
42
+
43
+    .chip {
44
+      flex: 1 0 auto;
45
+    }
46
+  }
47
+}

+ 77 - 0
yopa-web/resources/src/style/spectre/src/_avatars.scss View File

@@ -0,0 +1,77 @@
1
+// Avatars
2
+.avatar {
3
+  @include avatar-base();
4
+  background: $primary-color;
5
+  border-radius: 50%;
6
+  color: rgba($light-color, .85);
7
+  display: inline-block;
8
+  font-weight: 300;
9
+  line-height: 1.25;
10
+  margin: 0;
11
+  position: relative;
12
+  vertical-align: middle;
13
+
14
+  &.avatar-xs {
15
+    @include avatar-base($unit-4);
16
+  }
17
+  &.avatar-sm {
18
+    @include avatar-base($unit-6);
19
+  }
20
+  &.avatar-lg {
21
+    @include avatar-base($unit-12);
22
+  }
23
+  &.avatar-xl {
24
+    @include avatar-base($unit-16);
25
+  }
26
+
27
+  img {
28
+    border-radius: 50%;
29
+    height: 100%;
30
+    position: relative;
31
+    width: 100%;
32
+    z-index: $zindex-0;
33
+  }
34
+
35
+  .avatar-icon,
36
+  .avatar-presence {
37
+    background: $bg-color-light;
38
+    bottom: 14.64%;
39
+    height: 50%;
40
+    padding: $border-width-lg;
41
+    position: absolute;
42
+    right: 14.64%;
43
+    transform: translate(50%, 50%);
44
+    width: 50%;
45
+    z-index: $zindex-0 + 1;
46
+  }
47
+
48
+  .avatar-presence {
49
+    background: $gray-color;
50
+    box-shadow: 0 0 0 $border-width-lg $light-color;
51
+    border-radius: 50%;
52
+    height: .5em;
53
+    width: .5em;
54
+
55
+    &.online {
56
+      background: $success-color;
57
+    }
58
+
59
+    &.busy {
60
+      background: $error-color;
61
+    }
62
+
63
+    &.away {
64
+      background: $warning-color;
65
+    }
66
+  }
67
+
68
+  &[data-initial]::before {
69
+    color: currentColor;
70
+    content: attr(data-initial);
71
+    left: 50%;
72
+    position: absolute;
73
+    top: 50%;
74
+    transform: translate(-50%, -50%);
75
+    z-index: $zindex-0;
76
+  }
77
+}

+ 60 - 0
yopa-web/resources/src/style/spectre/src/_badges.scss View File

@@ -0,0 +1,60 @@
1
+// Badges
2
+.badge {
3
+  position: relative;
4
+  white-space: nowrap;
5
+
6
+  &[data-badge],
7
+  &:not([data-badge]) {
8
+    &::after {
9
+      background: $primary-color;
10
+      background-clip: padding-box;
11
+      border-radius: .5rem;
12
+      box-shadow: 0 0 0 .1rem $bg-color-light;
13
+      color: $light-color;
14
+      content: attr(data-badge);
15
+      display: inline-block;
16
+      transform: translate(-.05rem, -.5rem);
17
+    }
18
+  }
19
+  &[data-badge] {
20
+    &::after {
21
+      font-size: $font-size-sm;
22
+      height: .9rem;
23
+      line-height: 1;
24
+      min-width: .9rem;
25
+      padding: .1rem .2rem;
26
+      text-align: center;
27
+      white-space: nowrap;
28
+    }
29
+  }
30
+  &:not([data-badge]),
31
+  &[data-badge=""] {
32
+    &::after {
33
+      height: 6px;
34
+      min-width: 6px;
35
+      padding: 0;
36
+      width: 6px;
37
+    }
38
+  }
39
+
40
+  // Badges for Buttons
41
+  &.btn {
42
+    &::after {
43
+      position: absolute;
44
+      top: 0;
45
+      right: 0;
46
+      transform: translate(50%, -50%);
47
+    }
48
+  }
49
+
50
+  // Badges for Avatars
51
+  &.avatar {
52
+    &::after {
53
+      position: absolute;
54
+      top: 14.64%;
55
+      right: 14.64%;
56
+      transform: translate(50%, -50%);
57
+      z-index: $zindex-1;
58
+    }
59
+  }
60
+}

+ 71 - 0
yopa-web/resources/src/style/spectre/src/_bars.scss View File

@@ -0,0 +1,71 @@
1
+// Bars
2
+.bar {
3
+  background: $bg-color-dark;
4
+  border-radius: $border-radius;
5
+  display: flex;
6
+  flex-wrap: nowrap;
7
+  height: $unit-4;
8
+  width: 100%;
9
+
10
+  &.bar-sm {
11
+    height: $unit-1;
12
+  }
13
+
14
+  // TODO: attr() support
15
+  .bar-item {
16
+    background: $primary-color;
17
+    color: $light-color;
18
+    display: block;
19
+    font-size: $font-size-sm;
20
+    flex-shrink: 0;
21
+    line-height: $unit-4;
22
+    height: 100%;
23
+    position: relative;
24
+    text-align: center;
25
+    width: 0;
26
+
27
+    &:first-child {
28
+      border-bottom-left-radius: $border-radius;
29
+      border-top-left-radius: $border-radius;
30
+    }
31
+    &:last-child {
32
+      border-bottom-right-radius: $border-radius;
33
+      border-top-right-radius: $border-radius;
34
+      flex-shrink: 1;
35
+    }
36
+  }
37
+}
38
+
39
+// Slider bar
40
+.bar-slider {
41
+  height: $border-width-lg;
42
+  margin: $layout-spacing 0;
43
+  position: relative;
44
+
45
+  .bar-item {
46
+    left: 0;
47
+    padding: 0;
48
+    position: absolute;
49
+    &:not(:last-child):first-child {
50
+      background: $bg-color-dark;
51
+      z-index: $zindex-0;
52
+    }
53
+  }
54
+
55
+  .bar-slider-btn {
56
+    background: $primary-color;
57
+    border: 0;
58
+    border-radius: 50%;
59
+    height: $unit-3;
60
+    padding: 0;
61
+    position: absolute;
62
+    right: 0;
63
+    top: 50%;
64
+    transform: translate(50%, -50%);
65
+    width: $unit-3;
66
+
67
+    &:active {
68
+      box-shadow: 0 0 0 .1rem $primary-color;
69
+    }
70
+  }
71
+}

+ 44 - 0
yopa-web/resources/src/style/spectre/src/_base.scss View File

@@ -0,0 +1,44 @@
1
+// Base
2
+*,
3
+*::before,
4
+*::after {
5
+  box-sizing: inherit;
6
+}
7
+
8
+html {
9
+  box-sizing: border-box;
10
+  font-size: $html-font-size;
11
+  line-height: $html-line-height;
12
+  -webkit-tap-highlight-color: transparent;
13
+}
14
+
15
+body {
16
+  background: $body-bg;
17
+  color: $body-font-color;
18
+  font-family: $body-font-family;
19
+  font-size: $font-size;
20
+  overflow-x: hidden;
21
+  text-rendering: optimizeLegibility;
22
+}
23
+
24
+a {
25
+  color: $link-color;
26
+  outline: none;
27
+  text-decoration: none;
28
+
29
+  &:focus {
30
+    @include control-shadow();
31
+  }
32
+
33
+  &:focus,
34
+  &:hover,
35
+  &:active,
36
+  &.active {
37
+    color: $link-color-dark;
38
+    text-decoration: underline;
39
+  }
40
+
41
+  &:visited {
42
+    color: $link-color-light;
43
+  }
44
+}

+ 29 - 0
yopa-web/resources/src/style/spectre/src/_breadcrumbs.scss View File

@@ -0,0 +1,29 @@
1
+// Breadcrumbs
2
+.breadcrumb {
3
+  list-style: none;
4
+  margin: $unit-1 0;
5
+  padding: $unit-1 0;
6
+
7
+  .breadcrumb-item {
8
+    color: $gray-color-dark;
9
+    display: inline-block;
10
+    margin: 0;
11
+    padding: $unit-1 0;
12
+
13
+    &:not(:last-child) {
14
+      margin-right: $unit-1;
15
+
16
+      a {
17
+        color: $gray-color-dark;
18
+      }
19
+    }
20
+
21
+    &:not(:first-child) {
22
+      &::before {
23
+        color: $gray-color-dark;
24
+        content: "/";
25
+        padding-right: $unit-2;
26
+      }
27
+    }
28
+  }
29
+}

+ 193 - 0
yopa-web/resources/src/style/spectre/src/_buttons.scss View File

@@ -0,0 +1,193 @@
1
+// Buttons
2
+.btn {
3
+  appearance: none;
4
+  background: $bg-color-light;
5
+  border: $border-width solid $primary-color;
6
+  border-radius: $border-radius;
7
+  color: $primary-color;
8
+  cursor: pointer;
9
+  display: inline-block;
10
+  font-size: $font-size;
11
+  height: $control-size;
12
+  line-height: $line-height;
13
+  outline: none;
14
+  padding: $control-padding-y $control-padding-x;
15
+  text-align: center;
16
+  text-decoration: none;
17
+  transition: background .2s, border .2s, box-shadow .2s, color .2s;
18
+  user-select: none;
19
+  vertical-align: middle;
20
+  white-space: nowrap;
21
+  &:focus {
22
+    @include control-shadow();
23
+  }
24
+  &:focus,
25
+  &:hover {
26
+    background: $secondary-color;
27
+    border-color: $primary-color-dark;
28
+    text-decoration: none;
29
+  }
30
+  &:active,
31
+  &.active {
32
+    background: $primary-color-dark;
33
+    border-color: darken($primary-color-dark, 5%);
34
+    color: $light-color;
35
+    text-decoration: none;
36
+    &.loading {
37
+      &::after {
38
+        border-bottom-color: $light-color;
39
+        border-left-color: $light-color;
40
+      }
41
+    }
42
+  }
43
+  &[disabled],
44
+  &:disabled,
45
+  &.disabled {
46
+    cursor: default;
47
+    opacity: .5;
48
+    pointer-events: none;
49
+  }
50
+
51
+  // Button Primary
52
+  &.btn-primary {
53
+    background: $primary-color;
54
+    border-color: $primary-color-dark;
55
+    color: $light-color;
56
+    &:focus,
57
+    &:hover {
58
+      background: darken($primary-color-dark, 2%);
59
+      border-color: darken($primary-color-dark, 5%);
60
+      color: $light-color;
61
+    }
62
+    &:active,
63
+    &.active {
64
+      background: darken($primary-color-dark, 4%);
65
+      border-color: darken($primary-color-dark, 7%);
66
+      color: $light-color;
67
+    }
68
+    &.loading {
69
+      &::after {
70
+        border-bottom-color: $light-color;
71
+        border-left-color: $light-color;
72
+      }
73
+    }
74
+  }
75
+
76
+  // Button Colors
77
+  &.btn-success {
78
+    @include button-variant($success-color);
79
+  }
80
+
81
+  &.btn-error {
82
+    @include button-variant($error-color);
83
+  }
84
+
85
+  // Button Link
86
+  &.btn-link {
87
+    background: transparent;
88
+    border-color: transparent;
89
+    color: $link-color;
90
+    &:focus,
91
+    &:hover,
92
+    &:active,
93
+    &.active {
94
+      color: $link-color-dark;
95
+    }
96
+  }
97
+
98
+  // Button Sizes
99
+  &.btn-sm {
100
+    font-size: $font-size-sm;
101
+    height: $control-size-sm;
102
+    padding: $control-padding-y-sm $control-padding-x-sm;
103
+  }
104
+
105
+  &.btn-lg {
106
+    font-size: $font-size-lg;
107
+    height: $control-size-lg;
108
+    padding: $control-padding-y-lg $control-padding-x-lg;
109
+  }
110
+
111
+  // Button Block
112
+  &.btn-block {
113
+    display: block;
114
+    width: 100%;
115
+  }
116
+
117
+  // Button Action
118
+  &.btn-action {
119
+    width: $control-size;
120
+    padding-left: 0;
121
+    padding-right: 0;
122
+
123
+    &.btn-sm {
124
+      width: $control-size-sm;
125
+    }
126
+
127
+    &.btn-lg {
128
+      width: $control-size-lg;
129
+    }
130
+  }
131
+
132
+  // Button Clear
133
+  &.btn-clear {
134
+    background: transparent;
135
+    border: 0;
136
+    color: currentColor;
137
+    height: $unit-5;
138
+    line-height: $unit-4;
139
+    margin-left: $unit-1;
140
+    margin-right: -2px;
141
+    opacity: 1;
142
+    padding: $unit-h;
143
+    text-decoration: none;
144
+    width: $unit-5;
145
+
146
+    &:focus,
147
+    &:hover {
148
+      background: rgba($bg-color, .5);
149
+      opacity: .95;
150
+    }
151
+
152
+    &::before {
153
+      content: "\2715";
154
+    }
155
+  }
156
+}
157
+
158
+// Button groups
159
+.btn-group {
160
+  display: inline-flex;
161
+  flex-wrap: wrap;
162
+
163
+  .btn {
164
+    flex: 1 0 auto;
165
+    &:first-child:not(:last-child) {
166
+      border-bottom-right-radius: 0;
167
+      border-top-right-radius: 0;
168
+    }
169
+    &:not(:first-child):not(:last-child) {
170
+      border-radius: 0;
171
+      margin-left: -$border-width;
172
+    }
173
+    &:last-child:not(:first-child) {
174
+      border-bottom-left-radius: 0;
175
+      border-top-left-radius: 0;
176
+      margin-left: -$border-width;
177
+    }
178
+    &:focus,
179
+    &:hover,
180
+    &:active,
181
+    &.active {
182
+      z-index: $zindex-0;
183
+    }
184
+  }
185
+
186
+  &.btn-group-block {
187
+    display: flex;
188
+
189
+    .btn {
190
+      flex: 1 0 0;
191
+    }
192
+  }
193
+}

+ 222 - 0
yopa-web/resources/src/style/spectre/src/_calendars.scss View File

@@ -0,0 +1,222 @@
1
+// Calendars
2
+.calendar {
3
+  border: $border-width solid $border-color;
4
+  border-radius: $border-radius;
5
+  display: block;
6
+  min-width: 280px;
7
+
8
+  .calendar-nav {
9
+    align-items: center;
10
+    background: $bg-color;
11
+    border-top-left-radius: $border-radius;
12
+    border-top-right-radius: $border-radius;
13
+    display: flex;
14
+    font-size: $font-size-lg;
15
+    padding: $layout-spacing;
16
+  }
17
+
18
+  .calendar-header,
19
+  .calendar-body {
20
+    display: flex;
21
+    flex-wrap: wrap;
22
+    justify-content: center;
23
+    padding: $layout-spacing 0;
24
+
25
+    .calendar-date {
26
+      flex: 0 0 14.28%; // 7 calendar-items each row
27
+      max-width: 14.28%;
28
+    }
29
+  }
30
+
31
+  .calendar-header {
32
+    background: $bg-color;
33
+    border-bottom: $border-width solid $border-color;
34
+    color: $gray-color;
35
+    font-size: $font-size-sm;
36
+    text-align: center;
37
+  }
38
+
39
+  .calendar-body {
40
+    color: $gray-color-dark;
41
+  }
42
+
43
+  .calendar-date {
44
+    border: 0;
45
+    padding: $unit-1;
46
+
47
+    .date-item {
48
+      appearance: none;
49
+      background: transparent;
50
+      border: $border-width solid transparent;
51
+      border-radius: 50%;
52
+      color: $gray-color-dark;
53
+      cursor: pointer;
54
+      font-size: $font-size-sm;
55
+      height: $unit-7;
56
+      line-height: $unit-5;
57
+      outline: none;
58
+      padding: $unit-h;
59
+      position: relative;
60
+      text-align: center;
61
+      text-decoration: none;
62
+      transition: background .2s, border .2s, box-shadow .2s, color .2s;
63
+      vertical-align: middle;
64
+      white-space: nowrap;
65
+      width: $unit-7;
66
+
67
+      &.date-today {
68
+        border-color: $secondary-color-dark;
69
+        color: $primary-color;
70
+      }
71
+
72
+      &:focus {
73
+        @include control-shadow();
74
+      }
75
+
76
+      &:focus,
77
+      &:hover {
78
+        background: $secondary-color-light;
79
+        border-color: $secondary-color-dark;
80
+        color: $primary-color;
81
+        text-decoration: none;
82
+      }
83
+      &:active,
84
+      &.active {
85
+        background: $primary-color-dark;
86
+        border-color: darken($primary-color-dark, 5%);
87
+        color: $light-color;
88
+      }
89
+
90
+      // Calendar badge support
91
+      &.badge {
92
+        &::after {
93
+          position: absolute;
94
+          top: 3px;
95
+          right: 3px;
96
+          transform: translate(50%, -50%);
97
+        }
98
+      }
99
+    }
100
+
101
+    .date-item,
102
+    .calendar-event {
103
+      &:disabled,
104
+      &.disabled {
105
+        cursor: default;
106
+        opacity: .25;
107
+        pointer-events: none;
108
+      }
109
+    }
110
+
111
+    &.prev-month,
112
+    &.next-month {
113
+      .date-item,
114
+      .calendar-event {
115
+        opacity: .25;
116
+      }
117
+    }
118
+  }
119
+
120
+  .calendar-range {
121
+    position: relative;
122
+
123
+    &::before {
124
+      background: $secondary-color;
125
+      content: "";
126
+      height: $unit-7;
127
+      left: 0;
128
+      position: absolute;
129
+      right: 0;
130
+      top: 50%;
131
+      transform: translateY(-50%);
132
+    }
133
+    &.range-start {
134
+      &::before {
135
+        left: 50%;
136
+      }
137
+    }
138
+    &.range-end {
139
+      &::before {
140
+        right: 50%;
141
+      }
142
+    }
143
+
144
+    &.range-start,
145
+    &.range-end {
146
+      .date-item {
147
+        background: $primary-color-dark;
148
+        border-color: darken($primary-color-dark, 5%);
149
+        color: $light-color;
150
+      }
151
+    }
152
+
153
+    .date-item {
154
+      color: $primary-color;
155
+    }
156
+  }
157
+
158
+  // Calendars size
159
+  &.calendar-lg {
160
+    .calendar-body {
161
+      padding: 0;
162
+
163
+      .calendar-date {
164
+        border-bottom: $border-width solid $border-color;
165
+        border-right: $border-width solid $border-color;
166
+        display: flex;
167
+        flex-direction: column;
168
+        height: 5.5rem;
169
+        padding: 0;
170
+
171
+        &:nth-child(7n) {
172
+          border-right: 0;
173
+        }
174
+        &:nth-last-child(-n+7) {
175
+          border-bottom: 0;
176
+        }
177
+      }
178
+    }
179
+
180
+    .date-item {
181
+      align-self: flex-end;
182
+      height: $unit-7;
183
+      margin-right: $layout-spacing-sm;
184
+      margin-top: $layout-spacing-sm;
185
+    }
186
+
187
+    .calendar-range {
188
+      &::before {
189
+        top: 19px;
190
+      }
191
+      &.range-start {
192
+        &::before {
193
+          left: auto;
194
+          width: 19px;
195
+        }
196
+      }
197
+      &.range-end {
198
+        &::before {
199
+          right: 19px;
200
+        }
201
+      }
202
+    }
203
+
204
+    .calendar-events {
205
+      flex-grow: 1;
206
+      line-height: 1;
207
+      overflow-y: auto;
208
+      padding: $layout-spacing-sm;
209
+    }
210
+
211
+    .calendar-event {
212
+      border-radius: $border-radius;
213
+      font-size: $font-size-sm;
214
+      display: block;
215
+      margin: $unit-h auto;
216
+      overflow: hidden;
217
+      padding: 3px 4px;
218
+      text-overflow: ellipsis;
219
+      white-space: nowrap;
220
+    }
221
+  }
222
+}

+ 43 - 0
yopa-web/resources/src/style/spectre/src/_cards.scss View File

@@ -0,0 +1,43 @@
1
+// Cards
2
+.card {
3
+  background: $bg-color-light;
4
+  border: $border-width solid $border-color;
5
+  border-radius: $border-radius;
6
+  display: flex;
7
+  flex-direction: column;
8
+
9
+  .card-header,
10
+  .card-body,
11
+  .card-footer {
12
+    padding: $layout-spacing-lg;
13
+    padding-bottom: 0;
14
+
15
+    &:last-child {
16
+      padding-bottom: $layout-spacing-lg;
17
+    }
18
+  }
19
+
20
+  .card-body {
21
+    flex: 1 1 auto;
22
+  }
23
+
24
+  .card-image {
25
+    padding-top: $layout-spacing-lg;
26
+
27
+    &:first-child {
28
+      padding-top: 0;
29
+
30
+      img {
31
+        border-top-left-radius: $border-radius;
32
+        border-top-right-radius: $border-radius;
33
+      }
34
+    }
35
+
36
+    &:last-child {
37
+      img {
38
+        border-bottom-left-radius: $border-radius;
39
+        border-bottom-right-radius: $border-radius;
40
+      }
41
+    }
42
+  }
43
+}

+ 136 - 0
yopa-web/resources/src/style/spectre/src/_carousels.scss View File

@@ -0,0 +1,136 @@
1
+// Carousels
2
+// The number of carousel images
3
+$carousel-number: 8;
4
+
5
+%carousel-image-checked { 
6
+  animation: carousel-slidein .75s ease-in-out 1;
7
+  opacity: 1;
8
+  z-index: $zindex-1;
9
+}
10
+
11
+%carousel-nav-checked { 
12
+  color: $gray-color-light;
13
+}
14
+
15
+.carousel {
16
+  background: $bg-color;
17
+  display: block;
18
+  overflow: hidden;
19
+  position: relative;
20
+  width: 100%;
21
+  -webkit-overflow-scrolling: touch;
22
+  z-index: $zindex-0;
23
+
24
+  .carousel-container {
25
+    height: 100%;
26
+    left: 0;
27
+    position: relative;
28
+    &::before {
29
+      content: "";
30
+      display: block;
31
+      padding-bottom: 56.25%;
32
+    }
33
+
34
+    .carousel-item {
35
+      animation: carousel-slideout 1s ease-in-out 1;
36
+      height: 100%;
37
+      left: 0;
38
+      margin: 0;
39
+      opacity: 0;
40
+      position: absolute;
41
+      top: 0;
42
+      width: 100%;
43
+
44
+      &:hover {
45
+        .item-prev,
46
+        .item-next {
47
+          opacity: 1;
48
+        }
49
+      }
50
+    }
51
+
52
+    .item-prev,
53
+    .item-next {
54
+      background: rgba($gray-color-light, .25);
55
+      border-color: rgba($gray-color-light, .5);
56
+      color: $gray-color-light;
57
+      opacity: 0;
58
+      position: absolute;
59
+      top: 50%;
60
+      transition: all .4s;
61
+      transform: translateY(-50%);
62
+      z-index: $zindex-1;
63
+    }
64
+    .item-prev {
65
+      left: 1rem;
66
+    }
67
+    .item-next {
68
+      right: 1rem;
69
+    }
70
+  }
71
+
72
+  .carousel-locator {
73
+    @for $i from 1 through ($carousel-number) {
74
+      &:nth-of-type(#{$i}):checked ~ .carousel-container .carousel-item:nth-of-type(#{$i}) {
75
+        @extend %carousel-image-checked;
76
+      }
77
+    }
78
+
79
+    @for $i from 1 through ($carousel-number) {
80
+      &:nth-of-type(#{$i}):checked ~ .carousel-nav .nav-item:nth-of-type(#{$i}) {
81
+        @extend %carousel-nav-checked;
82
+      }
83
+    }
84
+  }
85
+
86
+  .carousel-nav {
87
+    bottom: $layout-spacing;
88
+    display: flex;
89
+    justify-content: center;
90
+    left: 50%;
91
+    position: absolute;
92
+    transform: translateX(-50%);
93
+    width: 10rem;
94
+    z-index: $zindex-1;
95
+
96
+    .nav-item {
97
+      color: rgba($gray-color-light, .5);
98
+      display: block;
99
+      flex: 1 0 auto;
100
+      height: $unit-8;
101
+      margin: $unit-1;
102
+      max-width: 2.5rem;
103
+      position: relative;
104
+
105
+      &::before {
106
+        background: currentColor;
107
+        content: "";
108
+        display: block;
109
+        height: $unit-h;
110
+        position: absolute;
111
+        top: .5rem;
112
+        width: 100%;
113
+      }
114
+    }
115
+  }
116
+}
117
+
118
+@keyframes carousel-slidein {
119
+  0% {
120
+    transform: translateX(100%);
121
+  }
122
+  100% {
123
+    transform: translateX(0);
124
+  }
125
+}
126
+
127
+@keyframes carousel-slideout {
128
+  0% {
129
+    opacity: 1;
130
+    transform: translateX(0);
131
+  }
132
+  100% {
133
+    opacity: 1;
134
+    transform: translateX(-50%);
135
+  }
136
+}

+ 33 - 0
yopa-web/resources/src/style/spectre/src/_chips.scss View File

@@ -0,0 +1,33 @@
1
+// Chips
2
+.chip {
3
+  align-items: center;
4
+  background: $bg-color-dark;
5
+  border-radius: 5rem;
6
+  display: inline-flex;
7
+  font-size: 90%;
8
+  height: $unit-6;
9
+  line-height: $unit-4;
10
+  margin: $unit-h;
11
+  max-width: $control-width-sm;
12
+  overflow: hidden;
13
+  padding: $unit-1 $unit-2;
14
+  text-decoration: none;
15
+  text-overflow: ellipsis;
16
+  vertical-align: middle;
17
+  white-space: nowrap;
18
+
19
+  &.active {
20
+    background: $primary-color;
21
+    color: $light-color;
22
+  }
23
+
24
+  .avatar {
25
+    margin-left: -$unit-2;
26
+    margin-right: $unit-1;
27
+  }
28
+
29
+  .btn-clear {
30
+    border-radius: 50%;
31
+    transform: scale(.75);
32
+  }
33
+}

+ 31 - 0
yopa-web/resources/src/style/spectre/src/_codes.scss View File

@@ -0,0 +1,31 @@
1
+// Codes
2
+code {
3
+  @include label-base();
4
+  @include label-variant($code-color, lighten($code-color, 42.5%));
5
+  font-size: 85%;
6
+}
7
+
8
+.code {
9
+  border-radius: $border-radius;
10
+  color: $body-font-color;
11
+  position: relative;
12
+
13
+  &::before {
14
+    color: $gray-color;
15
+    content: attr(data-lang);
16
+    font-size: $font-size-sm;
17
+    position: absolute;
18
+    right: $layout-spacing;
19
+    top: $unit-h;
20
+  }
21
+
22
+  code {
23
+    background: $bg-color;
24
+    color: inherit;
25
+    display: block;
26
+    line-height: 1.5;
27
+    overflow-x: auto;
28
+    padding: 1rem;
29
+    width: 100%;
30
+  }
31
+}

+ 116 - 0
yopa-web/resources/src/style/spectre/src/_comparison-sliders.scss View File

@@ -0,0 +1,116 @@
1
+// Image comparison slider
2
+// Credit: http://codepen.io/solipsistacp/pen/Gpmaq
3
+.comparison-slider {
4
+  height: 50vh;
5
+  overflow: hidden;
6
+  position: relative;
7
+  width: 100%;
8
+  -webkit-overflow-scrolling: touch;
9
+
10
+  .comparison-before,
11
+  .comparison-after {
12
+    height: 100%;
13
+    left: 0;
14
+    margin: 0;
15
+    overflow: hidden;
16
+    position: absolute;
17
+    top: 0;
18
+
19
+    img {
20
+      height: 100%;
21
+      object-fit: cover;
22
+      object-position: left center;
23
+      position: absolute;
24
+      width: 100%;
25
+    }
26
+  }
27
+
28
+  .comparison-before {
29
+    width: 100%;
30
+    z-index: 1;
31
+
32
+    .comparison-label {
33
+      right: $unit-4;
34
+    }
35
+  }
36
+
37
+  .comparison-after {
38
+    max-width: 100%;
39
+    min-width: 0;
40
+    z-index: 2;
41
+
42
+    &::before {
43
+      background: transparent;
44
+      content: "";
45
+      cursor: default;
46
+      height: 100%;
47
+      left: 0;
48
+      position: absolute;
49
+      right: $unit-4;
50
+      top: 0;
51
+      z-index: $zindex-0;
52
+    }
53
+
54
+    &::after {
55
+      background: currentColor;
56
+      border-radius: 50%;
57
+      box-shadow: 0 -5px, 0 5px;
58
+      color: $light-color;
59
+      content: "";
60
+      height: 3px;
61
+      pointer-events: none;
62
+      position: absolute;
63
+      right: $unit-2;
64
+      top: 50%;
65
+      transform: translate(50%, -50%);
66
+      width: 3px;
67
+    }
68
+
69
+    .comparison-label {
70
+      left: $unit-4;
71
+    }
72
+  }
73
+
74
+  .comparison-resizer {
75
+    animation: first-run 1.5s 1 ease-in-out;
76
+    cursor: ew-resize;
77
+    height: $unit-4;
78
+    left: 0;
79
+    max-width: 100%;
80
+    min-width: $unit-4;
81
+    opacity: 0;
82
+    outline: none;
83
+    position: relative;
84
+    resize: horizontal;
85
+    top: 50%;
86
+    transform: translateY(-50%) scaleY(30);
87
+    width: 0;
88
+  }
89
+
90
+  .comparison-label {
91
+    background: rgba($dark-color, .5);
92
+    bottom: $unit-4;
93
+    color: $light-color;
94
+    padding: $unit-1 $unit-2;
95
+    position: absolute;
96
+    user-select: none;
97
+  }
98
+}
99
+
100
+@keyframes first-run {
101
+  0% {
102
+    width: 0;
103
+  }
104
+  25% {
105
+    width: $unit-12;
106
+  }
107
+  50% {
108
+    width: $unit-4;
109
+  }
110
+  75% {
111
+    width: $unit-6;
112
+  }
113
+  100% {
114
+    width: 0;
115
+  }
116
+}

+ 36 - 0
yopa-web/resources/src/style/spectre/src/_dropdowns.scss View File

@@ -0,0 +1,36 @@
1
+// Dropdown
2
+.dropdown {
3
+  display: inline-block;
4
+  position: relative;
5
+
6
+  .menu {
7
+    animation: slide-down .15s ease 1;
8
+    display: none;
9
+    left: 0;
10
+    max-height: 50vh;
11
+    overflow-y: auto;
12
+    position: absolute;
13
+    top: 100%;
14
+  }
15
+
16
+  &.dropdown-right {
17
+    .menu {
18
+      left: auto;
19
+      right: 0;
20
+    }
21
+  }
22
+
23
+  &.active .menu,
24
+  .dropdown-toggle:focus + .menu,
25
+  .menu:hover {
26
+    display: block;
27
+  }
28
+
29
+  // Fix dropdown-toggle border radius in button groups
30
+  .btn-group {
31
+    .dropdown-toggle:nth-last-child(2) {
32
+      border-bottom-right-radius: $border-radius;
33
+      border-top-right-radius: $border-radius;
34
+    }
35
+  }
36
+}

+ 21 - 0
yopa-web/resources/src/style/spectre/src/_empty.scss View File

@@ -0,0 +1,21 @@
1
+// Empty states (or Blank slates)
2
+.empty {
3
+  background: $bg-color;
4
+  border-radius: $border-radius;
5
+  color: $gray-color-dark;
6
+  text-align: center;
7
+  padding: $unit-16 $unit-8;
8
+
9
+  .empty-icon {
10
+    margin-bottom: $layout-spacing-lg;
11
+  }
12
+
13
+  .empty-title,
14
+  .empty-subtitle {
15
+    margin: $layout-spacing auto;
16
+  }
17
+
18
+  .empty-action {
19
+    margin-top: $layout-spacing-lg;
20
+  }
21
+}

+ 37 - 0
yopa-web/resources/src/style/spectre/src/_filters.scss View File

@@ -0,0 +1,37 @@
1
+// Filters 
2
+// The number of filter options 
3
+$filter-number: 8 !default;
4
+
5
+%filter-checked-nav { 
6
+  background: $primary-color;
7
+  color: $light-color;
8
+}
9
+
10
+%filter-checked-body { 
11
+  display: none;
12
+}
13
+
14
+.filter {
15
+  .filter-nav {
16
+    margin: $layout-spacing 0;
17
+  }
18
+
19
+  .filter-body {
20
+    display: flex;
21
+    flex-wrap: wrap;
22
+  }
23
+
24
+  .filter-tag {
25
+    @for $i from 0 through ($filter-number) {
26
+      &#tag-#{$i}:checked ~ .filter-nav .chip[for="tag-#{$i}"] {
27
+        @extend %filter-checked-nav;
28
+      }
29
+    }
30
+
31
+    @for $i from 1 through ($filter-number) {
32
+      &#tag-#{$i}:checked ~ .filter-body .filter-item:not([data-tag~="tag-#{$i}"]) {
33
+        @extend %filter-checked-body;
34
+      }
35
+    }
36
+  }
37
+}

+ 555 - 0
yopa-web/resources/src/style/spectre/src/_forms.scss View File

@@ -0,0 +1,555 @@
1
+// Forms
2
+.form-group {
3
+  &:not(:last-child) {
4
+    margin-bottom: $layout-spacing;
5
+  }
6
+}
7
+
8
+fieldset {
9
+  margin-bottom: $layout-spacing-lg;
10
+}
11
+
12
+legend {
13
+  font-size: $font-size-lg;
14
+  font-weight: 500;
15
+  margin-bottom: $layout-spacing-lg;
16
+}
17
+
18
+// Form element: Label
19
+.form-label {
20
+  display: block;
21
+  line-height: $line-height;
22
+  padding: $control-padding-y + $border-width 0;
23
+
24
+  &.label-sm {
25
+    font-size: $font-size-sm;
26
+    padding: $control-padding-y-sm + $border-width 0;
27
+  }
28
+
29
+  &.label-lg {
30
+    font-size: $font-size-lg;
31
+    padding: $control-padding-y-lg + $border-width 0;
32
+  }
33
+}
34
+
35
+// Form element: Input
36
+.form-input {
37
+  appearance: none;
38
+  background: $bg-color-light;
39
+  background-image: none;
40
+  border: $border-width solid $border-color-dark;
41
+  border-radius: $border-radius;
42
+  color: $body-font-color;
43
+  display: block;
44
+  font-size: $font-size;
45
+  height: $control-size;
46
+  line-height: $line-height;
47
+  max-width: 100%;
48
+  outline: none;
49
+  padding: $control-padding-y $control-padding-x;
50
+  position: relative;
51
+  transition: background .2s, border .2s, box-shadow .2s, color .2s;
52
+  width: 100%;
53
+  &:focus {
54
+    @include control-shadow();
55
+    border-color: $primary-color;
56
+  }
57
+  &::placeholder {
58
+    color: $gray-color;
59
+  }
60
+
61
+  // Input sizes
62
+  &.input-sm {
63
+    font-size: $font-size-sm;
64
+    height: $control-size-sm;
65
+    padding: $control-padding-y-sm $control-padding-x-sm;
66
+  }
67
+
68
+  &.input-lg {
69
+    font-size: $font-size-lg;
70
+    height: $control-size-lg;
71
+    padding: $control-padding-y-lg $control-padding-x-lg;
72
+  }
73
+
74
+  &.input-inline {
75
+    display: inline-block;
76
+    vertical-align: middle;
77
+    width: auto;
78
+  }
79
+
80
+  // Input types
81
+  &[type="file"] {
82
+    height: auto;
83
+  }
84
+}
85
+
86
+// Form element: Textarea
87
+textarea.form-input {
88
+  &,
89
+  &.input-lg,
90
+  &.input-sm {
91
+    height: auto;
92
+  }
93
+}
94
+
95
+// Form element: Input hint
96
+.form-input-hint {
97
+  color: $gray-color;
98
+  font-size: $font-size-sm;
99
+  margin-top: $unit-1;
100
+
101
+  .has-success &,
102
+  .is-success + & {
103
+    color: $success-color;
104
+  }
105
+
106
+  .has-error &,
107
+  .is-error + & {
108
+    color: $error-color;
109
+  }
110
+}
111
+
112
+// Form element: Select
113
+.form-select {
114
+  appearance: none;
115
+  border: $border-width solid $border-color-dark;
116
+  border-radius: $border-radius;
117
+  color: inherit;
118
+  font-size: $font-size;
119
+  height: $control-size;
120
+  line-height: $line-height;
121
+  outline: none;
122
+  padding: $control-padding-y $control-padding-x;
123
+  vertical-align: middle;
124
+  width: 100%;
125
+  background: $bg-color-light; 
126
+  &:focus {
127
+    @include control-shadow();
128
+    border-color: $primary-color;
129
+  }
130
+  &::-ms-expand {
131
+    display: none;
132
+  }
133
+
134
+  // Select sizes
135
+  &.select-sm {
136
+    font-size: $font-size-sm;
137
+    height: $control-size-sm;
138
+    padding: $control-padding-y-sm ($control-icon-size + $control-padding-x-sm) $control-padding-y-sm $control-padding-x-sm;
139
+  }
140
+
141
+  &.select-lg {
142
+    font-size: $font-size-lg;
143
+    height: $control-size-lg;
144
+    padding: $control-padding-y-lg ($control-icon-size + $control-padding-x-lg) $control-padding-y-lg $control-padding-x-lg;
145
+  }
146
+
147
+  // Multiple select
148
+  &[size],
149
+  &[multiple] {
150
+    height: auto;
151
+    padding: $control-padding-y $control-padding-x;
152
+
153
+    option {
154
+      padding: $unit-h $unit-1;
155
+    }
156
+  }
157
+  &:not([multiple]):not([size]) {
158
+    background: $bg-color-light url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center / .4rem .5rem;
159
+    padding-right: $control-icon-size + $control-padding-x;
160
+  }
161
+}
162
+
163
+// Form Icons
164
+.has-icon-left,
165
+.has-icon-right {
166
+  position: relative;
167
+
168
+  .form-icon {
169
+    height: $control-icon-size;
170
+    margin: 0 $control-padding-y;
171
+    position: absolute;
172
+    top: 50%;
173
+    transform: translateY(-50%);
174
+    width: $control-icon-size;
175
+    z-index: $zindex-0 + 1;
176
+  }
177
+}
178
+
179
+.has-icon-left {
180
+  .form-icon {
181
+    left: $border-width;
182
+  }
183
+
184
+  .form-input {
185
+    padding-left: $control-icon-size + $control-padding-y * 2;
186
+  }
187
+}
188
+
189
+.has-icon-right {
190
+  .form-icon {
191
+    right: $border-width;
192
+  }
193
+
194
+  .form-input {
195
+    padding-right: $control-icon-size + $control-padding-y * 2;
196
+  }
197
+}
198
+
199
+// Form element: Checkbox and Radio
200
+.form-checkbox,
201
+.form-radio,
202
+.form-switch {
203
+  display: block;
204
+  line-height: $line-height;
205
+  margin: ($control-size - $control-size-sm) / 2 0;
206
+  min-height: $control-size-sm;
207
+  padding: (($control-size-sm - $line-height) / 2) $control-padding-x (($control-size-sm - $line-height) / 2) ($control-icon-size + $control-padding-x);
208
+  position: relative;
209
+
210
+  input {
211
+    clip: rect(0, 0, 0, 0);
212
+    height: 1px;
213
+    margin: -1px;
214
+    overflow: hidden;
215
+    position: absolute;
216
+    width: 1px;
217
+    &:focus + .form-icon {
218
+      @include control-shadow();
219
+      border-color: $primary-color;
220
+    }
221
+    &:checked + .form-icon {
222
+      background: $primary-color;
223
+      border-color: $primary-color;
224
+    }
225
+  }
226
+
227
+  .form-icon {
228
+    border: $border-width solid $border-color-dark;
229
+    cursor: pointer;
230
+    display: inline-block;
231
+    position: absolute;
232
+    transition: background .2s, border .2s, box-shadow .2s, color .2s;
233
+  }
234
+
235
+  // Input checkbox, radio and switch sizes
236
+  &.input-sm {
237
+    font-size: $font-size-sm;
238
+    margin: 0;
239
+  }
240
+
241
+  &.input-lg {
242
+    font-size: $font-size-lg;
243
+    margin: ($control-size-lg - $control-size-sm) / 2 0;
244
+  }
245
+}
246
+
247
+.form-checkbox,
248
+.form-radio {
249
+  .form-icon {
250
+    background: $bg-color-light;
251
+    height: $control-icon-size;
252
+    left: 0;
253
+    top: ($control-size-sm - $control-icon-size) / 2;
254
+    width: $control-icon-size;
255
+  }
256
+
257
+  input {
258
+    &:active + .form-icon {
259
+      background: $bg-color-dark;
260
+    }
261
+  }
262
+}
263
+.form-checkbox {
264
+  .form-icon {
265
+    border-radius: $border-radius;
266
+  }
267
+
268
+  input {
269
+    &:checked + .form-icon {
270
+      &::before {
271
+        background-clip: padding-box;
272
+        border: $border-width-lg solid $light-color;
273
+        border-left-width: 0;
274
+        border-top-width: 0;
275
+        content: "";
276
+        height: 9px;
277
+        left: 50%;
278
+        margin-left: -3px;
279
+        margin-top: -6px;
280
+        position: absolute;
281
+        top: 50%;
282
+        transform: rotate(45deg);
283
+        width: 6px;
284
+      }
285
+    }
286
+    &:indeterminate + .form-icon {
287
+      background: $primary-color;
288
+      border-color: $primary-color;
289
+      &::before {
290
+        background: $bg-color-light;
291
+        content: "";
292
+        height: 2px;
293
+        left: 50%;
294
+        margin-left: -5px;
295
+        margin-top: -1px;
296
+        position: absolute;
297
+        top: 50%;
298
+        width: 10px;
299
+      }
300
+    }
301
+  }
302
+}
303
+.form-radio {
304
+  .form-icon {
305
+    border-radius: 50%;
306
+  }
307
+
308
+  input {
309
+    &:checked + .form-icon {
310
+      &::before {
311
+        background: $bg-color-light;
312
+        border-radius: 50%;
313
+        content: "";
314
+        height: 6px;
315
+        left: 50%;
316
+        position: absolute;
317
+        top: 50%;
318
+        transform: translate(-50%, -50%);
319
+        width: 6px;
320
+      }
321
+    }
322
+  }
323
+}
324
+
325
+// Form element: Switch
326
+.form-switch {
327
+  padding-left: ($unit-8 + $control-padding-x);
328
+
329
+  .form-icon {
330
+    background: $gray-color;
331
+    background-clip: padding-box;
332
+    border-radius: $unit-2 + $border-width;
333
+    height: $unit-4 + $border-width * 2;
334
+    left: 0;
335
+    top: ($control-size-sm - $unit-4) / 2 - $border-width;
336
+    width: $unit-8;
337
+    &::before {
338
+      background: $bg-color-light;
339
+      border-radius: 50%;
340
+      content: "";
341
+      display: block;
342
+      height: $unit-4;
343
+      left: 0;
344
+      position: absolute;
345
+      top: 0;
346
+      transition: background .2s, border .2s, box-shadow .2s, color .2s, left .2s;
347
+      width: $unit-4;
348
+    }
349
+  }
350
+
351
+  input {
352
+    &:checked + .form-icon {
353
+      &::before {
354
+        left: 14px;
355
+      }
356
+    }
357
+    &:active + .form-icon {
358
+      &::before {
359
+        background: $bg-color;
360
+      }
361
+    }
362
+  }
363
+}
364
+
365
+// Form element: Input groups
366
+.input-group {
367
+  display: flex;
368
+
369
+  .input-group-addon {
370
+    background: $bg-color;
371
+    border: $border-width solid $border-color-dark;
372
+    border-radius: $border-radius;
373
+    line-height: $line-height;
374
+    padding: $control-padding-y $control-padding-x;
375
+    white-space: nowrap;
376
+
377
+    &.addon-sm {
378
+      font-size: $font-size-sm;
379
+      padding: $control-padding-y-sm $control-padding-x-sm;
380
+    }
381
+
382
+    &.addon-lg {
383
+      font-size: $font-size-lg;
384
+      padding: $control-padding-y-lg $control-padding-x-lg;
385
+    }
386
+  }
387
+
388
+  .form-input,
389
+  .form-select {
390
+    flex: 1 1 auto;
391
+    width: 1%;
392
+  }
393
+
394
+  .input-group-btn {
395
+    z-index: $zindex-0;
396
+  }
397
+
398
+  .form-input,
399
+  .form-select,
400
+  .input-group-addon,
401
+  .input-group-btn {
402
+    &:first-child:not(:last-child) {
403
+      border-bottom-right-radius: 0;
404
+      border-top-right-radius: 0;
405
+    }
406
+    &:not(:first-child):not(:last-child) {
407
+      border-radius: 0;
408
+      margin-left: -$border-width;
409
+    }
410
+    &:last-child:not(:first-child) {
411
+      border-bottom-left-radius: 0;
412
+      border-top-left-radius: 0;
413
+      margin-left: -$border-width;
414
+    }
415
+    &:focus {
416
+      z-index: $zindex-0 + 1;
417
+    }
418
+  }
419
+
420
+  .form-select {
421
+    width: auto;
422
+  }
423
+
424
+  &.input-inline {
425
+    display: inline-flex;
426
+  }
427
+}
428
+
429
+// Form validation states
430
+.form-input,
431
+.form-select {
432
+  .has-success &,
433
+  &.is-success {
434
+    background: lighten($success-color, 53%);
435
+    border-color: $success-color;
436
+    &:focus {
437
+      @include control-shadow($success-color);
438
+    }
439
+  }
440
+
441
+  .has-error &,
442
+  &.is-error {
443
+    background: lighten($error-color, 53%);
444
+    border-color: $error-color;
445
+    &:focus {
446
+      @include control-shadow($error-color);
447
+    }
448
+  }
449
+}
450
+
451
+.form-checkbox,
452
+.form-radio,
453
+.form-switch {
454
+  .has-error &,
455
+  &.is-error {
456
+    .form-icon {
457
+      border-color: $error-color;
458
+    }
459
+
460
+    input {
461
+      &:checked + .form-icon {
462
+        background: $error-color;
463
+        border-color: $error-color;
464
+      }
465
+
466
+      &:focus + .form-icon {
467
+        @include control-shadow($error-color);
468
+        border-color: $error-color;
469
+      }
470
+    }
471
+  }
472
+}
473
+
474
+.form-checkbox {
475
+  .has-error &,
476
+  &.is-error {
477
+    input {
478
+      &:indeterminate + .form-icon {
479
+        background: $error-color;
480
+        border-color: $error-color;
481
+      }
482
+    }
483
+  }
484
+}
485
+
486
+// validation based on :placeholder-shown (Edge doesn't support it yet)
487
+.form-input {
488
+  &:not(:placeholder-shown) {
489
+    &:invalid {
490
+      border-color: $error-color;
491
+      &:focus {
492
+        @include control-shadow($error-color);
493
+        background: lighten($error-color, 53%);
494
+      }
495
+
496
+      & + .form-input-hint {
497
+        color: $error-color;
498
+      }
499
+    }
500
+  }
501
+}
502
+
503
+// Form disabled and readonly
504
+.form-input,
505
+.form-select {
506
+  &:disabled,
507
+  &.disabled {
508
+    background-color: $bg-color-dark;
509
+    cursor: not-allowed;
510
+    opacity: .5;
511
+  }
512
+}
513
+
514
+.form-input {
515
+  &[readonly] {
516
+    background-color: $bg-color;
517
+  }
518
+}
519
+
520
+input {
521
+  &:disabled,
522
+  &.disabled {
523
+    & + .form-icon {
524
+      background: $bg-color-dark;
525
+      cursor: not-allowed;
526
+      opacity: .5;
527
+    }
528
+  }
529
+}
530
+
531
+.form-switch {
532
+  input {
533
+    &:disabled,
534
+    &.disabled {
535
+      & + .form-icon::before {
536
+        background: $bg-color-light;
537
+      }
538
+    }
539
+  }
540
+}
541
+
542
+// Form horizontal
543
+.form-horizontal {
544
+  padding: $layout-spacing 0;
545
+
546
+  .form-group {
547
+    display: flex;
548
+    flex-wrap: wrap;
549
+  }
550
+}
551
+
552
+// Form inline
553
+.form-inline {
554
+  display: inline-block;
555
+}

+ 22 - 0
yopa-web/resources/src/style/spectre/src/_hero.scss View File

@@ -0,0 +1,22 @@
1
+// Hero
2
+.hero {
3
+  display: flex;
4
+  flex-direction: column;
5
+  justify-content: space-between;
6
+  padding-bottom: 4rem;
7
+  padding-top: 4rem;
8
+
9
+  &.hero-sm {
10
+    padding-bottom: 2rem;
11
+    padding-top: 2rem;
12
+  }
13
+
14
+  &.hero-lg {
15
+    padding-bottom: 8rem;
16
+    padding-top: 8rem;
17
+  }
18
+
19
+  .hero-body {
20
+    padding: $layout-spacing;
21
+  }
22
+}

+ 5 - 0
yopa-web/resources/src/style/spectre/src/_icons.scss View File

@@ -0,0 +1,5 @@
1
+// CSS Icons
2
+@import "icons/icons-core";
3
+@import "icons/icons-navigation";
4
+@import "icons/icons-action";
5
+@import "icons/icons-object";

+ 34 - 0
yopa-web/resources/src/style/spectre/src/_labels.scss View File

@@ -0,0 +1,34 @@
1
+// Labels
2
+.label {
3
+  @include label-base();
4
+  @include label-variant(lighten($body-font-color, 5%), $bg-color-dark);
5
+  display: inline-block;
6
+
7
+  // Label rounded
8
+  &.label-rounded {
9
+    border-radius: 5rem;
10
+    padding-left: .4rem;
11
+    padding-right: .4rem; 
12
+  }
13
+
14
+  // Label colors
15
+  &.label-primary {
16
+    @include label-variant($light-color, $primary-color);
17
+  }
18
+
19
+  &.label-secondary {
20
+    @include label-variant($primary-color, $secondary-color);
21
+  }
22
+
23
+  &.label-success {
24
+    @include label-variant($light-color, $success-color);
25
+  }
26
+
27
+  &.label-warning {
28
+    @include label-variant($light-color, $warning-color);
29
+  }
30
+
31
+  &.label-error {
32
+    @include label-variant($light-color, $error-color);
33
+  }
34
+}

+ 446 - 0
yopa-web/resources/src/style/spectre/src/_layout.scss View File

@@ -0,0 +1,446 @@
1
+// Layout
2
+.container {
3
+  margin-left: auto;
4
+  margin-right: auto;
5
+  padding-left: $layout-spacing;
6
+  padding-right: $layout-spacing;
7
+  width: 100%;
8
+
9
+  $grid-spacing: ($layout-spacing / ($layout-spacing * 0 + 1)) * $html-font-size;
10
+
11
+  &.grid-xl {
12
+    max-width: $grid-spacing * 2 + $size-xl;
13
+  }
14
+
15
+  &.grid-lg {
16
+    max-width: $grid-spacing * 2 + $size-lg;
17
+  }
18
+
19
+  &.grid-md {
20
+    max-width: $grid-spacing * 2 + $size-md;
21
+  }
22
+
23
+  &.grid-sm {
24
+    max-width: $grid-spacing * 2 + $size-sm;
25
+  }
26
+
27
+  &.grid-xs {
28
+    max-width: $grid-spacing * 2 + $size-xs;
29
+  }
30
+}
31
+
32
+// Responsive breakpoint system
33
+.show-xs,
34
+.show-sm,
35
+.show-md,
36
+.show-lg,
37
+.show-xl {
38
+  display: none !important;
39
+}
40
+
41
+// Responsive grid system
42
+.cols,
43
+.columns {
44
+  display: flex;
45
+  flex-wrap: wrap;
46
+  margin-left: -$layout-spacing;
47
+  margin-right: -$layout-spacing;
48
+
49
+  &.col-gapless {
50
+    margin-left: 0;
51
+    margin-right: 0;
52
+
53
+    & > .column {
54
+      padding-left: 0;
55
+      padding-right: 0;
56
+    }
57
+  }
58
+  &.col-oneline {
59
+    flex-wrap: nowrap;
60
+    overflow-x: auto;
61
+  }
62
+}
63
+[class~="col-"],
64
+.column {
65
+  flex: 1;
66
+  max-width: 100%;
67
+  padding-left: $layout-spacing;
68
+  padding-right: $layout-spacing;
69
+
70
+  &.col-12,
71
+  &.col-11,
72
+  &.col-10,
73
+  &.col-9,
74
+  &.col-8,
75
+  &.col-7,
76
+  &.col-6,
77
+  &.col-5,
78
+  &.col-4,
79
+  &.col-3,
80
+  &.col-2,
81
+  &.col-1,
82
+  &.col-auto {
83
+    flex: none;
84
+  }
85
+}
86
+.col-12 {
87
+  width: 100%;
88
+}
89
+.col-11 {
90
+  width: 91.66666667%;
91
+}
92
+.col-10 {
93
+  width: 83.33333333%;
94
+}
95
+.col-9 {
96
+  width: 75%;
97
+}
98
+.col-8 {
99
+  width: 66.66666667%;
100
+}
101
+.col-7 {
102
+  width: 58.33333333%;
103
+}
104
+.col-6 {
105
+  width: 50%;
106
+}
107
+.col-5 {
108
+  width: 41.66666667%;
109
+}
110
+.col-4 {
111
+  width: 33.33333333%;
112
+}
113
+.col-3 {
114
+  width: 25%;
115
+}
116
+.col-2 {
117
+  width: 16.66666667%;
118
+}
119
+.col-1 {
120
+  width: 8.33333333%;
121
+}
122
+.col-auto {
123
+  flex: 0 0 auto;
124
+  max-width: none;
125
+  width: auto;
126
+}
127
+.col-mx-auto {
128
+  margin-left: auto;
129
+  margin-right: auto;
130
+}
131
+.col-ml-auto {
132
+  margin-left: auto;
133
+}
134
+.col-mr-auto {
135
+  margin-right: auto;
136
+}
137
+@media (max-width: $size-xl) {
138
+  .col-xl-12,
139
+  .col-xl-11,
140
+  .col-xl-10,
141
+  .col-xl-9,
142
+  .col-xl-8,
143
+  .col-xl-7,
144
+  .col-xl-6,
145
+  .col-xl-5,
146
+  .col-xl-4,
147
+  .col-xl-3,
148
+  .col-xl-2,
149
+  .col-xl-1,
150
+  .col-xl-auto {
151
+    flex: none;
152
+  }
153
+  .col-xl-12 {
154
+    width: 100%;
155
+  }
156
+  .col-xl-11 {
157
+    width: 91.66666667%;
158
+  }
159
+  .col-xl-10 {
160
+    width: 83.33333333%;
161
+  }
162
+  .col-xl-9 {
163
+    width: 75%;
164
+  }
165
+  .col-xl-8 {
166
+    width: 66.66666667%;
167
+  }
168
+  .col-xl-7 {
169
+    width: 58.33333333%;
170
+  }
171
+  .col-xl-6 {
172
+    width: 50%;
173
+  }
174
+  .col-xl-5 {
175
+    width: 41.66666667%;
176
+  }
177
+  .col-xl-4 {
178
+    width: 33.33333333%;
179
+  }
180
+  .col-xl-3 {
181
+    width: 25%;
182
+  }
183
+  .col-xl-2 {
184
+    width: 16.66666667%;
185
+  }
186
+  .col-xl-1 {
187
+    width: 8.33333333%;
188
+  }
189
+  .col-xl-auto {
190
+    width: auto;